
/*    MIDIRECV.C    */

/*
 *
 * As of April 1998 this version of midirecv.c enables
 * Csound to receive MIDI input to trigger orchestras.
 *
 *    Dave Phillips <dlphilp@bright.net>
 *
 */

/* Modified 1998 June 5 by Damien Miller & Robin Whittle    
 * to compile under glibc, rather than the earlier C library.  
 * Took out #include <bsd/sgtty.h> and used termios.h and 
 * errno.h instead.  New code in MidiOpen.               
 * Changes flagged with the text "glibc" and original code    
 * is included in comments.   
 * 
 * Damien had been reading W. Richard Steven's "Advanced  
 * Programming for the Unix Environment" and whipped up these 
 * changes in a few minutes.                                  
 *          
 * Note!  We haven't tested this!!          
 *
 * Robin Whittle rw@firstpr.com.au          
 */


#include "cs.h"
#include "midiops.h"
#include "oload.h"
#include <sys/time.h>

					/* glibc changes.  Was:
					 *
					 * #include <bsd/sgtty.h>
					 */
#include <termios.h>
#include <errno.h>


#define INBAUD    EXTB
#define MBUFSIZ   1024
#define ON        1
#define OFF       0
#define MAXLONG   0x7FFFFFFFL

					/* glibc changes.  Was:
					 *
					 * static struct sgttyb tty;
					 */
static struct termios tty;

static u_char *mbuf, *bufp, *bufend, *endatp;
static u_char *sexbuf, *sexp, *sexend;
static u_char *fsexbuf, *fsexp, *fsexend;
static int  rtfd = 0;        /* init these to stdin */
static FILE *mfp = stdin;

MEVENT  *Midevtblk, *FMidevtblk;
MCHNBLK *m_chnbp[MAXCHAN];   /* ptrs to chan ctrl blks */

static float MastVol = 1.0f;     /* maps ctlr 7 to ctlr 9 */
static long  MTrkrem;
static double FltMidiNxtk, kprdspertick, ekrdQmil;

long   FMidiNxtk;
int    Mforcdecs = 0, Mxtroffs = 0, MTrkend = 0;

static void  m_chn_init(MEVENT *, short);
static void  (*nxtdeltim)(void), Fnxtdeltim(void), Rnxtdeltim(void);
extern void  schedofftim(INSDS *), deact(INSDS *), beep(void);
static float mapctl(int, float);
void midNotesOff(void);

static int   defaultinsno = 0;
extern INSTRTXT *instrtxtp[];
extern VPGLST *vpglist;
extern short *insbusy;
extern float **pctlist;
extern long  kcounter;
extern OPARMS O;
extern INX inxbas;
extern void xturnon(int, long);
extern void xturnoff(INSDS*);
extern void insxtroff(short);

void MidiOpen(void)   /* open a Midi event stream for reading, alloc bufs */
{                     /*     callable once from main.c                    */
        Midevtblk = (MEVENT *) mcalloc((long)sizeof(MEVENT));
        mbuf = (u_char *) mcalloc((long)MBUFSIZ);
        bufend = mbuf + MBUFSIZ;
        bufp = endatp = mbuf;
        sexbuf = (u_char *) mcalloc((long)MBUFSIZ);
        sexend = sexbuf + MBUFSIZ;
        sexp = NULL;
        m_chn_init(Midevtblk,(short)0);
        if (strcmp(O.Midiname,"stdin") == 0) {
            if (fcntl(rtfd, F_SETFL, fcntl(rtfd, F_GETFL, 0) | O_NDELAY) < 0)
                die("-M stdin fcntl failed");
        }
        else {                   /* open MIDI device, & set nodelay on reads  */

            if ((rtfd = open(O.Midiname, O_RDONLY | O_NDELAY, 0)) < 0)
                dies("cannot open %s", O.Midiname);

		/* glibc changes.  Was:
		 *   ioctl(rtfd, TIOCGETP, &tty);    for other machines      
           	 *   tty.sg_ispeed = INBAUD;         set baud rate         
		 *   tty.sg_flags = RAW;             and no I/O processing 
		 *   ioctl(rtfd, TIOCSETP, &tty);
		 */

		if (tcgetattr(rtfd, &tty) < 0)
			die("MIDI receive: Can't get termios info.");
		cfmakeraw(&tty);
		if (cfsetispeed(&tty, INBAUD) < 0)
			die("MIDI receive: Can't set input baud rate.");
		if (tcsetattr(rtfd, TCSANOW, &tty) < 0)
			die("MIDI receive: Can't set termios.");

        }
}

