You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
9.7 KiB
461 lines
9.7 KiB
/**************************************************************************
|
|
|
|
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 <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 <tqfile.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)
|
|
{
|
|
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 (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;
|
|
}
|