/* Copyright (c) 1997-1999 Miller Puckette and others. * For information on usage and redistribution, and for a DISCLAIMER OF ALL * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ /* Clock functions (which should move, but where?) and MIDI queueing */ #include "m_pd.h" #include "s_stuff.h" #include "m_imp.h" #ifdef UNIX #include #include #ifdef HAVE_BSTRING_H #include #endif #endif #ifdef MSW #include #include #include #include #endif #include #include #include typedef struct _midiqelem { double q_time; int q_portno; unsigned char q_onebyte; unsigned char q_byte1; unsigned char q_byte2; unsigned char q_byte3; } t_midiqelem; #define MIDIQSIZE 1024 t_midiqelem midi_outqueue[MIDIQSIZE]; int midi_outhead, midi_outtail; t_midiqelem midi_inqueue[MIDIQSIZE]; int midi_inhead, midi_intail; static double sys_midiinittime; /* this is our current estimate for at what "system" real time the current logical time's output should occur. */ static double sys_dactimeminusrealtime; /* same for input, should be schduler advance earlier. */ static double sys_adctimeminusrealtime; static double sys_newdactimeminusrealtime = -1e20; static double sys_newadctimeminusrealtime = -1e20; static double sys_whenupdate; void sys_initmidiqueue( void) { sys_midiinittime = clock_getlogicaltime(); sys_dactimeminusrealtime = sys_adctimeminusrealtime = 0; } /* this is called from the OS dependent code from time to time when we think we know the delay (outbuftime) in seconds, at which the last-output audio sample will go out the door. */ void sys_setmiditimediff(double inbuftime, double outbuftime) { double dactimeminusrealtime = .001 * clock_gettimesince(sys_midiinittime) - outbuftime - sys_getrealtime(); double adctimeminusrealtime = .001 * clock_gettimesince(sys_midiinittime) + inbuftime - sys_getrealtime(); if (dactimeminusrealtime > sys_newdactimeminusrealtime) sys_newdactimeminusrealtime = dactimeminusrealtime; if (adctimeminusrealtime > sys_newadctimeminusrealtime) sys_newadctimeminusrealtime = adctimeminusrealtime; if (sys_getrealtime() > sys_whenupdate) { sys_dactimeminusrealtime = sys_newdactimeminusrealtime; sys_adctimeminusrealtime = sys_newadctimeminusrealtime; sys_newdactimeminusrealtime = -1e20; sys_newadctimeminusrealtime = -1e20; sys_whenupdate = sys_getrealtime() + 1; } } /* return the logical time of the DAC sample we believe is currently going out, based on how much "system time" has elapsed since the last time sys_setmiditimediff got called. */ static double sys_getmidioutrealtime( void) { return (sys_getrealtime() + sys_dactimeminusrealtime); } static double sys_getmidiinrealtime( void) { return (sys_getrealtime() + sys_adctimeminusrealtime); } static void sys_putnext( void) { int portno = midi_outqueue[midi_outtail].q_portno; if (midi_outqueue[midi_outtail].q_onebyte) sys_putmidibyte(portno, midi_outqueue[midi_outtail].q_byte1); else sys_putmidimess(portno, midi_outqueue[midi_outtail].q_byte1, midi_outqueue[midi_outtail].q_byte2, midi_outqueue[midi_outtail].q_byte3); midi_outtail = (midi_outtail + 1 == MIDIQSIZE ? 0 : midi_outtail + 1); } /* #define TEST_DEJITTER */ void sys_pollmidioutqueue( void) { #ifdef TEST_DEJITTER static int db = 0; #endif double midirealtime = sys_getmidioutrealtime(); #ifdef TEST_DEJITTER if (midi_outhead == midi_outtail) db = 0; #endif while (midi_outhead != midi_outtail) { #ifdef TEST_DEJITTER if (!db) { post("out: del %f, midiRT %f logicaltime %f, RT %f dacminusRT %f", (midi_outqueue[midi_outtail].q_time - midirealtime), midirealtime, .001 * clock_gettimesince(sys_midiinittime), sys_getrealtime(), sys_dactimeminusrealtime); db = 1; } #endif if (midi_outqueue[midi_outtail].q_time <= midirealtime) sys_putnext(); else break; } } static void sys_queuemidimess(int portno, int onebyte, int a, int b, int c) { t_midiqelem *midiqelem; int newhead = midi_outhead +1; if (newhead == MIDIQSIZE) newhead = 0; /* if FIFO is full flush an element to make room */ if (newhead == midi_outtail) sys_putnext(); midi_outqueue[midi_outhead].q_portno = portno; midi_outqueue[midi_outhead].q_onebyte = onebyte; midi_outqueue[midi_outhead].q_byte1 = a; midi_outqueue[midi_outhead].q_byte2 = b; midi_outqueue[midi_outhead].q_byte3 = c; midi_outqueue[midi_outhead].q_time = .001 * clock_gettimesince(sys_midiinittime); midi_outhead = newhead; sys_pollmidioutqueue(); } #define MIDI_NOTEON 144 #define MIDI_POLYAFTERTOUCH 160 #define MIDI_CONTROLCHANGE 176 #define MIDI_PROGRAMCHANGE 192 #define MIDI_AFTERTOUCH 208 #define MIDI_PITCHBEND 224 void outmidi_noteon(int portno, int channel, int pitch, int velo) { if (pitch < 0) pitch = 0; else if (pitch > 127) pitch = 127; if (velo < 0) velo = 0; else if (velo > 127) velo = 127; sys_queuemidimess(portno, 0, MIDI_NOTEON + (channel & 0xf), pitch, velo); } void outmidi_controlchange(int portno, int channel, int ctl, int value) { if (ctl < 0) ctl = 0; else if (ctl > 127) ctl = 127; if (value < 0) value = 0; else if (value > 127) value = 127; sys_queuemidimess(portno, 0, MIDI_CONTROLCHANGE + (channel & 0xf), ctl, value); } void outmidi_programchange(int portno, int channel, int value) { if (value < 0) value = 0; else if (value > 127) value = 127; sys_queuemidimess(portno, 0, MIDI_PROGRAMCHANGE + (channel & 0xf), value, 0); } void outmidi_pitchbend(int portno, int channel, int value) { if (value < 0) value = 0; else if (value > 16383) value = 16383; sys_queuemidimess(portno, 0, MIDI_PITCHBEND + (channel & 0xf), (value & 127), ((value>>7) & 127)); } void outmidi_aftertouch(int portno, int channel, int value) { if (value < 0) value = 0; else if (value > 127) value = 127; sys_queuemidimess(portno, 0, MIDI_AFTERTOUCH + (channel & 0xf), value, 0); } void outmidi_polyaftertouch(int portno, int channel, int pitch, int value) { if (pitch < 0) pitch = 0; else if (pitch > 127) pitch = 127; if (value < 0) value = 0; else if (value > 127) value = 127; sys_queuemidimess(portno, 0, MIDI_POLYAFTERTOUCH + (channel & 0xf), pitch, value); } void outmidi_mclk(int portno) { sys_queuemidimess(portno, 1, 0xf8, 0,0); } /* ------------------------- MIDI input queue handling ------------------ */ typedef struct midiparser { int mp_status; int mp_sysex; int mp_gotbyte1; int mp_byte1; } t_midiparser; #define MIDINOTEOFF 0x80 #define MIDINOTEON 0x90 #define MIDIPOLYTOUCH 0xa0 #define MIDICONTROLCHANGE 0xb0 #define MIDIPROGRAMCHANGE 0xc0 #define MIDICHANNELTOUCH 0xd0 #define MIDIPITCHBEND 0xe0 /* functions in x_midi.c */ void inmidi_realtimein(int portno, int cmd); void inmidi_byte(int portno, int byte); void inmidi_sysex(int portno, int byte); void inmidi_noteon(int portno, int channel, int pitch, int velo); void inmidi_controlchange(int portno, int channel, int ctlnumber, int value); void inmidi_programchange(int portno, int channel, int value); void inmidi_pitchbend(int portno, int channel, int value); void inmidi_aftertouch(int portno, int channel, int value); void inmidi_polyaftertouch(int portno, int channel, int pitch, int value); static void sys_dispatchnextmidiin( void) { static t_midiparser parser[MAXMIDIINDEV], *parserp; int portno = midi_inqueue[midi_intail].q_portno, byte = midi_inqueue[midi_intail].q_byte1; if (!midi_inqueue[midi_intail].q_onebyte) bug("sys_dispatchnextmidiin"); if (portno < 0 || portno >= MAXMIDIINDEV) bug("sys_dispatchnextmidiin 2"); parserp = parser + portno; outlet_setstacklim(); if (byte >= 0xf8) inmidi_realtimein(portno, byte); else { inmidi_byte(portno, byte); if (byte < 0xf0) { if (byte & 0x80) { parserp->mp_status = byte; parserp->mp_gotbyte1 = 0; } else { int cmd = (parserp->mp_status & 0xf0); int chan = (parserp->mp_status & 0xf); int byte1 = parserp->mp_byte1, gotbyte1 = parserp->mp_gotbyte1; switch (cmd) { case MIDINOTEOFF: if (gotbyte1) inmidi_noteon(portno, chan, byte1, 0), parserp->mp_gotbyte1 = 0; else parserp->mp_byte1 = byte, parserp->mp_gotbyte1 = 1; break; case MIDINOTEON: if (gotbyte1) inmidi_noteon(portno, chan, byte1, byte), parserp->mp_gotbyte1 = 0; else parserp->mp_byte1 = byte, parserp->mp_gotbyte1 = 1; break; case MIDIPOLYTOUCH: if (gotbyte1) inmidi_polyaftertouch(portno, chan, byte1, byte), parserp->mp_gotbyte1 = 0; else parserp->mp_byte1 = byte, parserp->mp_gotbyte1 = 1; break; case MIDICONTROLCHANGE: if (gotbyte1) inmidi_controlchange(portno, chan, byte1, byte), parserp->mp_gotbyte1 = 0; else parserp->mp_byte1 = byte, parserp->mp_gotbyte1 = 1; break; case MIDIPROGRAMCHANGE: inmidi_programchange(portno, chan, byte); break; case MIDICHANNELTOUCH: inmidi_aftertouch(portno, chan, byte); break; case MIDIPITCHBEND: if (gotbyte1) inmidi_pitchbend(portno, chan, ((byte << 7) + byte1)), parserp->mp_gotbyte1 = 0; else parserp->mp_byte1 = byte, parserp->mp_gotbyte1 = 1; break; } } } } midi_intail = (midi_intail + 1 == MIDIQSIZE ? 0 : midi_intail + 1); } void sys_pollmidiinqueue( void) { #ifdef TEST_DEJITTER static int db = 0; #endif double logicaltime = .001 * clock_gettimesince(sys_midiinittime); #ifdef TEST_DEJITTER if (midi_inhead == midi_intail) db = 0; #endif while (midi_inhead != midi_intail) { #ifdef TEST_DEJITTER if (!db) { post("in del %f, logicaltime %f, RT %f adcminusRT %f", (midi_inqueue[midi_intail].q_time - logicaltime), logicaltime, sys_getrealtime(), sys_adctimeminusrealtime); db = 1; } #endif #if 0 if (midi_inqueue[midi_intail].q_time <= logicaltime - 0.007) post("late %f", 1000 * (logicaltime - midi_inqueue[midi_intail].q_time)); #endif if (midi_inqueue[midi_intail].q_time <= logicaltime) { #if 0 post("diff %f", 1000* (logicaltime - midi_inqueue[midi_intail].q_time)); #endif sys_dispatchnextmidiin(); } else break; } } /* this should be called from the system dependent MIDI code when a byte comes in, as a result of our calling sys_poll_midi. We stick it on a timetag queue and dispatch it at the appropriate logical time. */ void sys_midibytein(int portno, int byte) { static int warned = 0; t_midiqelem *midiqelem; int newhead = midi_inhead +1; if (newhead == MIDIQSIZE) newhead = 0; /* if FIFO is full flush an element to make room */ if (newhead == midi_intail) { if (!warned) { post("warning: MIDI timing FIFO overflowed"); warned = 1; } sys_dispatchnextmidiin(); } midi_inqueue[midi_inhead].q_portno = portno; midi_inqueue[midi_inhead].q_onebyte = 1; midi_inqueue[midi_inhead].q_byte1 = byte; midi_inqueue[midi_inhead].q_time = sys_getmidiinrealtime(); midi_inhead = newhead; sys_pollmidiinqueue(); } void sys_pollmidiqueue( void) { #if 0 static double lasttime; double newtime = sys_getrealtime(); if (newtime - lasttime > 0.007) post("delay %d", (int)(1000 * (newtime - lasttime))); lasttime = newtime; #endif sys_poll_midi(); /* OS dependent poll for MIDI input */ sys_pollmidioutqueue(); sys_pollmidiinqueue(); }