// winmidi.cpp : Defines the entry point for the DLL application. // // needs to be linked with winmm.lib #include "stdafx.h" #include "Python.h" #include "mmsystem.h" #include "iostream.h" // global stuff: (don't know enough C++ to know how not to do this) static PyObject * winmidi_error; // interpreter and thread states: PyInterpreterState * winmidi_iState = NULL; PyThreadState * winmidi_tState = NULL; // I don't think you'd ever have 32 devices hooked up to one machine, // so I'm betting this is probably safe. const WINMIDI_HANDLECOUNT = 32; HMIDIIN inHandles[WINMIDI_HANDLECOUNT]; HMIDIOUT outHandles[WINMIDI_HANDLECOUNT]; PyObject *callbacks[WINMIDI_HANDLECOUNT]; /****[internal callback stuff]*********************************************/ void _ensureIState() { // does nothing unless winmidi_istate is null if (winmidi_iState == NULL) { // we just want to temporarily grab the threadstate... PyThreadState * savestate = PyThreadState_Swap(NULL); if (savestate ==NULL) { Py_FatalError("couldn't get iState because current tState is invalid."); } // ... so we can get the interpreter state: winmidi_iState = savestate->interp; // now that we have it, restore the old threadstate: PyThreadState_Swap(savestate); } } BOOL _ensureTState() // returns whether or not it created a new thread. { if (winmidi_tState == NULL) { if (winmidi_iState == NULL) { Py_FatalError("no iState. call _ensureIState first."); } winmidi_tState = PyThreadState_New (winmidi_iState); return TRUE; } else { return FALSE; } } void CALLBACK _midiCallback(HMIDIIN handle, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) { if (callbacks[dwInstance] != 0) { // set up BOOL created = _ensureTState(); PyThreadState * oldtstate = PyThreadState_Swap(NULL); PyEval_AcquireThread(winmidi_tState); // do it PyObject * cbargs = Py_BuildValue ("(i)", uMsg); PyObject * result = PyObject_CallObject (callbacks[dwInstance], cbargs); if (NULL==result) { PyErr_Print(); Py_FatalError("_midiCallback failed trying to invoke python callback."); } // cleanup PyEval_ReleaseThread(winmidi_tState); PyThreadState_Swap(oldtstate); } else { // do nothing I guess... } } /****[exported python functions]****************************************************/ ///////////////////////// // syntax: outGetNumDevs() // returns: Integer // // Returns the number of available MIDI output devices. static PyObject * winmidi_outGetNumDevs(PyObject *self, PyObject *args) { int numdevs; numdevs = midiOutGetNumDevs(); return Py_BuildValue("i", numdevs); } ///////////////////////// // syntax: inGetNumDevs() // returns: Integer // // Returns the number of available MIDI input devices. static PyObject * winmidi_inGetNumDevs(PyObject *self, PyObject *args) { int numdevs; numdevs = midiInGetNumDevs(); return Py_BuildValue("i", numdevs); } ///////////////////////// // syntax: inOpen(devnum, pycb) // returns: None // // Opens the specified MIDI input device and defines a callback. static PyObject * winmidi_inOpen(PyObject *self, PyObject *args) { unsigned int devnum; PyObject * pycb; HMIDIIN hMidiIn; MMRESULT result = 0; // if they don't pass an int and a callback, return null... if (!PyArg_ParseTuple(args, "iO", &devnum, &pycb)) { return NULL; } // make sure the callback is a valid callable object if (!PyCallable_Check (pycb)) { PyErr_SetString (winmidi_error, "second param must be a callable object"); return NULL; } // we're still here.. try to actually open it... // we pass devnum twice: once to open it, and once so windows will tell the // callback which dev the event is for... Py_BEGIN_ALLOW_THREADS; result = midiInOpen(&hMidiIn, devnum, (DWORD) _midiCallback, (DWORD) devnum, CALLBACK_FUNCTION); Py_END_ALLOW_THREADS; if (result != 0) { PyErr_SetString (winmidi_error, "midiInOpen() failed."); return NULL; } // save the handle for later... inHandles[devnum] = hMidiIn; callbacks[devnum] = pycb; Py_INCREF(pycb); midiInStart(hMidiIn); return Py_BuildValue(""); // None } ///////////////////////// // syntax: inClose(devnum) // returns: None // // Closes the specified MIDI input device. static PyObject * winmidi_inClose(PyObject *self, PyObject *args) { unsigned int devnum; // if they don't pass an int, return null... if (!PyArg_ParseTuple(args, "i", &devnum)) { return NULL; } midiInStop(inHandles[devnum]); midiInClose(inHandles[devnum]); //@TODO: error checking in case midiInClose failed... inHandles[devnum] = 0; return Py_BuildValue(""); // None } /****[method table]***********************************************************/ static PyMethodDef winmidi_methods[] = { // flags: 1 = variable number of args, 2 = has keywords // { "python_method", c_function, flags, "comments" } {"outGetNumDevs", winmidi_outGetNumDevs, 1, "Returns the number of available MIDI output devices." }, //{"outGetDevCaps", winmidi_outGetDevCaps, 1, "Returns the number of available MIDI output devices." }, //{"outOpen", winmidi_outOpen, 1, "Returns the number of available MIDI output devices." }, //{"outShortMsg", winmidi_outShortMsg, 1, "Returns the number of available MIDI output devices." }, //{"outLongMsg", winmidi_outLongMsg, 1, "Returns the number of available MIDI output devices." }, {"inGetNumDevs", winmidi_inGetNumDevs, 1, "Returns the number of available MIDI input devices." }, //{"inGetDevCaps", winmidi_inGetDevCaps, 1, "Returns a tuple." }, {"inOpen", winmidi_inOpen, 1, "Opens the specified MIDI input device. expects (devnum, callback)" }, //{"inCallBack", winmidi_inCallBack, 1, "Returns the number of available MIDI input devices." }, {"inClose", winmidi_inClose, 1, "Closes the specified MIDI input device. expects (devnum)" }, {NULL, NULL} }; /****[initialization]*********************************************************/ extern "C" __declspec (dllexport) void initwinmidi() { PyObject *module, *dict; module = Py_InitModule("winmidi", winmidi_methods); dict = PyModule_GetDict(module); for (int i=0; i < WINMIDI_HANDLECOUNT; i++) { inHandles[i] = 0; outHandles[i] = 0; callbacks[i] = 0; } winmidi_error = PyString_FromString("winmidi error"); // ensure we have the interpreter state to work with: _ensureIState(); }