static void Fnxtdeltim(void) /* incr FMidiNxtk by next delta-time */
{                            /* standard, with var-length deltime */
        unsigned long deltim = 0;
        unsigned char c;
        short count = 1;

        if (MTrkrem > 0) {
            while ((c = getc(mfp)) & 0x80) {
                deltim += c & 0x7F;
                deltim <<= 7;
                count++;
            }
            MTrkrem -= count;
            if ((deltim += c) > 0) {                  /* if deltim nonzero */
                FltMidiNxtk += deltim * kprdspertick; /*   accum in double */
                FMidiNxtk = (long) FltMidiNxtk;       /*   the kprd equiv  */
/*              printf("FMidiNxtk = %ld\n", FMidiNxtk);  */
            }
        }
        else {
            printf("end of track in midifile '%s'\n", O.FMidiname);
            printf("%d forced decays, %d extra noteoffs\n",
                   Mforcdecs, Mxtroffs);
            MTrkend = 1;
            O.FMidiin = 0;
            if (O.ringbell && !O.termifend)  beep();
        }
}

static void Rnxtdeltim(void)        /* incr FMidiNxtk by next delta-time */
{             /* Roland MPU401 form: F8 time fillers, no Trkrem val, EOF */
        unsigned long deltim = 0;
        int c;

        do {
            if ((c = getc(mfp)) == EOF) {
                printf("end of MPU401 midifile '%s'\n", O.FMidiname);
                printf("%d forced decays, %d extra noteoffs\n",
                       Mforcdecs, Mxtroffs);
                MTrkend = 1;
                O.FMidiin = 0;
                if (O.ringbell && !O.termifend)  beep();
                return;
            }
            deltim += (c &= 0xFF);
        }
        while (c == 0xF8);      /* loop while sys_realtime tming clock */
        if (deltim) {                             /* if deltim nonzero */
            FltMidiNxtk += deltim * kprdspertick; /*   accum in double */
            FMidiNxtk = (long) FltMidiNxtk;       /*   the kprd equiv  */
/*          printf("FMidiNxtk = %ld\n", FMidiNxtk);  */
        }
}

void FMidiOpen(void) /* open a MidiFile for reading, sense MPU401 or standard */
{                    /*     callable once from main.c      */
        short sval;
        long lval, tickspersec;
        u_long deltim;
        char inbytes[16];    /* must be long-aligned, 16 >= MThd maxlen */
extern long natlong(long);
extern short natshort(short);

        FMidevtblk = (MEVENT *) mcalloc((long)sizeof(MEVENT));
        fsexbuf = (u_char *) mcalloc((long)MBUFSIZ);
        fsexend = fsexbuf + MBUFSIZ;
        fsexp = NULL;
        m_chn_init(FMidevtblk,(short)0);

        if (strcmp(O.FMidiname,"stdin") == 0) {
        }
        else if (!(mfp = fopen(O.FMidiname, "rb")))
            dies("cannot open '%s'", O.FMidiname);
        if ((inbytes[0] = getc(mfp)) != 'M')
            goto mpu401;
        if ((inbytes[1] = getc(mfp)) != 'T') {
            ungetc(inbytes[1],mfp);
            goto mpu401;
        }
        if (fread(inbytes+2, 1, 6, mfp) < 6)
            dies("unexpected end of '%s'", O.FMidiname);
        if (strncmp(inbytes, "MThd", 4) != 0)
            dies("we're confused.  file '%s' begins with 'MT',\nbut not a legal header chunk", O.FMidiname);
        printf("%s: found standard midifile header\n", O.FMidiname);
        if ((lval = natlong(*(long *)(inbytes+4))) < 6 || lval > 16) {
            sprintf(errmsg,"bad header length %ld in '%s'", lval, O.FMidiname);
            die(errmsg);
        }
        if (fread(inbytes, 1, (int)lval, mfp) < (unsigned long)lval)
            dies("unexpected end of '%s'", O.FMidiname);
        if ((sval = natshort(*(short *)inbytes)) != 0) {
            sprintf(errmsg,"%s: Midifile format %d not supported",
                    O.FMidiname, sval);
            die(errmsg);
        }
        if ((sval = natshort(*(short *)(inbytes+2))) != 1)
            dies("illegal ntracks in '%s'", O.FMidiname);
        if ((inbytes[4] & 0x80)) {
            short SMPTEformat, SMPTEticks;
            SMPTEformat = -(inbytes[4]);
            SMPTEticks = *(u_char *)inbytes+5;
            if (SMPTEformat == 29)  SMPTEformat = 30;  /* for drop frame */
            printf("SMPTE timing, %d frames/sec, %d ticks/frame\n",
                   SMPTEformat, SMPTEticks);
            tickspersec = SMPTEformat * SMPTEticks;
        }
        else {
            short Qticks = natshort(*(short *)(inbytes+4));
            printf("Metrical timing, Qtempo = 120.0, Qticks = %d\n", Qticks);
            ekrdQmil = ekr / Qticks / 1000000.;
            tickspersec = Qticks * 2;
        }
        kprdspertick = ekr / tickspersec;
        printf("kperiods/tick = %7.3f\n", kprdspertick);

chknxt: if (fread(inbytes, 1, 8, mfp) < 8)         /* read a chunk ID & size */
            dies("unexpected end of '%s'", O.FMidiname);
        if ((lval = natlong(*(long *)(inbytes+4))) <= 0)
            dies("improper chunksize in '%s'", O.FMidiname);
        if (strncmp(inbytes, "MTrk", 4) != 0) {    /* if not an MTrk chunk,  */
            do sval = getc(mfp);                   /*    skip over it        */
            while (--lval);
            goto chknxt;
        }
        printf("tracksize = %ld\n", lval);
        MTrkrem = lval;                            /* else we have a track   */
        FltMidiNxtk = 0.;
        FMidiNxtk = 0;                             /* init the time counters */
        nxtdeltim = Fnxtdeltim;                    /* set approp time-reader */
        nxtdeltim();                               /* incr by 1st delta-time */
        return;

mpu401: printf("%s: assuming MPU401 midifile format, ticksize = 5 msecs\n", O.FMidiname);
        kprdspertick = ekr / 200.;
        ekrdQmil = 1.;                             /* temp ctrl (not needed) */
        MTrkrem = MAXLONG;                         /* no tracksize limit     */
        FltMidiNxtk = 0.;
        FMidiNxtk = 0;
        nxtdeltim = Rnxtdeltim;                    /* set approp time-reader */
        if ((deltim = (inbytes[0] & 0xFF))) {      /* if 1st time nonzero    */
            FltMidiNxtk += deltim * kprdspertick;  /*     accum in double    */
            FMidiNxtk = (long) FltMidiNxtk;        /*     the kprd equiv     */
/*          printf("FMidiNxtk = %ld\n", FMidiNxtk);   */
            if (deltim == 0xF8)     /* if char was sys_realtime timing clock */
                nxtdeltim();                      /* then also read nxt time */
        }
}

