/* MidiIn -- C/python module for parsing MIDI input Copyright (C) 1999-2000 Eric S. Tiedemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Eric S. Tiedemann */ #include static const int EOX = 0xf7; static const int SEX = 0xf0; struct MidiIn { int status; int status3; int thirdp; int second; int (*cb)(void *, const char *, int, int, int, int); void *cb_data; int (*sysex_cb)(void *, int, const unsigned char *); void *sysex_cb_data; void (*nomem_cb)(void *); void *nomem_cb_data; unsigned char *sexbuf; int sexbufsz; int sexbufn; }; struct MidiIn * MidiIn_New() { struct MidiIn *m = (struct MidiIn *) calloc(1, sizeof(struct MidiIn)); return m; } static int channel(int b) { return (b & 0xF) + 1; } /* note: nargs includes channel number */ static struct statustab_s {int status; char *name; int nargs;} statustab[] = { 0x80, "note-off", 3, 0x90, "note-on", 3, 0xa0, "poly-key-pressure", 3, 0xb0, "control-change", 3, 0xc0, "program-change", 2, 0xd0, "channel-pressure", 2, 0xe0, "pitch-bend", 2, /* note: the 2 bytes are combined */ 0xf0, "system-exclusive", 0, /* handled separately */ 0xf1, "MTC-quarter-frame", 1, 0xf2, "song-position-pointer", 1, /* note: the 2 bytes are combined */ 0xf3, "song-select", 1, 0xf6, "tune-request", 0, 0xf7, "end-of-exclusive", 0, /* handled separately */ 0xf8, "timing-clock", 0, 0xfa, "start", 0, 0xfb, "continue", 0, 0xfc, "stop", 0, 0xfe, "active-sensing", 0, 0xff, "system-reset", 0, -1, "???", 0, }; static struct statustab_s * find_status_entry(int s) { struct statustab_s *st = statustab; s = s < 0xF0 ? (s & 0xF0) : s; while (st->status != -1 && st->status != s) st++; return st; } static const char * status2name(int s) { return find_status_entry(s)->name; } static int status2nargs(int s) { return find_status_entry(s)->nargs; } static const char * channel_mode_message(int i) { switch (i) { case 120: return("all-sound-off"); case 121: return("reset-all-controllers"); case 122: return("local-control"); case 123: return("all-notes-off"); case 124: return("omni-off"); case 125: return("omni-on"); case 126: return("mono-on"); case 127: return("poly-on"); default: return "???"; } } static int channel_mode_nargs(int v) { if (v == 122 || v == 126) return 2; else return 1; } static int channel_mode_normalize(int v, int b) { if (v == 122) return (b ? 1 : 0); else if (v == 126) { if (b > 16) return 0; else return b; } else return 0; } static int invoke_callback(struct MidiIn *m, int e, int v, int w) { /*fprintf(stderr, "%02x %d %d\n", e, v, w);*/ if (m->cb) { if ((e & 0xF0) == 0xB0) { /* handle special values of v for control change*/ if (v > 119 && v < 128) return m->cb(m->cb_data, channel_mode_message(v), channel_mode_nargs(v), channel(e), channel_mode_normalize(v, w), 0); else { return m->cb(m->cb_data, status2name(e), status2nargs(e), channel(e), v, w); } } else if (e == SEX) ; /* shouldn't be here actually */ else if ((e & 0xF0) == 0xE0) return m->cb(m->cb_data, status2name(e), 2, channel(e), v + w * 128, 0); else if (e == 0xF2) return m->cb(m->cb_data, status2name(e), 1, v + w * 128, 0, 0); else if (e < 0xF0) return m->cb(m->cb_data, status2name(e), status2nargs(e), channel(e), v, w); else return m->cb(m->cb_data, status2name(e), status2nargs(e), v, w, 0); } } static int real_time(int b) { switch (b) { case 0xf8: case 0xfa: case 0xfb: case 0xfc: case 0xfe: case 0xff: return 1; default: return 0; } } static int status_byte(int b) { return b & 0x80; } #define SEXBUF_INITIAL_SIZE 1024 static int sysexb(struct MidiIn *m, int b) { /*fprintf(stderr, "in sysexb %d:%d\n", m->sexbufn, m->sexbufsz);*/ if (!m->sexbuf) { if (!(m->sexbuf = (unsigned char *) malloc(SEXBUF_INITIAL_SIZE))) { if (m->nomem_cb) m->nomem_cb(m->nomem_cb_data); return 0; } else { m->sexbufsz = SEXBUF_INITIAL_SIZE; m->sexbufn = 0; } } if (m->sexbufn == m->sexbufsz) { if (!(m->sexbuf = (unsigned char *) malloc(m->sexbufsz * 2))) { free(m->sexbuf); m->sexbufn = m->sexbufsz = 0; m->sexbuf = 0; if (m->nomem_cb) m->nomem_cb(m->nomem_cb_data); return 0; } else { m->sexbufsz *= 2; } } /*fprintf(stderr, "about to append\n");*/ m->sexbuf[m->sexbufn++] = b; return 1; } static int sysexend(struct MidiIn *m) { int rc = 1; /*fprintf(stderr, "in sysexend %d:%d\n", m->sexbufn, m->sexbufsz);*/ if (m->sexbuf) { if (m->sysex_cb) rc = m->sysex_cb(m->sysex_cb_data, m->sexbufn, m->sexbuf); free(m->sexbuf); m->sexbufn = m->sexbufsz = 0; m->sexbuf = 0; } else /* empty sysex */ { unsigned char buf[1]; if (m->sysex_cb) rc = m->sysex_cb(m->sysex_cb_data, 0, buf); } return rc; } int MidiIn_InByte(struct MidiIn *m, int b) { /*printf("%d\n", b);*/ int rc = 1; if (status_byte(b)) { if (real_time(b)) rc = invoke_callback(m, b, 0, 0); else if (b == EOX) { if (m->status == SEX) { rc = sysexend(m); m->status = 0; } else /* complain? */ ; } else { if (m->status == SEX) rc = sysexend(m); m->status = b; if (b == 0xF6) { if (!invoke_callback(m, b, 0, 0)) rc = 0; } } } else /* data byte */ { if (m->status == SEX) rc = sysexb(m, b); else if (m->thirdp) { if (m->status3) { rc = invoke_callback(m, m->status3, m->second, b); m->status3 = 0; } else rc = invoke_callback(m, m->status, m->second, b); m->thirdp = 0; } else { if (m->status) { if (m->status < 0xC0) { m->thirdp = 1; m->second = b; } else { if (m->status < 0xE0) rc = invoke_callback(m, m->status, b, 0); else { if (m->status < 0xF0) { m->thirdp = 1; m->second = b; } else { if (m->status == 0xF2) { m->thirdp = 1; m->second = b; m->status3 = m->status; m->status = 0; } else { if (m->status == 0xF3 || m->status == 0xF1) { rc = invoke_callback(m, m->status, b, 0); m->status = 0; } else { /* ignore strange status byte */ m->status = 0; } } } } } } else ; /* ignore data byte with no status on record */ } } return rc; } int MidiIn_InBytes(struct MidiIn *m, unsigned char *bs, size_t n) { size_t i; for (i = 0; i < n; i++) if (!MidiIn_InByte(m, bs[i])) return 0; return 1; } #define PYTHON #ifdef PYTHON #include typedef struct { PyObject_HEAD struct MidiIn *o; } PyMidiIn; #define PyMidiIn_Check(o) ((o)->ob_type == &PyMidiIn_Type) static PyObject * PyMidiIn_inbytes(PyObject *self, PyObject *args) { unsigned char *s; int n; if (!PyArg_Parse(args, "s#;MIDI._In.inbytes", &s, &n)) return NULL; if (!MidiIn_InBytes(((PyMidiIn *) self)->o, s, n)) return NULL; Py_INCREF(Py_None); return Py_None; } static PyObject * PyMidiIn_read(PyObject *self, PyObject *args) { PyObject *f; FILE *fp; int errp = 0; size_t n; char buf[1]; if (!PyArg_Parse(args, "O;MIDI._In.read", &f)) return NULL; if (!PyFile_Check(f)) { PyErr_SetString(PyExc_TypeError, "argument must be a file"); return 0; } Py_INCREF(f); fp = PyFile_AsFile(f); while (1) { Py_BEGIN_ALLOW_THREADS n = fread(buf, 1, sizeof(buf), fp); Py_END_ALLOW_THREADS if (n == 0) break; if (!MidiIn_InBytes(((PyMidiIn *) self)->o, buf, n)) { errp = 1; break; } } if (errp) { Py_DECREF(f); return 0; } if (ferror(fp)) { Py_DECREF(f); PyErr_SetFromErrno(PyExc_IOError); return 0; } Py_DECREF(f); Py_INCREF(Py_None); return Py_None; } static PyObject * PyMidiIn_settab(PyObject *self, PyObject *args) { unsigned char *s; int n; PyObject *o; if (!PyArg_Parse(args, "O;MIDI._In.settab", &o)) return NULL; ((PyMidiIn *) self)->o->cb_data = o; Py_INCREF(Py_None); return Py_None; } static struct PyMethodDef PyMidiIn_methods[] = { {"inbytes", PyMidiIn_inbytes}, {"read", PyMidiIn_read}, {"settab", PyMidiIn_settab}, {NULL, NULL} }; static PyObject * PyMidiIn_getattr(PyObject *self, char *name) { if (strcmp(name, "__methods__") == 0) return Py_BuildValue("(s)", "inbytes"); else return Py_FindMethod(PyMidiIn_methods, self, name); } static void PyMidiIn_del(PyMidiIn *o) { Py_XDECREF((PyObject *) (o->o->cb_data)); Py_XDECREF((PyObject *) (o->o->sysex_cb_data)); Py_XDECREF((PyObject *) (o->o->nomem_cb_data)); return; } static PyTypeObject PyMidiIn_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, "MidiIn", sizeof(PyMidiIn), 0, (destructor) PyMidiIn_del, 0, PyMidiIn_getattr, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static PyObject * msgargs2tuple(const char *s, int nargs, int c, int v, int w) { switch (nargs) { case 0: return Py_BuildValue("(s)", s); break; case 1: return Py_BuildValue("(si)", s, c); break; case 2: return Py_BuildValue("(sii)", s, c, v); break; case 3: return Py_BuildValue("(siii)", s, c, v, w); break; default: return 0; } } static int callcbx(void *d, const char *s, int nargs, int c, int v, int w) { PyObject *p = (PyObject *) d, *res = 0, *args = 0; if (!(args = msgargs2tuple(s, nargs, c, v, w))) return 0; res = PyObject_CallObject(p, args); Py_DECREF(args); if (!res) return 0; else { Py_DECREF(res); return 1; } } static int callcb(void *d, const char *s, int nargs, int c, int v, int w) { int rc; rc = callcbx(d, s, nargs, c, v, w); return rc; } static int callcb1x(void *d, const char *s, int nargs, int c, int v, int w) { PyObject *tab = (PyObject *) d, *res = 0, *args = 0, *tab1 = 0; int rc = 1; if (!tab || PyObject_Not(tab)) return 1; if (!(args = msgargs2tuple(s, nargs, c, v, w))) return 0; /* all further returns are by goto to callout or out */ if (!PyDict_Check(tab)) { res = PyObject_CallObject(tab, args); goto callout; } else { int iargs[3]; int i, j; PyObject *tab0; tab1 = PyDict_GetItemString(tab, (char *) s); if (!tab1) { PyErr_Clear(); goto out; } /* Tear down the callback table structure based on number of args received. */ iargs[0] = c; iargs[1] = v; iargs[2] = w; for (i = 0; PyList_Check(tab1); i++) { if (i == nargs) { /* mmm..magic numbers :9 */ char buf[100]; sprintf(buf, "insufficient arguments (%d) for message type %.50s", nargs, s); PyErr_SetString(PyExc_RuntimeError, buf); rc = 0; goto out; } j = iargs[i]; if (j < 0 || j >= PyList_Size(tab1)) { /* mmm..magic numbers :9 */ char buf[100]; sprintf(buf, "invalid value (%d) for argument number %d of %.50s", j, i+1, s); PyErr_SetString(PyExc_RuntimeError, buf); rc = 0; goto out; } tab0 = PyList_GetItem(tab1, j); Py_DECREF(tab1); tab1 = tab0; } if (PyObject_IsTrue(tab1)) { res = PyObject_CallObject(tab1, args); goto callout; } else { goto out; } } callout: if (!res) rc = 0; else Py_DECREF(res); out: Py_XDECREF(args); Py_XDECREF(tab1); return rc; } static int callcb1(void *d, const char *s, int nargs, int c, int v, int w) { int rc; rc = callcb1x(d, s, nargs, c, v, w); return rc; } static int callscbx(void *d, int n, const unsigned char *bs) { PyObject *p = (PyObject *) d; /*fprintf(stderr, "about to call scb with %d bytes\n", n);*/ if (p) { PyObject *res = PyObject_CallFunction(p, "s#", bs, n); if (res) { Py_DECREF(res); return 1; } else return 0; } return 1; } static int callscb(void *d, int n, const unsigned char *bs) { int rc; rc = callscbx(d, n, bs); return rc; } static void nomemcb(void *v) { PyErr_SetString(PyExc_MemoryError, "sysex buffer out of memory"); } static PyObject * PyMidiIn_New(PyObject *self, PyObject *args) { PyObject *cb, *scb = 0; PyMidiIn *s; struct MidiIn *m; if (!PyArg_ParseTuple(args, "O|O;_MidiIn.MidiIn", &cb, &scb)) return NULL; if (!PyCallable_Check(cb)) { PyErr_SetString(PyExc_TypeError, "first arg must be callable"); return 0; } if (scb && !PyCallable_Check(scb)) { PyErr_SetString(PyExc_TypeError, "second arg must be callable"); return 0; } m = MidiIn_New(); if (!m) { PyErr_SetString(PyExc_MemoryError, "MIDI._In out of memory"); return 0; } s = PyObject_NEW(PyMidiIn, &PyMidiIn_Type); if (!s) return 0; s->o = m; m->cb = callcb; m->cb_data = cb; m->sysex_cb = callscb; m->sysex_cb_data = scb; m->nomem_cb = nomemcb; Py_INCREF(cb); if (scb) Py_INCREF(scb); return (PyObject *) s; } static PyObject * PyMidiIn_New1(PyObject *self, PyObject *args) { PyObject *tab, *scb = 0; PyMidiIn *s; struct MidiIn *m; if (!PyArg_ParseTuple(args, "O|O;MIDI._In", &tab, &scb)) return NULL; if (scb && !PyCallable_Check(scb)) { PyErr_SetString(PyExc_TypeError, "second arg must be callable"); return 0; } m = MidiIn_New(); if (!m) /* should set memory exception */ return 0; s = PyObject_NEW(PyMidiIn, &PyMidiIn_Type); if (!s) return 0; s->o = m; m->cb = callcb1; m->cb_data = tab; m->sysex_cb = callscb; m->sysex_cb_data = scb; m->nomem_cb = nomemcb; Py_INCREF(tab); if (scb) Py_INCREF(scb); return (PyObject *) s; } static PyMethodDef midiinMethods[] = { {"MidiIn", PyMidiIn_New, METH_VARARGS}, {"new1", PyMidiIn_New1, METH_VARARGS}, {"settab", PyMidiIn_settab, METH_VARARGS}, {0, 0, 0} }; DL_EXPORT(void) init_In() { Py_InitModule("_In", midiinMethods); } #endif /*#define TEST*/ #ifdef TEST #include int foo(void *data, const char *s, int nargs, int c, int u, int v) { printf("(%s %d %d %d %d)\n", s, nargs, c, u, v); return 1; } int main() { int c; int n = 0; struct MidiIn *m = MidiIn_New(); if (!m) return 1; m->cb = foo; while (n++ < 1000) MidiIn_InByte(m, getchar()); return 0; } #endif