/*
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version. This program 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 General Public License for more
details. You should have received a copy of the GNU General Public License
along with this program; if not, write to the...

Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston,
MA 02111-1307 USA
*/

char *usage = "\n\
midicomp v0.0.1 20031129 markc@renta.net                                    \n\
                                                                            \n\
http://alsa.opensrc.org/midicomp/                                           \n\
                                                                            \n\
Command line argument usage:                                                \n\
                                                                            \n\
    -d  --debug     send any debug output to stderr                         \n\
    -v  --verbose   output in columns with notes on                         \n\
    -c  --compile   compile ascii input into SMF                            \n\
    -n  --note      note on/off value as note|octave                        \n\
    -t  --time      use absolute time instead of ticks                      \n\
    -fN --fold=N    fold sysex data at N columns                            \n\
                                                                            \n\
To translate a SMF file to plain ascii format:                              \n\
                                                                            \n\
    midicomp some.mid                   # to view as plain text             \n\
    midicomp some.mid > some.asc        # to create a text version          \n\
                                                                            \n\
To translate a plain ascii formatted file to SMF:                           \n\
                                                                            \n\
    midicomp -c some.asc some.mid       # input and output filenames        \n\
    midicomp -c some.mid < some.asc     # input from stdin with one arg     \n\
                                                                            \n\
    midicomp some.mid | somefilter | midicomp -c some2.mid                  \n";

#include <malloc.h>
#include <setjmp.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include "midicomp.h"

int main(int argc,char **argv) {

    FILE *efopen();
    Mf_nomerge = 1;
    opterr = 0;
    int compile = 0;
    int c;

    struct option long_options[] = {
          {"debug",   no_argument,       0, 'd'},
          {"verbose", no_argument,       0, 'v'},
          {"compile", no_argument,       0, 'c'},
          {"note",    no_argument,       0, 'n'},
          {"time",    no_argument,       0, 't'},
          {"fold",    required_argument, 0, 'f'},
          {0, 0, 0, 0}
     };
    int option_index = 0;

	while ((c = getopt_long(argc,argv,"dvcntf:",long_options,&option_index)) != -1) {
        switch (c) {
        case 0:
            if (long_options[option_index].flag != 0)
                break;
        case 'd':
            dbg++;
            break;
        case 'f':
            fold = atoi(optarg);
            fprintf(stderr,"fold=%d\n",fold);
            break;
        case 'm':
            Mf_nomerge = 0;
            break;
        case 'n':
            notes++;
            break;
        case 't':
            times++;
            break;
         case 'c':
            compile++;
            break;
         case 'v':
            verbose++;
            notes++;
            break;
         case 'h':
         default:
            fprintf(stderr,"%s\n",usage);
            return 1;
        }
    }
if (dbg) fprintf(stderr,"main()\n");

    TrkNr = 0;
    Measure = 4;
    Beat = 96;
    Clicks = 96;
    M0 = 0;
    T0 = 0;

    if (compile) {
        char *infile;
        char *outfile;

        if (optind < argc) {
            yyin = efopen (argv[optind], "r");
            infile = argv[optind];
        } else {
            yyin = stdin;
            infile = "stdin";
        }
        if (optind+1 < argc ) {
            F = efopen(argv[optind+1],"wb");
            outfile = argv[optind+1];
        } else {
#ifdef SETMODE
            setmode (fileno(stdout), O_BINARY);
            F = stdout;
#else
            F = fdopen (fileno(stdout), "wb");
#endif
            outfile = "stdout";
        }
if (dbg) fprintf(stderr,"Compiling %s to %s\n",infile,outfile);

        Mf_putc = fileputc;
        Mf_wtrack = mywritetrack;
        translate();
        fclose(F);
        fclose(yyin);
    } else {
        if (verbose) {
            Onmsg   = "On      ch=%-2d  note=%-3s  vol=%-3d\n";
            Offmsg  = "Off     ch=%-2d  note=%-3s  vol=%-3d\n";
            PoPrmsg = "PolyPr  ch=%-2d  note=%-3s  val=%-3d\n";
            Parmsg  = "Param   ch=%-2d  con=%-3d   val=%-3d\n";
            Pbmsg   = "Pb      ch=%-2d  val=%-3d\n";
            PrChmsg = "ProgCh  ch=%-2d  prog=%-3d\n";
            ChPrmsg = "ChanPr  ch=%-2d  val=%-3d\n";
        }
        if (optind < argc)
            F = efopen(argv[optind],"rb");
        else
            F = fdopen(fileno(stdin),"rb");

        initfuncs();
        Mf_getc = filegetc;
        mfread();
        if (ferror(F)) error ("Output file error");
        fclose(F);
    }
}