void MidiClose(void)
{
#ifndef __ZPC__
    extern int close(int);
        if (rtfd) close(rtfd);
#endif
        if (mfp) fclose(mfp);
}

static void AllNotesOff(MCHNBLK *);

static void sustsoff(MCHNBLK *chn)  /* turnoff all notes in chnl sust array */
{                        /* called by SUSTAIN_SW_off only if count non-zero */
    INSDS *ip, **ipp1, **ipp2;
    short nn, suscnt;

    suscnt = chn->ksuscnt;
    ipp1 = ipp2 = chn->ksusptr + 64;          /* find midpoint of sustain array */
    ipp1--;
    for (nn = 64; nn--; ipp1--, ipp2++ ) {
        if ((ip = *ipp1) != NULL) {
            *ipp1 = NULL;
            do {
            if (ip->xtratim) {
                ip->relesing = 1;
                ip->offtim = (kcounter + ip->xtratim) * onedkr;
                schedofftim(ip);
            }
            else deact(ip);
            } while ((ip = ip->nxtolap) != NULL);
            if (--suscnt == 0)  break;
        }
        if ((ip = *ipp2) != NULL) {
            *ipp2 = NULL;
            do {
            if (ip->xtratim) {
                ip->relesing = 1;
                ip->offtim = (kcounter + ip->xtratim) * onedkr;
                schedofftim(ip);
            }
            else deact(ip);
            } while ((ip = ip->nxtolap) != NULL);
            if (--suscnt == 0)  break;
        }
    }
    if (suscnt) printf("sustain count still %d\n", suscnt);
    chn->ksuscnt = 0;
}

static void m_timcod_QF(int a, int b) { IGNORE(a); IGNORE(b);}  /* dummy sys_common targets */
static void m_song_pos(long a) { IGNORE(a);}
static void m_song_sel(long a) { IGNORE(a);}

