mirror of
				https://github.com/cookiengineer/audacity
				synced 2025-11-04 16:14:00 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			487 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			487 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
 * Read a Standard MIDI File.  Externally-assigned function pointers are
 | 
						|
 * called upon recognizing things in the file.  See midifile(3).
 | 
						|
 */
 | 
						|
 | 
						|
/*****************************************************************************
 | 
						|
*       Change Log
 | 
						|
*       Date    | who : Change
 | 
						|
*-----------+-----------------------------------------------------------------
 | 
						|
*  2-Mar-92 | GWL : created changelog; MIDIFILE_ERROR to satisfy compiler
 | 
						|
*****************************************************************************/
 | 
						|
 | 
						|
#include "stdio.h"
 | 
						|
#include "mfmidi.h"
 | 
						|
#include "string.h"
 | 
						|
#include "assert.h"
 | 
						|
 | 
						|
#define MIDIFILE_ERROR -1
 | 
						|
 | 
						|
/* public stuff */
 | 
						|
extern int abort_flag;
 | 
						|
 | 
						|
 | 
						|
void Midifile_reader::midifile()
 | 
						|
{
 | 
						|
    int ntrks;
 | 
						|
    midifile_error = 0;
 | 
						|
 | 
						|
    ntrks = readheader();
 | 
						|
    if (midifile_error) return;
 | 
						|
    if (ntrks <= 0) {
 | 
						|
        mferror("No tracks!");
 | 
						|
        /* no need to return since midifile_error is set */
 | 
						|
    }
 | 
						|
    while (ntrks-- > 0 && !midifile_error) readtrack();
 | 
						|
}
 | 
						|
 | 
						|
int Midifile_reader::readmt(char *s, int skip)
 | 
						|
    /* read through the "MThd" or "MTrk" header string */
 | 
						|
    /* if skip == 1, we attempt to skip initial garbage. */
 | 
						|
{
 | 
						|
    assert(strlen(s) == 4); // must be "MThd" or "MTrk"
 | 
						|
    int nread = 0;
 | 
						|
    char b[4];
 | 
						|
    char buff[32];
 | 
						|
    int c;
 | 
						|
    char *errmsg = "expecting ";
 | 
						|
 | 
						|
    retry:
 | 
						|
    while ( nread<4 ) {
 | 
						|
        c = Mf_getc();
 | 
						|
        if ( c == EOF ) {
 | 
						|
            errmsg = "EOF while expecting ";
 | 
						|
            goto err;
 | 
						|
        }
 | 
						|
        b[nread++] = c;
 | 
						|
    }
 | 
						|
    /* See if we found the 4 characters we're looking for */
 | 
						|
    if ( s[0]==b[0] && s[1]==b[1] && s[2]==b[2] && s[3]==b[3] )
 | 
						|
        return(0);
 | 
						|
    if ( skip ) {
 | 
						|
        /* If we are supposed to skip initial garbage, */
 | 
						|
        /* try again with the next character. */
 | 
						|
        b[0]=b[1];
 | 
						|
        b[1]=b[2];
 | 
						|
        b[2]=b[3];
 | 
						|
        nread = 3;
 | 
						|
        goto retry;
 | 
						|
    }
 | 
						|
    err:
 | 
						|
#pragma warning(disable: 4996) // strcpy is safe since strings have known lengths
 | 
						|
    (void) strcpy(buff,errmsg);
 | 
						|
    (void) strcat(buff,s);
 | 
						|
#pragma warning(default: 4996) // turn it back on
 | 
						|
    mferror(buff);
 | 
						|
    return(0);
 | 
						|
}
 | 
						|
 | 
						|
int Midifile_reader::egetc()
 | 
						|
    /* read a single character and abort on EOF */
 | 
						|
{
 | 
						|
    int c = Mf_getc();
 | 
						|
 | 
						|
    if ( c == EOF ) {
 | 
						|
        mferror("premature EOF");
 | 
						|
        return EOF;
 | 
						|
    }
 | 
						|
    Mf_toberead--;
 | 
						|
    return(c);
 | 
						|
}
 | 
						|
 | 
						|