mfread() {

    if (Mf_getc == NULLFUNC)
        mferror("mfread() called without setting Mf_getc");

    readheader();
    while(readtrack()) ;
}

static readmt(char *s) {

    int n = 0;
    char *p = s;
    int c;

    while ( n++<4 && (c=(*Mf_getc)()) != EOF ) {
        if ( c != *p++ ) {
            char buff[32];
            (void) strcpy(buff,"expecting ");
            (void) strcat(buff,s);
            mferror(buff);
        }
    }
    return(c);
}

static egetc() {

    int c = (*Mf_getc)();

    if ( c == EOF )
        mferror("premature EOF");
    Mf_toberead--;
    return(c);
}

static void readheader() {

    int format, ntrks, division;

    if (readmt("MThd") == EOF) return;
    Mf_toberead = read32bit();
    format      = read16bit();
    ntrks       = read16bit();
    division    = read16bit();
    if (Mf_header) (*Mf_header)(format,ntrks,division);
    while(Mf_toberead > 0) (void) egetc();
}

static readtrack() {

    long lookfor;
    int c, c1, type;
    int sysexcontinue = 0;
    int running = 0;
    int status = 0;
    int needed;
    static int chantype[] = {
        0, 0, 0, 0, 0, 0, 0, 0,
        2, 2, 2, 2, 1, 1, 2, 0
    };

    if (readmt("MTrk") == EOF) return(0);
    Mf_toberead = read32bit();
    Mf_currtime = 0;
    if (Mf_starttrack) (*Mf_starttrack)();

    while (Mf_toberead > 0) {
        Mf_currtime += readvarinum();
        c = egetc();
        if (sysexcontinue && c != 0xf7)
            mferror("didn't find expected continuation of a sysex");
        if ((c & 0x80) == 0) {
            if (status == 0) mferror("unexpected running status");
            running = 1;
            c1 = c;
            c = status;
        } else if (c < 0xf0) {
            status = c;
            running = 0;
        }
        needed = chantype[ (c>>4) & 0xf ];
        if (needed) {
            if (!running) c1 = egetc();
            chanmessage(status,c1,(needed>1) ? egetc() : 0 );
            continue;
        }

        switch(c) {
         case 0xff:
            type = egetc();
            lookfor = Mf_toberead - readvarinum();
            msginit();
            while(Mf_toberead >= lookfor) msgadd(egetc());
            metaevent(type);
            break;
         case 0xf0:
            lookfor = Mf_toberead - readvarinum();
            msginit();
            msgadd(0xf0);
            while(Mf_toberead > lookfor) msgadd(c=egetc());
            if (c == 0xf7 || Mf_nomerge == 0)
                sysex();
            else
                sysexcontinue = 1;
            break;
         case 0xf7:
            lookfor = Mf_toberead - readvarinum();
            if (! sysexcontinue) msginit();
            while (Mf_toberead > lookfor)  msgadd(c=egetc());
            if (! sysexcontinue) {
                if (Mf_arbitrary) (*Mf_arbitrary)(msgleng(),msg());
            } else if (c == 0xf7) {
                sysex();
                sysexcontinue = 0;
            }
            break;
         default:
            badbyte(c);
            break;
        }
    }
    if ( Mf_endtrack ) (*Mf_endtrack)();
    return(1);
}