static void m_chanmsg(MEVENT *mep) /* exec non-note chnl_voice & chnl_mode cmnds */
{
    MCHNBLK *chn = m_chnbp[mep->chan];
    short n;
    float *fp;

    switch(mep->type) {
    case POLYAFT_TYPE:
            chn->polyaft[mep->dat1] = mep->dat2;     /* Polyphon per-Key Press  */
            break;
    case CONTROL_TYPE:                              /* CONTROL CHANGE MESSAGES: */
            if ((n = mep->dat1) >= 111)                 /* if special, redirect */
                goto special;          
            if (n == RPNLSB && mep->dat2 == 127 && chn->dpmsb == 127)
                chn->ctl_val[DATENABL] = 0.0f;
            else if (n == NRPNMSB || n == RPNMSB)
                chn->dpmsb = mep->dat2;
            else if (n == NRPNLSB || n == RPNLSB) {
                chn->dplsb = mep->dat2;
                chn->ctl_val[DATENABL] = 1.0f;
            } else if (n == DATENTRY && chn->ctl_val[DATENABL] != 0.0f) {    
                int   msb = chn->dpmsb;
                int   lsb = chn->dplsb;
                float fval;
                if (msb == 0 && lsb == 0) {         
                    chn->ctl_val[BENDSENS] = mep->dat2;
                }
                else if (msb == 1) {                /* GS system PART PARAMS */
                    int ctl;
                    switch(lsb) {
                        case 8:  ctl = VIB_RATE;        break;
                        case 9:  ctl = VIB_DEPTH;       break;
                        case 10: ctl = VIB_DELAY;       break;
                        case 32: ctl = TVF_CUTOFF;      break;
                        case 33: ctl = TVF_RESON;       break;
                        case 99: ctl = TVA_RIS;         break;
                        case 100:ctl = TVA_DEC;         break;
                        case 102:ctl = TVA_RLS;         break;
                        default:printf("unknown NPRN lsb %d\n", lsb);
                    }
                    fval = (float) (mep->dat2 - 64);
                    if (uctl_map != NULL && (fp = uctl_map[ctl]) != NULL) {
                        float xx = (fval * *fp++);
                        fval = xx + *fp;    /* optionally map */
                    }
                    chn->ctl_val[ctl] = fval;           /* then store     */
                } else {                             
                    if (msb < 24 || msb == 25 || msb == 27 || msb > 31
                     || lsb < 25 || lsb > 87)
                        printf("unknown drum param nos, msb %ld lsb %ld\n", 
                                (long)msb, (long)lsb);
                    else {
                        static int drtab[8] = {0,0,1,1,2,3,4,5};
                        int parnum = drtab[msb - 24];
                        if (parnum == 0)
                            fval = (float) (mep->dat2 - 64);
                        else fval = mep->dat2;
                        if (dsctl_map != NULL) {
                            fp = &dsctl_map[parnum*2];
                            if (*fp != 0.) {
                              float xx = (fval * *fp++);
                                fval = xx + *fp;    /* optionally map */
                            }
                        }                                   
			  printf("CHAN %ld DRUMKEY %ld not in keylst,"
				 " PARAM %ld NOT UPDATED\n",
				 (long)mep->chan+1, (long)lsb, (long)msb);
                    }
                }
            }
            else if (uctl_map != NULL && (fp = uctl_map[n]) != NULL) {
              float xx = (mep->dat2 * *fp++);
                chn->ctl_val[n] = xx + *fp;    /* optional map */
            }
            else chn->ctl_val[n] = (float) mep->dat2;   /* record data as float */
            if (n == VOLUME)
                chn->ctl_val[MOD_VOLUME] = chn->ctl_val[VOLUME] * MastVol;
            else if (n == SUSTAIN_SW) {
                short temp = (mep->dat2 >= O.SusPThresh);
                if (chn->sustaining != temp) {          /* if sustainP changed  */
                    if (chn->sustaining && chn->ksuscnt)/*  & going off */
                        sustsoff(chn);                  /*      reles any notes */
                    chn->sustaining = temp;
                }
            }
            break;

special:    if (n < 121) {          /* for ctrlr 111, 112, ... chk inexclus lists */
                int index = mep->dat2;                  /*    for the index given */
                INX *inxp = &inxbas;
                while ((inxp = inxp->nxtinx) != NULL)
                    if (inxp->ctrlno == n) {            /* if found ctrlno xclist */
                        int *insp, cnt = inxp->inscnt;
                        if (index <= cnt) {             /*   & the index in-range */
			  INSDS *ip;
			  long xtratim = 0;           /*     turnoff all instrs */
			  for (insp = inxp->inslst; cnt--; insp++)
			    if ((ip = instrtxtp[*insp]->instance) != NULL) {
			      do  if (ip->actflg) {
				if (ip->xtratim > xtratim)
				  xtratim = ip->xtratim;
				xturnoff(ip);
			      }
			      while ((ip = ip->nxtinstance) != NULL);
			    }
			  if (index) {
			    int insno = inxp->inslst[index-1];
			    xturnon(insno, xtratim); /*     & schedstart this */
			    printf("instr %ld now on\n", (long)insno);
			  }
                        }
                        else printf("index %ld exceeds ctrl %ld exclus list\n",
                                (long)index, (long)n);
                        return;
                    }
                printf("ctrl %ld has no exclus list\n", (long)n);
                break;
            }
/* modemsg: */
            if (n == 121) {                         /* CHANNEL MODE MESSAGES:  */
                float *fp = chn->ctl_val + 1;   /* from ctlr 1 */
                short nn = 101;                 /* to ctlr 101 */
                do *fp++ = 0.0f;                /*   reset all ctlrs to 0 */
                while (--nn);                           /* exceptions:  */
                chn->ctl_val[7] = mapctl(7, 127.0f);    /*   volume     */
                chn->ctl_val[8] = mapctl(8, 64.0f);     /*   balance    */
                chn->ctl_val[10] = mapctl(10, 64.0f);   /*   pan        */
                chn->ctl_val[11] = mapctl(11, 127.0f);  /*   expression */
                chn->ctl_val[BENDSENS] = mapctl(BENDSENS, 2.0f);
                chn->ctl_val[9] = chn->ctl_val[7] * MastVol;
            }
            else if (n == 122) {                        /* absorb lcl ctrl data */
/*                int lcl_ctrl = mep->dat2;  ?? */              /* 0:off, 127:on */
            }
            else if (n == 123) midNotesOff();           /* allchnl AllNotesOff */
            else if (n == 126) {                        /* MONO mode */
                if (chn->monobas == NULL) {
                    MONPCH *mnew, *mend;
                    chn->monobas = (MONPCH *)mcalloc((long)sizeof(MONPCH) * 8);
                    mnew = chn->monobas;  mend = mnew + 8;
                    do  mnew->pch = -1;
                    while (++mnew < mend);
                }
                chn->mono = 1;
            }
            else if (n == 127) {                        /* POLY mode */
                if (chn->monobas != NULL) {
                    free((char *)chn->monobas);
                    chn->monobas = NULL;
                }
                chn->mono = 0;
            }
            else printf("chnl mode msg %d not implemented\n", n);
            break;
    case AFTOUCH_TYPE:
            chn->aftouch = mep->dat1;               /* chanl (all-key) Press */
            break;
    case PCHBEND_TYPE:
            chn->pchbend = (float)(((mep->dat2 - 64) << 7) + mep->dat1)/8192.0f;
            chn->posbend = (float)((mep->dat2 << 7) + mep->dat1) / 16384.0f;
            break;
    case SYSTEM_TYPE:              /* sys_common 1-3 only:  chan contains which */
            switch(mep->chan) {
            case 1: m_timcod_QF((int)((mep->dat1)>>4) & 0x7, (int)mep->dat1 & 0xF);
                    break;
            case 2: m_song_pos((((long)mep->dat2)<<7) + mep->dat1);
                    break;
            case 3: m_song_sel((long)mep->dat1);
                    break;
            default:sprintf(errmsg,"unrecognized sys_common type %d", mep->chan);
                    die(errmsg);
            }
            break;
    default:sprintf(errmsg,"unrecognized message type %d", mep->type);
            die(errmsg);
    }
}

