/*
 * 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"

#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. */
{
    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:
    (void) strcpy(buff,errmsg);
    (void) strcat(buff,s);
    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 ) {
                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];

    (void) sprintf(buff,"unexpected byte: 0x%02x",c);
    mferror(buff);
}

void Midifile_reader::metaevent(int type)
{
    int leng = msgleng();
    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;
}

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()
{
    char *newmess;
    char *oldmess = Msgbuff;
    int oldleng = Msgsize;

    Msgsize += MSGINCREMENT;
    newmess = (char *) Mf_malloc((sizeof(char) * Msgsize) );

    /* copy old message into larger new one */
    if ( oldmess != 0 ) {
        register char *p = newmess;
        register char *q = oldmess;
        register char *endq = &oldmess[oldleng];

        for ( ; q!=endq ; p++,q++ )
            *p = *q;
        Mf_free(oldmess, oldleng);
    }
    Msgbuff = newmess;
}