static badbyte(int c) {

    char buff[32];

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

static metaevent(type) {

    int leng = msgleng();
    char *m = msg();

    switch  (type) {
    case 0x00:
        if (Mf_seqnum) (*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:
        if (Mf_text) (*Mf_text)(type,leng,m);
        break;
    case 0x2f:
        if (Mf_eot) (*Mf_eot)();
        break;
    case 0x51:
        if (Mf_tempo) (*Mf_tempo)(to32bit(0,m[0],m[1],m[2]));
        break;
    case 0x54:
        if (Mf_smpte)
            (*Mf_smpte)(m[0],m[1],m[2],m[3],m[4]);
        break;
    case 0x58:
        if (Mf_timesig)
            (*Mf_timesig)(m[0],m[1],m[2],m[3]);
        break;
    case 0x59:
        if (Mf_keysig) (*Mf_keysig)(m[0],m[1]);
        break;
    case 0x7f:
        if (Mf_sqspecific) (*Mf_sqspecific)(leng,m);
        break;
    default:
        if (Mf_metamisc) (*Mf_metamisc)(type,leng,m);
    }
}

static sysex() {

    if (Mf_sysex) (*Mf_sysex)(msgleng(),msg());
}

static chanmessage(int status,int c1,int c2) {

    int chan = status & 0xf;

    switch(status & 0xf0) {
     case 0x80: if (Mf_off) (*Mf_off)(chan,c1,c2); break;
     case 0x90: if (Mf_on) (*Mf_on)(chan,c1,c2); break;
     case 0xa0: if (Mf_pressure) (*Mf_pressure)(chan,c1,c2); break;
     case 0xb0: if (Mf_parameter) (*Mf_parameter)(chan,c1,c2); break;
     case 0xe0: if (Mf_pitchbend) (*Mf_pitchbend)(chan,c1,c2); break;
     case 0xc0: if (Mf_program) (*Mf_program)(chan,c1); break;
     case 0xd0: if (Mf_chanpressure) (*Mf_chanpressure)(chan,c1); break;
    }
}

static long readvarinum() {

    long value;
    int c;

    c = egetc();
    value = c;
    if (c & 0x80) {
        value &= 0x7f;
        do {
            c = egetc();
            value = (value << 7) + (c & 0x7f);
        } while (c & 0x80);
    }
    return (value);
}

static long to32bit(c1,c2,c3,c4) {

    long value = 0L;

    value = (c1 & 0xff);
    value = (value<<8) + (c2 & 0xff);
    value = (value<<8) + (c3 & 0xff);
    value = (value<<8) + (c4 & 0xff);
    return (value);
}

static to16bit(int c1,int c2) {

    return ((c1 & 0xff ) << 8) + (c2 & 0xff);
}

static long read32bit() {

    int c1, c2, c3, c4;

    c1 = egetc();
    c2 = egetc();
    c3 = egetc();
    c4 = egetc();
    return to32bit(c1,c2,c3,c4);
}

static read16bit() {

    int c1, c2;
    c1 = egetc();
    c2 = egetc();
    return to16bit(c1,c2);
}

mferror(char *s) {

    if (Mf_error) (*Mf_error)(s);
    exit(1);
}

static msginit() {

    Msgindex = 0;
}

static char * msg() {

    return(Msgbuff);
}

static msgleng() {

    return(Msgindex);
}

static msgadd(int c) {

    if (Msgindex >= Msgsize) biggermsg();
    Msgbuff[Msgindex++] = c;
}

static biggermsg() {

    char *newmess;
    char *oldmess = Msgbuff;
    int oldleng = Msgsize;

    Msgsize += MSGINCREMENT;
    newmess = (char *) malloc((unsigned)(sizeof(char)*Msgsize));
    if (newmess == NULL) mferror("malloc error!");
    if (oldmess != NULL) {
        register char *p = newmess;
        register char *q = oldmess;
        register char *endq = &oldmess[oldleng];
        for( ; q!=endq ; p++,q++ ) *p = *q;
        free(oldmess);
    }
    Msgbuff = newmess;
}

void mfwrite(int format,int ntracks,int division,FILE *fp) {

    int i;
    void mf_w_track_chunk(),mf_w_header_chunk();

    if (Mf_putc == NULLFUNC)
        mferror("mfmf_write() called without setting Mf_putc");
    if (Mf_wtrack == NULLFUNC)
        mferror("mfmf_write() called without setting Mf_mf_writetrack");
    mf_w_header_chunk(format,ntracks,division);
    if (format == 1 && ( Mf_wtempotrack )) {
        mf_w_track_chunk(-1,fp,Mf_wtempotrack);
        ntracks--;
    }
    for(i = 0; i < ntracks; i++)
        mf_w_track_chunk(i,fp,Mf_wtrack);
}

void mf_w_track_chunk(which_track,fp,wtrack)
int which_track;
FILE *fp;
int (*wtrack)();
{
    unsigned long trkhdr,trklength;
    long offset, place_marker;
    void write16bit(),write32bit();
    
    trkhdr = MTrk;
    trklength = 0;

    offset = ftell(fp);
    write32bit(trkhdr);
    write32bit(trklength);

    Mf_numbyteswritten = 0L;
    laststat = 0;
    (*wtrack)(which_track);

    if (laststat != meta_event || lastmeta != end_of_track) {
        eputc(0);
        eputc(meta_event);
        eputc(end_of_track);
        eputc(0);
    }

    laststat = 0;
    place_marker = ftell(fp);
    if (fseek(fp,offset,0) < 0)
        mferror("error seeking during final stage of write");
    trklength = Mf_numbyteswritten;
    write32bit(trkhdr);
    write32bit(trklength);
    fseek(fp,place_marker,0);
}

void mf_w_header_chunk(int format,int ntracks,int division) {

    unsigned long ident,length;
    void write16bit(),write32bit();
    
    ident = MThd;
    length = 6;
    write32bit(ident);
    write32bit(length);
    write16bit(format);
    write16bit(ntracks);
    write16bit(division);
}

int mf_w_midi_event(unsigned long delta_time,unsigned int type,
unsigned int chan,unsigned char *data,unsigned long size) {

    int i;
    unsigned char c;

    WriteVarLen(delta_time);
    c = type | chan;

    if(chan > 15)
        perror("error: MIDI channel greater than 16\n");
    if (!Mf_RunStat || laststat != c)
        eputc(c);
    laststat = c;
    for(i = 0; i < size; i++)
    eputc(data[i]);

    return(size);
}

int mf_w_meta_event(unsigned long delta_time,
unsigned int type,unsigned char *data,unsigned long size) {

    int i;

    WriteVarLen(delta_time);
    eputc(meta_event);
    laststat = meta_event;
    eputc(type);
    lastmeta = type;
    WriteVarLen(size);
    for(i = 0; i < size; i++) {
        if (eputc(data[i]) != data[i]) return(-1);
    }
    return(size);
}

int mf_w_sysex_event(unsigned long delta_time,
unsigned char *data,unsigned long size) {

    int i;

    WriteVarLen(delta_time);
    eputc(*data);
    laststat = 0;
    WriteVarLen(size-1);
    for(i = 1; i < size; i++) {
        if(eputc(data[i]) != data[i]) return(-1);
    }
    return(size);
}

void mf_w_tempo(unsigned long delta_time,unsigned long tempo) {

    WriteVarLen(delta_time);
    eputc(meta_event);
    laststat = meta_event;
    eputc(set_tempo);
    eputc(3);
    eputc((unsigned)(0xff & (tempo >> 16)));
    eputc((unsigned)(0xff & (tempo >> 8)));
    eputc((unsigned)(0xff & tempo));
}

unsigned long mf_sec2ticks(float secs,int division,unsigned int tempo) {

     return (long)(((secs * 1000.0) / 4.0 * division) / tempo);
}

void WriteVarLen(unsigned long value) {

    unsigned long buffer;

    buffer = value & 0x7f;
    while((value >>= 7) > 0) {
        buffer <<= 8;
        buffer |= 0x80;
        buffer += (value & 0x7f);
    }
    while(1) {
        eputc((unsigned)(buffer & 0xff));
        if (buffer & 0x80)
            buffer >>= 8;
        else
            return;
    }
}

float mf_ticks2sec(int ticks,unsigned int division,unsigned long tempo) {

    float smpte_format,smpte_resolution;

    if (division > 0) {
        return((float)(((float) (ticks)*(float)(tempo))/
		    ((float)(division)*1000000.0)));
    } else {
       smpte_format = upperbyte(division);
       smpte_resolution = lowerbyte(division);
       return(float)((float) ticks/(smpte_format*smpte_resolution*1000000.0));
    }
}

void write32bit(unsigned long data) {

    eputc((unsigned)((data >> 24) & 0xff));
    eputc((unsigned)((data >> 16) & 0xff));
    eputc((unsigned)((data >> 8 ) & 0xff));
    eputc((unsigned)(data & 0xff));
}

void write16bit(int data) {

    eputc((unsigned)((data & 0xff00) >> 8));
    eputc((unsigned)(data & 0xff));
}

int eputc(unsigned char c) {

    int return_val;
    
    if ((Mf_putc) == NULLFUNC) {
        mferror("Mf_putc undefined");
        return(-1);
    }
    
    return_val = (*Mf_putc)(c);

    if (return_val == EOF) mferror("error writing");
    Mf_numbyteswritten++;
    return(return_val);
}

char *mknote(int pitch) {

    static char * Notes [] =
        {"c","c#","d","d#","e","f","f#","g","g#","a","a#","b"};
    static char buf[5];
    if ( notes )
        sprintf (buf,"%s%d",Notes[pitch % 12], pitch/12);
    else
        sprintf (buf,"%d",pitch);
    return buf;
}

myheader(int format,int ntrks,int division) {

    if (division & 0x8000) {
        times = 0;
        printf("MFile %d %d %d %d\n",
            format,ntrks,-((-(division>>8))&0xff),division&0xff);
    } else {
        printf("MFile %d %d %d\n",format,ntrks,division);
    }
    if (format > 2) {
        fprintf(stderr, "Can't deal with format %d files\n",format);
        exit (1);
    }
    Beat = Clicks = division;
    TrksToDo = ntrks;
}

mytrstart() {

    printf("MTrk\n");
    TrkNr ++;
}

mytrend() {

    printf("TrkEnd\n");
    --TrksToDo;
}

mynon(int chan,int pitch,int vol) {

    prtime();
    printf(Onmsg,chan+1,mknote(pitch),vol);
}

mynoff(int chan,int pitch,int vol) {

    prtime();
    printf(Offmsg,chan+1,mknote(pitch),vol);
}

mypressure(int chan,int pitch,int press) {

    prtime();
    printf(PoPrmsg,chan+1,mknote(pitch),press);
}

myparameter(int chan,int control,int value) {

    prtime();
    printf(Parmsg,chan+1,control,value);
}

mypitchbend(int chan,int lsb,int msb) {

    prtime();
    printf(Pbmsg,chan+1,128*msb+lsb);
}

myprogram(int chan,int program) {

    prtime();
    printf(PrChmsg,chan+1,program);
}

mychanpressure(int chan,int press) {

    prtime();
    printf(ChPrmsg,chan+1,press);
}

mysysex(int leng,char *mess) {

    prtime();
    printf("SysEx");
    prhex (mess, leng);
}

mymmisc(int type,int leng,char *mess) {

    prtime();
    printf("Meta 0x%02x",type);
    prhex(mess, leng);
}

mymspecial(int leng,char *mess) {

    prtime();
    printf("SeqSpec");
    prhex(mess, leng);
}

mymtext(int type,int leng,char *mess) {

    static char *ttype[] = {
        NULL,
        "Text","Copyright","TrkName","InstrName","Lyric","Marker","Cue","Unrec"
    };
    int unrecognized = (sizeof(ttype)/sizeof(char *)) - 1;
    prtime();
    if (type < 1 || type > unrecognized)
        printf("Meta 0x%02x ",type);
    else if (type == 3 && TrkNr == 1)
        printf("Meta SeqName ");
    else
        printf("Meta %s ",ttype[type]);
    prtext (mess, leng);
}

mymseq(int num) {

    prtime();
    printf("SeqNr %d\n",num);
}

mymeot() {

    prtime();
    printf("Meta TrkEnd\n");
}

mykeysig(int sf,int mi) {

    prtime();
    printf("KeySig %d %s\n",(sf>127?sf-256:sf),(mi?"minor":"major"));
}

mytempo(long tempo) {

    prtime();
    printf("Tempo %ld\n",tempo);
}

mytimesig(int nn,int dd,int cc,int bb) {

    int denom = 1;

    while (dd-- > 0) denom *= 2;
    prtime();
    printf("TimeSig %d/%d %d %d\n",nn,denom,cc,bb);
    M0 += (Mf_currtime-T0)/(Beat*Measure);
    T0 = Mf_currtime;
    Measure = nn;
    Beat = 4 * Clicks / denom;
}

mysmpte(int hr,int mn,int se,int fr,int ff) {

    prtime();
    printf("SMPTE %d %d %d %d %d\n",hr,mn,se,fr,ff);
}

myarbitrary(int leng,char *mess) {

    prtime();
    printf("Arb",leng);
    prhex(mess, leng);
}

prtime() {

    if (times) {
        long m = (Mf_currtime-T0)/Beat;
        if (verbose)
            printf("%03ld:%02ld:%03ld ",
			    m/Measure+M0,m%Measure,(Mf_currtime-T0)%Beat);
        else
            printf("%ld:%ld:%ld ",
			    m/Measure+M0,m%Measure,(Mf_currtime-T0)%Beat);
    } else {
        if (verbose)
            printf("%-10ld ",Mf_currtime);
        else
            printf("%ld ",Mf_currtime);
    }
}

prtext(unsigned char *p,int leng) {

    int n, c;
    int pos = 25;
    
    printf("\"");
    for ( n=0; n<leng; n++ ) {
        c = *p++;
        if (fold && pos >= fold) {
            printf ("\\\n\t");
            pos = 13;    /* tab + \xab + \ */
            if (c == ' ' || c == '\t') {
                putchar ('\\');
                ++pos;
            }
        }
        switch (c) {
         case '\\':
         case '"':
            printf ("\\%c", c);
            pos += 2;
            break;
         case '\r':
            printf ("\\r");
            pos += 2;
            break;
         case '\n':
            printf ("\\n");
            pos += 2;
            break;
         case '\0':
            printf ("\\0");
            pos += 2;
            break;
         default:
            if (isprint(c)) {
                putchar(c);
                ++pos;
            } else {
                printf("\\x%02x" , c);
                pos += 4;
            }
        }
    }
    printf("\"\n");
}

prhex(unsigned char *p,int leng) {

    int n;
    int pos = 25;

    for(n=0; n<leng; n++,p++) {
        if (fold && pos >= fold) {
            printf ("\\\n\t%02x",*p);
            pos = 14;
        } else {
            printf(" %02x",*p);
            pos += 3;
        }
    }
    printf("\n");
}

myerror(char *s) {

    if (TrksToDo <= 0)
        fprintf(stderr,"Error: Garbage at end\n",s);
    else
        fprintf(stderr,"Error: %s\n",s);
}

initfuncs() {

    Mf_error = myerror;
    Mf_header =  myheader;
    Mf_starttrack =  mytrstart;
    Mf_endtrack =  mytrend;
    Mf_on =  mynon;
    Mf_off =  mynoff;
    Mf_pressure =  mypressure;
    Mf_parameter =  myparameter;
    Mf_pitchbend =  mypitchbend;
    Mf_program =  myprogram;
    Mf_chanpressure =  mychanpressure;
    Mf_sysex =  mysysex;
    Mf_metamisc =  mymmisc;
    Mf_seqnum =  mymseq;
    Mf_eot =  mymeot;
    Mf_timesig =  mytimesig;
    Mf_smpte =  mysmpte;
    Mf_tempo =  mytempo;
    Mf_keysig =  mykeysig;
    Mf_sqspecific =  mymspecial;
    Mf_text =  mymtext;
    Mf_arbitrary =  myarbitrary;
}

prs_error(char *s) {

    int c;
    int count;
    int ln = (eol_seen? lineno-1 : lineno);
    fprintf (stderr, "%d: %s\n", ln, s);
    if (yyleng > 0 && *yytext != '\n')
        fprintf (stderr, "*** %*s ***\n", yyleng, yytext);
    count = 0;
    while (count < 100 &&
       (c=yylex()) != EOL && c != EOF) count++/* skip rest of line */;
    if (c == EOF) exit(1);
    if (err_cont)
        longjmp (erjump,1);
}

syntax() {

    prs_error("Syntax error");
}

translate() {

    if (yylex() == MTHD) {
        Format = getint("MFile format");
        Ntrks = getint("MFile #tracks");
        Clicks = getint("MFile Clicks");
        if (Clicks < 0)
            Clicks = (Clicks&0xff)<<8|getint("MFile SMPTE division");
        checkeol();
        mfwrite(Format, Ntrks, Clicks, F);
    } else {
        fprintf (stderr, "Missing MFile - can't continue\n");
        exit(1);
    }
}

static int mywritetrack() {

    int opcode, c;
    long currtime = 0;
    long newtime, delta;
    int i, k;
    
    while ((opcode = yylex()) == EOL) ;
    if (opcode != MTRK) prs_error("Missing MTrk");
    checkeol();
    while(1) {
        err_cont = 1;
        setjmp (erjump);
        switch(yylex()) {
         case MTRK:
            prs_error("Unexpected MTrk");
         case EOF:
            err_cont = 0;
            error("Unexpected EOF");
            return -1;
         case TRKEND:
            err_cont = 0;
            checkeol();
            return 1;
         case INT:
            newtime = yyval;
            if ((opcode=yylex())=='/') {
                if (yylex()!=INT) prs_error("Illegal time value");
                newtime = (newtime-M0)*Measure+yyval;
                if (yylex()!='/'||yylex()!=INT) prs_error("Illegal time value");
                newtime = T0 + newtime*Beat + yyval;
                opcode = yylex();
            }
            delta = newtime - currtime;
            switch(opcode) {
             case ON:
             case OFF:
             case POPR:
                checkchan();
                checknote();
                checkval();
                mf_w_midi_event(delta,opcode,chan,data,2L);
                break;
             case PAR:
                checkchan();
                checkcon();
                checkval();
                mf_w_midi_event(delta,opcode,chan,data,2L);
                break;
             case PB:
                checkchan();
                splitval();
                mf_w_midi_event(delta,opcode,chan,data,2L);
                break;
             case PRCH:
                checkchan();
                checkprog();
                mf_w_midi_event(delta,opcode,chan,data,1L);
                break;
             case CHPR:
                checkchan();
                checkval();
                data[0] = data[1];
                mf_w_midi_event(delta,opcode,chan,data,1L);
                break;
             case SYSEX:
             case ARB:
                gethex();
                mf_w_sysex_event(delta,buffer,(long)buflen);
                break;
             case TEMPO:
                if (yylex() != INT) syntax();
                mf_w_tempo (delta,yyval);
                break;
             case TIMESIG: {
                    int nn,denom,cc,bb;
                    if (yylex() != INT || yylex() != '/') syntax();
                    nn = yyval;
                    denom = getbyte("Denom");
                    cc = getbyte("clocks per click");
                    bb = getbyte("32nd notes per 24 clocks");
                    for(i=0, k=1 ; k<denom; i++, k<<=1);
                    if (k!=denom) error("Illegal TimeSig");
                    data[0] = nn;
                    data[1] = i;
                    data[2] = cc;
                    data[3] = bb;
                    M0 += (newtime-T0)/(Beat*Measure);
                    T0 = newtime;
                    Measure = nn;
                    Beat = 4 * Clicks / denom;
                    mf_w_meta_event(delta,time_signature,data,4L);
                }
                break;
             case SMPTE:
                for(i = 0; i < 5; i++) data[i] = getbyte("SMPTE");
                mf_w_meta_event(delta,smpte_offset,data,5L);
                break;
             case KEYSIG:
                data[0] = i = getint("Keysig");
                if (i < -7 || i > 7)
                    error("Key Sig must be between -7 and 7");
                if ((c=yylex()) != MINOR && c != MAJOR)
                    syntax();
                data[1] = (c == MINOR);
                mf_w_meta_event(delta,key_signature,data,2L);
                break;
             case SEQNR:
                get16val("SeqNr");
                mf_w_meta_event(delta,sequence_number,data,2L);
                break;
             case META: {
                    int type = yylex();
                    switch(type) {
                     case TRKEND: type = end_of_track; break;
                     case TEXT:
                     case COPYRIGHT:
                     case SEQNAME:
                     case INSTRNAME:
                     case LYRIC:
                     case MARKER:
                     case CUE: type -= (META+1); break;
                     case INT: type = yyval; break;
                     default: prs_error("Illegal Meta type");
                    }
                    if (type == end_of_track)
                        buflen = 0;
                    else
                        gethex();
                    mf_w_meta_event(delta,type,buffer,(long)buflen);
                    break;
                }
             case SEQSPEC:
                gethex();
                mf_w_meta_event(delta,sequencer_specific,buffer,(long)buflen);
                break;
             default:
                prs_error("Unknown input");
                break;
            }
            currtime = newtime;
         case EOL:
            break;
         default:
            prs_error("Unknown input");
            break;
        }
        checkeol();
    }
}

getbyte(char *mess) {

    char ermesg[100];

    getint(mess);
    if (yyval < 0 || yyval > 127) {
        sprintf(ermesg,"Wrong value (%ld) for %s",yyval,mess);
        error(ermesg);
        yyval = 0;
    }
    return yyval;
}

getint(char *mess) {

    char ermesg[100];
    if (yylex() != INT) {
        sprintf(ermesg,"Integer expected for %s",mess);
        error(ermesg);
        yyval = 0;
    }
    return yyval;
}

static void checkchan() {

    if (yylex() != CH || yylex() != INT) syntax();
    if (yyval < 1 || yyval > 16) error("Chan must be between 1 and 16");
    chan = yyval-1;
}

static void checknote() {

    int c;

    if (yylex() != NOTE || ((c=yylex()) != INT && c != NOTEVAL))
    syntax();
    if (c == NOTEVAL) {
        static int notes[] = {9,11,0,2,4,5,7};
        char *p = yytext;
        c = *p++;
        if (isupper(c)) c = tolower(c);
        yyval = notes[c-'a'];
        switch(*p) {
         case '#':
         case '+': yyval++; p++; break;
         case 'b':
         case 'B':
         case '-': yyval--; p++; break;
       }
       yyval += 12 * atoi(p);
    }
    if (yyval < 0 || yyval > 127)
        error("Note must be between 0 and 127");
    data[0] = yyval;
}

static void checkval() {

    if (yylex() != VAL || yylex() != INT) syntax();
    if (yyval < 0 || yyval > 127)
        error("Value must be between 0 and 127");
    data[1] = yyval;
}

static void splitval() {

    if (yylex() != VAL || yylex() != INT) syntax();
    if (yyval < 0 || yyval > 16383)
       error("Value must be between 0 and 16383");
    data[0] = yyval%128;
    data[1] = yyval/128;
}

static void get16val() {

    if (yylex() != VAL || yylex() != INT) syntax();
    if (yyval < 0 || yyval > 65535)
        error("Value must be between 0 and 65535");
    data[0] = (yyval>>8)&0xff;
    data[1] = yyval&0xff;
}

static void checkcon() {

    if (yylex() != CON || yylex() != INT)
    syntax();
    if (yyval < 0 || yyval > 127)
        error("Controller must be between 0 and 127");
    data[0] = yyval;
}

static void checkprog() {

    if (yylex() != PROG || yylex() != INT) syntax();
    if (yyval < 0 || yyval > 127)
        error("Program number must be between 0 and 127");
    data[0] = yyval;
}

static void checkeol() {

    if (eol_seen) return;
    if (yylex() != EOL) {
        prs_error ("Garbage deleted");
        while (!eol_seen) yylex();
    }
}

static void gethex() {

    int c;

    buflen = 0;
    do_hex = 1;
    c = yylex();
    if (c == STRING) {
        int i = 0;
        if (yyleng-1 > bufsiz) {
            bufsiz = yyleng-1;
            if (buffer)
                buffer = realloc(buffer, bufsiz);
            else
                buffer = malloc (bufsiz);
            if (! buffer) error("Out of memory");
        }
        while(i<yyleng-1) {
            c = yytext[i++];
rescan:
            if (c == '\\') {
                switch (c = yytext[i++]) {
                 case '0': c = '\0'; break;
                 case 'n': c = '\n'; break;
                 case 'r': c = '\r'; break;
                 case 't': c = '\t'; break;
                 case 'x':
                    if (sscanf (yytext+i, "%2x", &c) != 1)
                        prs_error ("Illegal \\x in string");
                    i += 2;
                    break;
                 case '\r':
                 case '\n':
                    while ((c=yytext[i++])==' '||c=='\t'||c=='\r'||c=='\n')
                        goto rescan;
                }
            }
            buffer[buflen++] = c;
        }
    } else if (c == INT) {
        do {
            if (buflen >= bufsiz) {
                bufsiz += 128;
                if (buffer)
                    buffer = realloc(buffer,bufsiz);
                else
                   buffer = malloc(bufsiz);
                if (! buffer) error ("Out of memory");
            }
            buffer[buflen++] = yyval;
            c = yylex();
        } while (c == INT);
        if (c != EOL) prs_error("Unknown hex input");
    } else {
        prs_error("String or hex input expected");
    }
}

long bankno (char *s,int n) {

    long res = 0;
    int c;
    while(n-- > 0) {
        c = (*s++);
        if (islower(c))
            c -= 'a';
        else if (isupper(c))
            c -= 'A';
        else
            c -= '1';
        res = res * 8 + c;
    }
    return res;
}


FILE *efopen(char *name,char *mode) {
if (dbg) fprintf(stderr,"efopen(%s,%s)\n",name,mode);

    FILE *f;
    if ((f = fopen(name,mode)) == NULL) {
        (void) fprintf(stderr,"Cannot open '%s', %s!\n",name,strerror(errno));
        exit(1);
    }
    return(f);
}

fileputc(int c) {

    return putc(c,F);
}

int filegetc() {

    return(getc(F));
}