static void m_chn_init(MEVENT *mep, short chan)
    /* alloc a midi control blk for a midi chnl */
    /*  & assign corr instr n+1, else a default */
{        
    MCHNBLK *chn;

    if (!defaultinsno) {
      int n;                  /* find lowest instr as default */
      for (n = 1; n <= maxinsno; n++)
        if (instrtxtp[n] != NULL) {
          defaultinsno = n;
          goto alcon;
        }
      die("midi init cannot find any instrs");
    }
alcon:
    if ((chn = m_chnbp[chan]) == NULL)
      m_chnbp[chan] = chn = (MCHNBLK *) mcalloc((long)sizeof(MCHNBLK));
/*     chn->Omni = 1; */
/*     chn->Poly = 1; */
/*     chn->bas_chnl = chan; */
/*     chn->nchnls = 1; */
    if (instrtxtp[chan+1] != NULL)           /* if corresp instr exists  */
      chn->pgmno = chan+1;                 /*     assign as pgmno      */
    else chn->pgmno = defaultinsno;          /* else assign the default  */
/*     chn->pbensens = 1.0; */                 /* pbend sensit 1 semitone  */
    mep->type = CONTROL_TYPE;
    mep->chan = chan;
    mep->dat1 = 121;  /* reset all controllers */
    m_chanmsg(mep);
    printf("midi channel %d using instr %d\n", chan + 1, chn->pgmno);
}

static void ctlreset(short chan)    /* reset all controllers for this channel */
{        
        MEVENT  mev;
        mev.type = CONTROL_TYPE;
        mev.chan = chan;
        mev.dat1 = 121;         
        m_chanmsg(&mev);
}