int Midifile_reader::readheader()
 | 
						|
    /* read a header chunk */
 | 
						|
{
 | 
						|
    int format, ntrks, division;
 | 
						|
 | 
						|
    if ( readmt("MThd",Mf_skipinit) == EOF )
 | 
						|
        return(0);
 | 
						|
 | 
						|
    Mf_toberead = read32bit();
 | 
						|
    if (midifile_error) return MIDIFILE_ERROR;
 | 
						|
    format = read16bit();
 | 
						|
    if (midifile_error) return MIDIFILE_ERROR;
 | 
						|
    ntrks = read16bit();
 | 
						|
    if (midifile_error) return MIDIFILE_ERROR;
 | 
						|
    division = read16bit();
 | 
						|
    if (midifile_error) return MIDIFILE_ERROR;
 | 
						|
 | 
						|
    Mf_header(format,ntrks,division);
 | 
						|
 | 
						|
    /* flush any extra stuff, in case the length of header is not 6 */
 | 
						|
    while ( Mf_toberead > 0 && !midifile_error)
 | 
						|
        (void) egetc();
 | 
						|
    return(ntrks);
 | 
						|
}
 | 
						|
 | 
						|
void Midifile_reader::readtrack()
 | 
						|
    /* read a track chunk */
 | 
						|
{
 | 
						|
    /* This array is indexed by the high half of a status byte.  It's */
 | 
						|
    /* value is either the number of bytes needed (1 or 2) for a channel */
 | 
						|
    /* message, or 0 (meaning it's not  a channel message). */
 | 
						|
    static int chantype[] = {
 | 
						|
        0, 0, 0, 0, 0, 0, 0, 0,       /* 0x00 through 0x70 */
 | 
						|
        2, 2, 2, 2, 1, 1, 2, 0        /* 0x80 through 0xf0 */
 | 
						|
    };
 | 
						|
    long lookfor, lng;
 | 
						|
    int c, c1, type;
 | 
						|
    int sysexcontinue = 0;  /* 1 if last message was an unfinished sysex */
 | 
						|
    int running = 0;        /* 1 when running status used */
 | 
						|
    int status = 0;         /* (possibly running) status byte */
 | 
						|
    int needed;
 | 
						|
 | 
						|
    if ( readmt("MTrk",0) == EOF )
 | 
						|
        return;
 | 
						|
 | 
						|
    Mf_toberead = read32bit();
 | 
						|
 | 
						|
    if (midifile_error) return;
 | 
						|
 | 
						|
    Mf_currtime = 0L;
 | 
						|
 | 
						|
    Mf_starttrack();
 | 
						|
 | 
						|
    while ( Mf_toberead > 0 ) {
 | 
						|
 | 
						|
        Mf_currtime += readvarinum();   /* delta time */
 | 
						|
        if (midifile_error) return;
 | 
						|
 | 
						|
        c = egetc();
 | 
						|
       if (midifile_error) return;
 | 
						|
 | 
						|
        if ( sysexcontinue && c != 0xf7 ) {
 | 
						|
            mferror("didn't find expected continuation of a sysex");
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        if ( (c & 0x80) == 0 ) {        /* running status? */
 | 
						|
            if ( status == 0 ) {
 | 
						|
                mferror("unexpected running status");
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            running = 1;
 | 
						|
       } else {
 | 
						|
            status = c;
 | 
						|
            running = 0;
 | 
						|
        }
 | 
						|
 | 
						|
        needed = chantype[ (status>>4) & 0xf ];
 | 
						|
 | 
						|
        if ( needed ) {        /* ie. is it a channel message? */
 | 
						|
 | 
						|
            if ( running )
 | 
						|
                c1 = c;
 | 
						|
            else {
 | 
						|
                c1 = egetc();
 | 
						|
                if (midifile_error) return;
 | 
						|
            }
 | 
						|
            chanmessage( status, c1, (needed>1) ? egetc() : 0 );
 | 
						|
            if (midifile_error) return;
 | 
						|
            continue;;
 | 
						|
        }
 | 
						|
 | 
						|
        switch ( c ) {
 | 
						|
 | 
						|
        case 0xff:                  /* meta event */
 | 
						|
 | 
						|
            type = egetc();
 | 
						|
            if (midifile_error) return;
 | 
						|
            /* watch out - Don't combine the next 2 statements */
 | 
						|
            lng = readvarinum();
 | 
						|
            if (midifile_error) return;
 | 
						|
            lookfor = Mf_toberead - lng;
 | 
						|
            msginit();
 | 
						|
 | 
						|
            while ( Mf_toberead > lookfor ) {
 | 
						|
                unsigned char c = egetc();
 | 
						|
                if (midifile_error) return;
 | 
						|
                msgadd(c);
 | 
						|
            }
 | 
						|
            metaevent(type);
 | 
						|
            break;
 | 
						|
 | 
						|
        case 0xf0:            /* start of system exclusive */
 | 
						|
 | 
						|
            /* watch out - Don't combine the next 2 statements */
 | 
						|
            lng = readvarinum();
 | 
						|
            if (midifile_error) return;
 | 
						|
            lookfor = Mf_toberead - lng;
 | 
						|
            msginit();
 | 
						|
            msgadd(0xf0);
 | 
						|
 | 
						|
            while ( Mf_toberead > lookfor ) {
 | 
						|
                c = egetc();
 | 
						|
                if (midifile_error) return;
 | 
						|
                msgadd(c);
 | 
						|
            }
 | 
						|
            if ( c==0xf7 || Mf_nomerge==0 )
 | 
						|
                sysex();
 | 
						|
            else
 | 
						|
                sysexcontinue = 1;  /* merge into next msg */
 | 
						|
            break;
 | 
						|
 | 
						|
        case 0xf7:     /* sysex continuation or arbitrary stuff */
 | 
						|
 | 
						|
            /* watch out - Don't combine the next 2 statements */
 | 
						|
            lng = readvarinum();
 | 
						|
            if (midifile_error) return;
 | 
						|
            lookfor = Mf_toberead - lng;
 | 
						|
 | 
						|
            if ( ! sysexcontinue )
 | 
						|
                msginit();
 | 
						|
 | 
						|
            while ( Mf_toberead > lookfor ) {
 | 
						|
                c = egetc();
 | 
						|
                if (midifile_error) return;
 | 
						|
                msgadd(c);
 | 
						|
            }
 | 
						|
            if ( ! sysexcontinue ) {
 | 
						|
                    Mf_arbitrary(msgleng(), msg());
 | 
						|
            }
 | 
						|
            else if ( c == 0xf7 ) {
 | 
						|
                sysex();
 | 
						|
                sysexcontinue = 0;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        default:
 | 
						|
 | 
						|
            badbyte(c);
 | 
						|
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
        Mf_endtrack();
 | 
						|
    return;
 | 
						|
}
 | 
						|
 | 
						|
void Midifile_reader::badbyte(int c)
 | 
						|
{
 | 
						|
    char buff[32];
 | 
						|
#pragma warning(disable: 4996) // safe in this case
 | 
						|
    (void) sprintf(buff,"unexpected byte: 0x%02x",c);
 | 
						|
#pragma warning(default: 4996)
 | 
						|
    mferror(buff);
 | 
						|
}
 | 
						|
 | 
						|
void Midifile_reader::metaevent(int type)
 | 
						|
{
 | 
						|
    int leng = msgleng();
 | 
						|
    // made this unsigned to avoid sign extend
 | 
						|
    unsigned char *m = msg();
 | 
						|
 | 
						|
    switch ( type ) {
 | 
						|
    case 0x00:
 | 
						|
        Mf_seqnum(to16bit(m[0],m[1]));
 | 
						|
        break;
 | 
						|
    case 0x01:     /* Text event */
 | 
						|
    case 0x02:     /* Copyright notice */
 | 
						|
    case 0x03:     /* Sequence/Track name */
 | 
						|
    case 0x04:     /* Instrument name */
 | 
						|
    case 0x05:     /* Lyric */
 | 
						|
    case 0x06:     /* Marker */
 | 
						|
    case 0x07:     /* Cue point */
 | 
						|
    case 0x08:
 | 
						|
    case 0x09:
 | 
						|
    case 0x0a:
 | 
						|
    case 0x0b:
 | 
						|
    case 0x0c:
 | 
						|
    case 0x0d:
 | 
						|
    case 0x0e:
 | 
						|
    case 0x0f:
 | 
						|
        /* These are all text events */
 | 
						|
        Mf_text(type,leng,m);
 | 
						|
        break;
 | 
						|
    case 0x20:
 | 
						|
        Mf_chanprefix(m[0]);
 | 
						|
        break;
 | 
						|
    case 0x21:
 | 
						|
        Mf_portprefix(m[0]);
 | 
						|
        break;
 | 
						|
    case 0x2f:     /* End of Track */
 | 
						|
        Mf_eot();
 | 
						|
        break;
 | 
						|
    case 0x51:     /* Set tempo */
 | 
						|
        Mf_tempo(to32bit(0,m[0],m[1],m[2]));
 | 
						|
        break;
 | 
						|
    case 0x54:
 | 
						|
        Mf_smpte(m[0],m[1],m[2],m[3],m[4]);
 | 
						|
        break;
 | 
						|
    case 0x58:
 | 
						|
        Mf_timesig(m[0],m[1],m[2],m[3]);
 | 
						|
        break;
 | 
						|
    case 0x59:
 | 
						|
        Mf_keysig(m[0],m[1]);
 | 
						|
        break;
 | 
						|
    case 0x7f:
 | 
						|
        Mf_sqspecific(leng,m);
 | 
						|
        break;
 | 
						|
    default:
 | 
						|
        Mf_metamisc(type,leng,m);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void Midifile_reader::sysex()
 | 
						|
{
 | 
						|
    Mf_sysex(msgleng(), msg());
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void Midifile_reader::chanmessage(int status, int c1, int c2)
 | 
						|
{
 | 
						|
    int chan = status & 0xf;
 | 
						|
 | 
						|
    switch ( status & 0xf0 ) {
 | 
						|
    case NOTEOFF:
 | 
						|
        Mf_off(chan,c1,c2);
 | 
						|
        break;
 | 
						|
    case NOTEON:
 | 
						|
        Mf_on(chan,c1,c2);
 | 
						|
        break;
 | 
						|
    case PRESSURE:
 | 
						|
        Mf_pressure(chan,c1,c2);
 | 
						|
        break;
 | 
						|
    case CONTROLLER:
 | 
						|
        Mf_controller(chan,c1,c2);
 | 
						|
        break;
 | 
						|
    case PITCHBEND:
 | 
						|
        Mf_pitchbend(chan,c1,c2);
 | 
						|
        break;
 | 
						|
    case PROGRAM:
 | 
						|
        Mf_program(chan,c1);
 | 
						|
        break;
 | 
						|
    case CHANPRESSURE:
 | 
						|
        Mf_chanpressure(chan,c1);
 | 
						|
        break;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* readvarinum - read a varying-length number, and return the */
 | 
						|
/* number of characters it took. */
 | 
						|
 | 
						|
long Midifile_reader::readvarinum()
 | 
						|
{
 | 
						|
    long value;
 | 
						|
    int c;
 | 
						|
 | 
						|
    c = egetc();
 | 
						|
    if (midifile_error) return 0;
 | 
						|
 | 
						|
    value = (long) c;
 | 
						|
    if ( c & 0x80 ) {
 | 
						|
        value &= 0x7f;
 | 
						|
        do {
 | 
						|
            c = egetc();
 | 
						|
            if (midifile_error) return 0;
 | 
						|
            value = (value << 7) + (c & 0x7f);
 | 
						|
        } while (c & 0x80);
 | 
						|
    }
 | 
						|
    return (value);
 | 
						|
}
 | 
						|
 | 
						|
long Midifile_reader::to32bit(int c1, int c2, int c3, int c4)
 | 
						|
{
 | 
						|
    long value = 0L;
 | 
						|
 | 
						|
    value = (c1 & 0xff);
 | 
						|
    value = (value<<8) + (c2 & 0xff);
 | 
						|
    value = (value<<8) + (c3 & 0xff);
 | 
						|
    value = (value<<8) + (c4 & 0xff);
 | 
						|
    return (value);
 | 
						|
}
 | 
						|
 | 
						|
int Midifile_reader::to16bit(int c1, int c2)
 | 
						|
{
 | 
						|
    return ((c1 & 0xff ) << 8) + (c2 & 0xff);
 | 
						|
}
 | 
						|
 | 
						|
long Midifile_reader::read32bit()
 | 
						|
{
 | 
						|
    int c1, c2, c3, c4;
 | 
						|
 | 
						|
    c1 = egetc(); if (midifile_error) return 0;
 | 
						|
    c2 = egetc(); if (midifile_error) return 0;
 | 
						|
    c3 = egetc(); if (midifile_error) return 0;
 | 
						|
    c4 = egetc(); if (midifile_error) return 0;
 | 
						|
    return to32bit(c1,c2,c3,c4);
 | 
						|
}
 | 
						|
 | 
						|
int Midifile_reader::read16bit()
 | 
						|
{
 | 
						|
    int c1, c2;
 | 
						|
    c1 = egetc(); if (midifile_error) return 0;
 | 
						|
    c2 = egetc(); if (midifile_error) return 0;
 | 
						|
    return to16bit(c1,c2);
 | 
						|
}
 | 
						|
 | 
						|
void Midifile_reader::mferror(char *s)
 | 
						|
{
 | 
						|
    Mf_error(s);
 | 
						|
    midifile_error = 1;
 | 
						|
}
 | 
						|
 | 
						|
/* The code below allows collection of a system exclusive message of */
 | 
						|
/* arbitrary length.  The Msgbuff is expanded as necessary.  The only */
 | 
						|
/* visible data/routines are msginit(), msgadd(), msg(), msgleng(). */
 | 
						|
 | 
						|
#define MSGINCREMENT 128
 | 
						|
 | 
						|
Midifile_reader::Midifile_reader()
 | 
						|
{
 | 
						|
    Mf_nomerge = 0;
 | 
						|
    Mf_currtime = 0L;
 | 
						|
    Mf_skipinit = 0;
 | 
						|
    Mf_toberead = 0;
 | 
						|
 | 
						|
    Msgbuff = 0;      /* message buffer */
 | 
						|
    Msgsize = 0;        /* Size of currently allocated Msg */
 | 
						|
    Msgindex = 0;       /* index of next available location in Msg */
 | 
						|
}
 | 
						|
 | 
						|
void Midifile_reader::finalize()
 | 
						|
{
 | 
						|
    if (Msgbuff) Mf_free(Msgbuff, Msgsize);
 | 
						|
    Msgbuff = NULL;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void Midifile_reader::msginit()
 | 
						|
{
 | 
						|
    Msgindex = 0;
 | 
						|
}
 | 
						|
 | 
						|
unsigned char *Midifile_reader::msg()
 | 
						|
{
 | 
						|
    return(Msgbuff);
 | 
						|
}
 | 
						|
 | 
						|
int Midifile_reader::msgleng()
 | 
						|
{
 | 
						|
    return(Msgindex);
 | 
						|
}
 | 
						|
 | 
						|
void Midifile_reader::msgadd(int c)
 | 
						|
{
 | 
						|
    /* If necessary, allocate larger message buffer. */
 | 
						|
    if ( Msgindex >= Msgsize )
 | 
						|
        msgenlarge();
 | 
						|
    Msgbuff[Msgindex++] = c;
 | 
						|
}
 | 
						|
 | 
						|
void Midifile_reader::msgenlarge()
 | 
						|
{
 | 
						|
    unsigned char *newmess;
 | 
						|
    unsigned char *oldmess = Msgbuff;
 | 
						|
    int oldleng = Msgsize;
 | 
						|
 | 
						|
    Msgsize += MSGINCREMENT;
 | 
						|
    newmess = (unsigned char *) Mf_malloc((sizeof(unsigned char) * Msgsize) );
 | 
						|
 | 
						|
    /* copy old message into larger new one */
 | 
						|
    if ( oldmess != 0 ) {
 | 
						|
        memcpy(newmess, oldmess, oldleng);
 | 
						|
        Mf_free(oldmess, oldleng);
 | 
						|
    }
 | 
						|
    Msgbuff = newmess;
 | 
						|
}
 |