diff options
Diffstat (limited to 'libkmid/midfile.cc')
-rw-r--r-- | libkmid/midfile.cc | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/libkmid/midfile.cc b/libkmid/midfile.cc new file mode 100644 index 000000000..baf0a2b37 --- /dev/null +++ b/libkmid/midfile.cc @@ -0,0 +1,460 @@ +/************************************************************************** + + midfile.cc - function which reads a midi file,and creates the track classes + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "midfile.h" +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include "sndcard.h" +#include "midispec.h" +#include "mt32togm.h" +#include "sys/stat.h" +#include <config.h> + +#include <kprocess.h> +#include <qfile.h> + +int fsearch(FILE *fh,const char *text,long *ptr); + +/* This function gives the metronome tempo, from a tempo data as found in + a midi file */ +double tempoToMetronomeTempo(ulong x) +{ + return 60/((double)x/1000000); +} + +double metronomeTempoToTempo(ulong x) +{ + return ((double)60*x)/1000000; +} + +int uncompressFile(const char *gzname, char *tmpname) + // Returns 0 if OK, 1 if error (tmpname not set) +{ + QString cmd("gzip -dc " + KProcess::quote(gzname)); + FILE *infile = popen( QFile::encodeName(cmd).data(), "r"); + if (infile==NULL) { + fprintf(stderr,"ERROR : popen failed : %s\n",QFile::encodeName(cmd).data()); + return 1; + } + strcpy(tmpname, "/tmp/KMid.XXXXXXXXXX"); + int fd = mkstemp(tmpname); + if (fd == -1) + { + pclose(infile); + return 1; + } + FILE *outfile= fdopen(fd,"wb"); + if (outfile==NULL) + { + pclose(infile); + return 1; + } + int n=getc(infile); + if (n==EOF) + { + pclose(infile); + fclose(outfile); + unlink(tmpname); + return 1; + } + fputc(n,outfile); + int buf[BUFSIZ]; + n = fread(buf, 1, BUFSIZ, infile); + while (n>0) + { + fwrite(buf, 1, n, outfile); + n = fread(buf, 1, BUFSIZ, infile); + } + + pclose(infile); + + //if (pclose(infile) != 0) fprintf(stderr,"Error : pclose failed\n"); + // Is it right for pclose to always fail ? + + fclose(outfile); + return 0; +} + +MidiTrack **readMidiFile( const char *name, MidiFileInfo *info, int &ok) +{ + ok=1; + MidiTrack **tracks; + + struct stat buf; + if (stat(name,&buf) || !S_ISREG(buf.st_mode)) + { + fprintf(stderr,"ERROR: %s is not a regular file\n",name); + ok=-6; + return NULL; + } + + FILE *fh=fopen(name,"rb"); + if (fh==NULL) + { + fprintf(stderr,"ERROR: Can't open file %s\n",name); + ok=-1; + return NULL; + } + char text[4]; + text[0] = 0; + fread(text,1,4,fh); + if ((strncmp(text,"MThd",4)!=0)&&(strcmp(&name[strlen(name)-3],".gz")==0)) + { + fclose(fh); + char tempname[200]; + fprintf(stderr,"Trying to open zipped midi file...\n"); + if (uncompressFile(name,tempname)!=0) + { + fprintf(stderr,"ERROR: %s is not a (zipped) midi file\n",name); + ok=-2; + return NULL; + } + fh=fopen(tempname,"rb"); + fread(text,1,4,fh); + unlink(tempname); + } + + if (strncmp(text,"MThd",4)!=0) + { + fseek(fh,0,SEEK_SET); + long pos; + if (fsearch(fh,"MThd",&pos)==0) + { + fclose(fh); + fprintf(stderr,"ERROR: %s is not a midi file.\n",name); + ok=-2; + return NULL; + } + fseek(fh,pos,SEEK_SET); + fread(text,1,4,fh); + } + long header_size=readLong(fh); + info->format=readShort(fh); + info->ntracks=readShort(fh); + info->ticksPerCuarterNote=readShort(fh); + if (info->ticksPerCuarterNote<0) + { + fprintf(stderr,"ERROR: Ticks per cuarter note is negative !\n"); + fprintf(stderr,"Please report this error to : larrosa@kde.org\n"); + fclose(fh); + ok=-3; + return NULL; + } + if (header_size>6) fseek(fh,header_size-6,SEEK_CUR); + tracks=new MidiTrack*[info->ntracks]; + if (tracks==NULL) + { + fprintf(stderr,"ERROR: Not enough memory\n"); + fclose(fh); + ok=-4; + return NULL; + } + int i=0; + while (i<info->ntracks) + { + fread(text,1,4,fh); + if (strncmp(text,"MTrk",4)!=0) + { + fprintf(stderr,"ERROR: Not a well built midi file\n"); + fprintf(stderr,"%s",text); + fclose(fh); + ok=-5; + return NULL; + } + tracks[i]=new MidiTrack(fh,info->ticksPerCuarterNote,i); + if (tracks[i]==NULL) + { + fprintf(stderr,"ERROR: Not enough memory"); + fclose(fh); + ok=-4; + return NULL; + } + i++; + } + + fclose(fh); + + return tracks; + +} + +void parseInfoData(MidiFileInfo *info,MidiTrack **tracks,float ratioTempo) +{ + + info->ticksTotal=0; + info->millisecsTotal=0.0; + info->ticksPlayed=0; + int i; + for (i=0;i<256;i++) + { + info->patchesUsed[i]=0; + } + + int parsing=1; + int trk,minTrk; + ulong tempo=(ulong)(500000 * ratioTempo); + +#ifdef MIDFILEDEBUG + printf("Parsing 1 ...\n"); +#endif + + int pgminchannel[16]; + for (i=0;i<16;i++) + { + pgminchannel[i]=0; + } + + int j; + for (i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + tracks[i]->changeTempo(tempo); + } + double prevms=0; + double minTime=0; + double maxTime; + MidiEvent *ev=new MidiEvent; + while (parsing) + { + prevms=minTime; + trk=0; + minTrk=0; + maxTime=minTime + 2 * 60000L; + minTime=maxTime; + while (trk<info->ntracks) + { + if (tracks[trk]->absMsOfNextEvent()<minTime) + { + minTrk=trk; + minTime=tracks[minTrk]->absMsOfNextEvent(); + } + trk++; + } + if ((minTime==maxTime)) + { + parsing=0; +#ifdef MIDFILEDEBUG + printf("END of parsing\n"); +#endif + } + else + { + trk=0; + while (trk<info->ntracks) + { + tracks[trk]->currentMs(minTime); + trk++; + } + } + trk=minTrk; + tracks[trk]->readEvent(ev); + + switch (ev->command) + { + case (MIDI_NOTEON) : + if (ev->chn!=PERCUSSION_CHANNEL) + info->patchesUsed[pgminchannel[ev->chn]]++; + else + info->patchesUsed[ev->note+128]++; + break; + case (MIDI_PGM_CHANGE) : + pgminchannel[ev->chn]=(ev->patch); + break; + case (MIDI_SYSTEM_PREFIX) : + if (((ev->command|ev->chn)==META_EVENT)&&(ev->d1==ME_SET_TEMPO)) + { + tempo=(ulong)(((ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2])) * ratioTempo); + for (j=0;j<info->ntracks;j++) + { + tracks[j]->changeTempo(tempo); + } + } + break; + } + } + + delete ev; + info->millisecsTotal=prevms; + + for (i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + } + +#ifdef MIDFILEDEBUG + printf("info.ticksTotal = %ld \n",info->ticksTotal); + printf("info.ticksPlayed= %ld \n",info->ticksPlayed); + printf("info.millisecsTotal = %g \n",info->millisecsTotal); + printf("info.TicksPerCN = %d \n",info->ticksPerCuarterNote); +#endif + +} + + +void parsePatchesUsed(MidiTrack **tracks,MidiFileInfo *info,int gm) +{ + int i; + for (i=0;i<256;i++) + { + info->patchesUsed[i]=0; + } + int parsing=1; + int trk,minTrk; + ulong tempo=500000; + +#ifdef MIDFILEDEBUG + printf("Parsing for patches ...\n"); +#endif + + int j; + for (i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + } + double prevms=0; + double minTime=0; + double maxTime; + ulong tmp; + MidiEvent *ev=new MidiEvent; + int pgminchannel[16]; + for (i=0;i<16;i++) + { + pgminchannel[i]=0; + } + + while (parsing) + { + prevms=minTime; + trk=0; + minTrk=0; + maxTime=minTime + 2 * 60000L; + minTime=maxTime; + while (trk<info->ntracks) + { + if (tracks[trk]->absMsOfNextEvent()<minTime) + { + minTrk=trk; + minTime=tracks[minTrk]->absMsOfNextEvent(); + } + trk++; + } + if ((minTime==maxTime)) + { + parsing=0; +#ifdef MIDFILEDEBUG + printf("END of parsing for patches\n"); +#endif + } + else + { + trk=0; + while (trk<info->ntracks) + { + tracks[trk]->currentMs(minTime); + trk++; + } + } + trk=minTrk; + tracks[trk]->readEvent(ev); + switch (ev->command) + { + case (MIDI_NOTEON) : + if (ev->chn!=PERCUSSION_CHANNEL) + info->patchesUsed[pgminchannel[ev->chn]]++; + else + info->patchesUsed[ev->note+128]++; + break; + case (MIDI_PGM_CHANGE) : + pgminchannel[ev->chn]=(gm==1)?(ev->patch):(MT32toGM[ev->patch]); + break; + case (MIDI_SYSTEM_PREFIX) : + if (((ev->command|ev->chn)==META_EVENT)&&(ev->d1==ME_SET_TEMPO)) + { + if (tempoToMetronomeTempo(tmp=((ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2])))>=8) + { + tempo=tmp; + // printf("setTempo %ld\n",tempo); + for (j=0;j<info->ntracks;j++) + { + tracks[j]->changeTempo(tempo); + } + } + } + break; + } + } + + delete ev; + + for (i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + } + +} + +int fsearch(FILE *fh,const char *text,long *ptr) + // Search for "text" through the fh file and then returns : + // text MUST BE smaller than 256 characters + // 0 if not was found + // 1 if it was found and in ptr (if !=NULL) the position where text begins. +{ + if ((text==NULL)||(text[0]==0)) return 0; + char buf[1024]; + char tmp[256]; + long pos; + int l=strlen(text); + int i,k,r; + while (!feof(fh)) + { + pos=ftell(fh); + k=fread(buf,1,1024,fh); + i=0; + while (i<k) + { + if (buf[i]==text[0]) + { + if (k-i>=l) + r=strncmp(text,&buf[i],l); + else + { + fseek(fh,pos+i,SEEK_SET); + if (fread(tmp,1,l,fh)<(uint)l) return 0; + fseek(fh,pos+k,SEEK_SET); + r=strncmp(text,tmp,l); + } + if (r==0) + { + if (ptr!=NULL) *ptr=pos+i; + return 1; + } + } + i++; + } + } + return 0; +} |