MCHNBLK *m_getchnl(short chan)          /* get or create a chnlblk ptr */
{
        MCHNBLK *chn;
        if (chan < 0 || chan >= MAXCHAN) {
            sprintf(errmsg,"illegal midi chnl no %d", chan+1);
            die(errmsg);
        }
        if ((chn = m_chnbp[chan]) == NULL) {
            m_chnbp[chan] = chn = (MCHNBLK *) mcalloc((long)sizeof(MCHNBLK)); 
            chn->pgmno = -1;
            chn->insno = -1;
            ctlreset(chan);
        }
        return(chn);
}

void m_chinsno(short chan, short insno)         /* assign an insno to a chnl */
{                                               /* =massign: called from i0  */
    MCHNBLK  *chn = NULL;                              
    
    if (insno <= 0 /* || insno >= maxinsno */
        || instrtxtp[insno] == NULL)  die("unknown instr");
    if (m_chnbp[chan] != NULL)
      printf("massign: chnl %d exists, ctrls now defaults\n");
    chn = m_getchnl(chan);
    chn->insno = insno;
    chn->posbend = 0.5f;                 /* for pos pchbend (0 - 1.) */
    ctlreset(chan);
    printf("chnl %d using instr %d\n", chan+1, chn->insno);
}

static void AllNotesOff(MCHNBLK *chn)
{
    INSDS *ip, **ipp = chn->kinsptr;
    int nn = 128;

    do {
        if ((ip = *ipp) != NULL) {      /* if find a note in kinsptr slot */
            deact(ip);                  /*    deactivate, clear the slot  */
            *ipp = NULL;
        }
        ipp++;
    } while (--nn); 
    if (chn->sustaining)                /* same for notes in sustain list */
        sustsoff(chn);                  
    insxtroff(chn->insno);              /* finally rm all xtratim hanging */
}

void midNotesOff(void)          /* turnoff ALL curr midi notes, ALL chnls */
{                               /* called by musmon, ctrl 123 & sensFMidi */
        int chan = 0;                   
        MCHNBLK *chn;
        do  if ((chn = m_chnbp[chan]) != NULL)
                AllNotesOff(chn);
        while (++chan < MAXCHAN);
}


static float mapctl(int ctl, float fval)
{
    float *fp;
    if (uctl_map != NULL && (fp = uctl_map[ctl]) != NULL)
        fval = (fval * *fp++) + *fp;
    return(fval);
}

void setmastvol(short mvdat)    /* set MastVol & adjust all chan modvols */
{
        MCHNBLK *chn;
        int chnl;
        MastVol = (float)mvdat * (1.0f/128.0f);
        for (chnl = 0; chnl < MAXCHAN; chnl++)
            if ((chn = m_chnbp[chnl]) != NULL)
                chn->ctl_val[MOD_VOLUME] = chn->ctl_val[VOLUME] * MastVol;
}


static void m_start(void) {}      /* dummy sys_realtime targets */
static void m_contin(void) {}
static void m_stop(void) {}
static void m_sysReset(void) {}
static void m_tuneReq(void) {}

static int sexcnt = 0;
static void m_sysex(u_char *sbuf, u_char *sp) /* sys_excl msg, sexbuf: ID + data */
{
    int nbytes = sp - sbuf;
    if (++sexcnt >= 100) {
        printf("100th system exclusive $%x, length %d\n", *sbuf, nbytes);
        sexcnt = 0;
    }
}

static short datbyts[8] = { 2, 2, 2, 2, 1, 1, 2, 0 };
static short m_clktim = 0;
static short m_sensing = 0;

