/************************************************************************** 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/libtdemid.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 ***************************************************************************/ #include "midfile.h" #include #include #include #include #include "sndcard.h" #include "midispec.h" #include "mt32togm.h" #include "sys/stat.h" #include #include #include 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) { TQString cmd("gzip -dc " + TDEProcess::quote(gzname)); FILE *infile = popen( TQFile::encodeName(cmd).data(), "r"); if (infile==NULL) { fprintf(stderr,"ERROR : popen failed : %s\n",TQFile::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 (intracks) { 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;intracks;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 (trkntracks) { if (tracks[trk]->absMsOfNextEvent()absMsOfNextEvent(); } trk++; } if ((minTime==maxTime)) { parsing=0; #ifdef MIDFILEDEBUG printf("END of parsing\n"); #endif } else { trk=0; while (trkntracks) { 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;jntracks;j++) { tracks[j]->changeTempo(tempo); } } break; } } delete ev; info->millisecsTotal=prevms; for (i=0;intracks;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;intracks;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 (trkntracks) { if (tracks[trk]->absMsOfNextEvent()absMsOfNextEvent(); } trk++; } if ((minTime==maxTime)) { parsing=0; #ifdef MIDFILEDEBUG printf("END of parsing for patches\n"); #endif } else { trk=0; while (trkntracks) { 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;jntracks;j++) { tracks[j]->changeTempo(tempo); } } } break; } } delete ev; for (i=0;intracks;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=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; }