int sensMidi(void)         /* sense a MIDI event, collect the data & dispatch */
{                          /*  called from kperf(), return(2) if MIDI on/off  */
        short  c, type;
        MEVENT *mep = Midevtblk;
static  short datreq, datcnt;
        int  n;

	/* For select() call, from David Ratajczak */
        fd_set rfds;
        struct timeval tv;
        int retval;

nxtchr: if (bufp >= endatp) {

	   /********  NEW STUFF **********/ /* from David Ratajczak */
	   /* Use select() to make truly */
	   /* non-blocking call to midi  */
	   /******************************/

           /* Watch rtfd to see when it has input. */
           FD_ZERO(&rfds);
           FD_SET(rtfd, &rfds);
	   /* return immediately */
           tv.tv_sec = 0;
           tv.tv_usec = 0;

           retval = select(rtfd+1, &rfds, NULL, NULL, &tv);
           /* Don't rely on the value of tv now! */

           if (retval) {
	     if(retval<0)printf("sensMIDI: retval errno %d",errno);
	     if ((n = read(rtfd, (char *)mbuf, MBUFSIZ)) > 0) {
	       bufp = mbuf;
	       endatp = mbuf + n;
	     }
	     else return(0);
	   } else return(0);
	}
        if ((c = *bufp++) & 0x80) {              /* STATUS byte:      */
            type = c & 0xF0;
            if (type == SYSTEM_TYPE) { 
                short lo3 = (c & 0x07);
                if (c & 0x08)                    /* sys_realtime:     */
                    switch (lo3) {               /*   dispatch now    */
                    case 0: m_clktim++;
                            goto nxtchr;
                    case 2: m_start();
                            goto nxtchr;
                    case 3: m_contin();
                            goto nxtchr;
                    case 4: m_stop();
                            goto nxtchr;
                    case 6: m_sensing = 1;
                            goto nxtchr;
                    case 7: m_sysReset();
                            goto nxtchr;
                    default: printf("undefined sys-realtime msg %x\n",c);
                            goto nxtchr;
                    }
                else {                          /* sys_non-realtime status:   */
                    if (sexp != NULL) {            /* implies           */
                        m_sysex(sexbuf,sexp);      /*   sys_exclus end  */
                        sexp = NULL;
                    }
                    switch (lo3) {                 /* dispatch on lo3:  */
                    case 7: goto nxtchr;           /* EOX: already done */
                    case 0: sexp = sexbuf;         /* sys_ex begin:     */
                            goto nxtchr;           /*   goto copy data  */
                    case 1:                        /* sys_common:       */
                    case 3: datreq = 1;            /*   need some data  */
                            break;
                    case 2: datreq = 2;            /*   (so build evt)  */
                            break;     
                    case 6: m_tuneReq();           /*   this do immed   */
                            goto nxtchr;
                    default: printf("undefined sys_common msg %x\n", c);
                            datreq = 32767; /* waste any data following */
                            datcnt = 0;
                            goto nxtchr;
                    }
                }
                mep->type = type;               /* begin sys_com event  */
                mep->chan = lo3;                /* holding code in chan */
                datcnt = 0;
                goto nxtchr;
            }
            else {                              /* other status types:  */
                short chan;
                if (sexp != NULL) {                /* also implies      */
                    m_sysex(sexbuf,sexp);          /*   sys_exclus end  */
                    sexp = NULL;
                }
                chan = c & 0xF;
                if (m_chnbp[chan] == NULL)         /* chk chnl exists   */
                    m_chn_init(mep, chan);
                mep->type = type;                  /* & begin new event */
                mep->chan = chan;
                datreq = datbyts[(type>>4) & 0x7];
                datcnt = 0;
                goto nxtchr;
            }
        }
        if (sexp != NULL) {                   /* NON-STATUS byte:      */
            if (sexp < sexend)                /* if sys_excl           */
                *sexp++ = (u_char)c;          /*    special data sav   */
            else printf("system exclusive buffer overflow\n");
            goto nxtchr;
        }
        if (datcnt == 0)
            mep->dat1 = c;                    /* else normal data      */
        else mep->dat2 = c;
        if (++datcnt < datreq)                /* if msg incomplete     */
            goto nxtchr;                      /*   get next char       */
        datcnt = 0;                           /* else allow a repeat   */
                        /* NB:  this allows repeat in syscom 1,2,3 too */
        if (mep->type > NOTEON_TYPE) {        /* if control or syscom  */
            m_chanmsg(mep);                   /*   handle from here    */
            goto nxtchr;                      /*   & go look for more  */
        }
        return(2);                            /* else it's note_on/off */
}

static long vlendatum(void)  /* rd variable len datum from input stream */
{
        long datum = 0;
        unsigned char c;
        while ((c = getc(mfp)) & 0x80) {
            datum += c & 0x7F;
            datum <<= 7;
            MTrkrem--;
        }
        datum += c;
        MTrkrem--;
        return(datum);
}

static void fsexdata(int n) /* place midifile data into a sys_excl buffer */
{
        MTrkrem -= n;
        if (fsexp == NULL)                 /* 1st call, init the ptr */
            fsexp = fsexbuf;
        if (fsexp + n <= fsexend) {
            fread(fsexp, 1, n, mfp);       /* addin the new bytes    */
            fsexp += n;
            if (*(fsexp-1) == 0xF7) {      /* if EOX at end          */
                m_sysex(fsexbuf,fsexp);    /*    execute and clear   */
                fsexp = NULL;
            }
        }
        else {
            unsigned char c;
            printf("system exclusive buffer overflow\n");
            do c = getc(mfp);
            while (--n);
            if (c == 0xF7)
                fsexp = NULL;
        }
}

int sensFMidi(void)     /* read a MidiFile event, collect the data & dispatch */
{                     /* called from kperf(), return(SENSMFIL) if MIDI on/off */
        short  c, type;
        MEVENT *mep = FMidevtblk;
        long len;
static short datreq;

nxtevt: if (--MTrkrem < 0 || (c = getc(mfp)) == EOF)
            goto Trkend;
        if (!(c & 0x80))      /* no status, assume running */
            goto datcpy;
        if ((type = c & 0xF0) == SYSTEM_TYPE) {     /* STATUS byte:      */
            short lo3;
            switch(c) {
            case 0xF0:                          /* SYS_EX event:  */
                if ((len = vlendatum()) <= 0)
                    die("zero length sys_ex event");
                printf("reading sys_ex event, length %ld\n",len);
                fsexdata((int)len);
                goto nxtim;
            case 0xF7:                          /* ESCAPE event:  */
                if ((len = vlendatum()) <= 0)
                    die("zero length escape event");
                printf("escape event, length %ld\n",len);
                if (sexp != NULL)
                    fsexdata((int)len);       /* if sysex contin, send  */
                else {
                    MTrkrem -= len;
                    do c = getc(mfp);    /* else for now, waste it */
                    while (--len);
                }
                goto nxtim;
            case 0xFF:                          /* META event:     */
                if (--MTrkrem < 0 || (type = getc(mfp)) == EOF)
                    goto Trkend;
                len = vlendatum();
                MTrkrem -= len;
                switch(type) {
                  long usecs;
                case 0x51: usecs = 0;           /* set new Tempo       */
                           do {
                               usecs <<= 8;
                               usecs += (c = getc(mfp)) & 0xFF;
                           }
                           while (--len);
                           if (usecs <= 0)
                               printf("%ld usecs illegal in Tempo event\n", usecs);
                           else {
                               kprdspertick = usecs * ekrdQmil;
                         /*    printf("Qtempo = %5.1f\n", 60000000. / usecs); */
                           }
                           break;
                case 0x01:
                case 0x02:
                case 0x03:
                case 0x04:
                case 0x05:                      /* print text events  */
                case 0x06:
                case 0x07: while (len--) {
                               int ch;
                               putchar(ch = getc(mfp));
                               if (dribble) putc(ch, dribble);
                           }
                           break;
                case 0x2F: goto Trkend;         /* normal end of track */
                default:   printf("skipping meta event type %x\n",type);
                           do c = getc(mfp);
                           while (--len);
                }
                goto nxtim;
            }
            lo3 = (c & 0x07);
            if (c & 0x08) {                  /* sys_realtime:     */
                switch (lo3) {               /*   dispatch now    */
                case 0:
                case 1: break;    /* Timing Clk handled in Rnxtdeltim() */
                case 2: m_start();
                        break;
                case 3: m_contin();
                        break;
                case 4: m_stop();
                        break;
                case 6: m_sensing = 1;
                        break;
                case 7: m_sysReset();
                        break;
                default: printf("undefined sys-realtime msg %x\n",c);
                }
                goto nxtim;
            }
            else if (lo3 == 6) {          /* sys_non-realtime status:   */
                m_tuneReq();              /* do this one immed  */
                goto nxtim;
            }
            else {
                mep->type = type;         /* ident sys_com event  */
                mep->chan = lo3;          /* holding code in chan */
                switch (lo3) {            /* now need some data   */
                case 1:
                case 3: datreq = 1;
                        break;
                case 2: datreq = 2;
                        break;     
                default: sprintf(errmsg,"undefined sys_common msg %x\n", c);
                        die(errmsg);
                }
            }
        }
        else {                              /* other status types:  */
            short chan = c & 0xF;
            if (m_chnbp[chan] == NULL)      /*   chk chnl exists    */
                m_chn_init(mep, chan);
            mep->type = type;               /*   & begin new event  */
            mep->chan = chan;
            datreq = datbyts[(type>>4) & 0x7];
        }
        c = getc(mfp);
        MTrkrem--;

datcpy: mep->dat1 = c;                        /* sav the required data */
        if (datreq == 2) {
            mep->dat2 = getc(mfp);
            MTrkrem--;
        }
        if (mep->type > NOTEON_TYPE) {        /* if control or syscom  */
            m_chanmsg(mep);                   /*   handle from here    */
            goto nxtim;
        }
        nxtdeltim();
        return(3);                            /* else it's note_on/off */

nxtim:  nxtdeltim();
        if (O.FMidiin && kcounter >= FMidiNxtk)
            goto nxtevt;
        return(0);

Trkend: printf("end of midi track in '%s'\n", O.FMidiname);
        printf("%d forced decays, %d extra noteoffs\n", Mforcdecs, Mxtroffs);
        MTrkend = 1;
        O.FMidiin = 0;
        if (O.ringbell && !O.termifend)  beep();
        return(0);
}

