#include "csoundxomodule.h" #include "message.h" #include "CircularBuffer.h" //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // // Threaded Version // // //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /* [Accessing Samples] Access Samples sample format - PUBLIC int csoundGetSampleFormat(CSOUND *); sample size - PUBLIC int csoundGetSampleSize(CSOUND *); [Channels] sending / receiving [Tables] PUBLIC int csoundTableLength(CSOUND *, int table); */ typedef struct { PyObject_HEAD PyThreadState *tstate; CSOUND *csound; PyObject *consoleCallback; PyObject *outValueCallback; PyObject *temp; int messageBufferIndex; char messageBuffer[MESSAGE_BUFFER_SIZE+2]; void *threadPtr; int endThread; int paused; void *queueLock; void *pauseLock; void *flushLock; CircularBuffer *circularBuffer; PyInterpreterState *interp; char **arguments; int argLength; Message *firstMessage; } CsoundXOThreadObject; // callbacks void csoundMessageHandler(CSOUND *csound, int attr, const char *format, va_list valist); int midiInRead(CSOUND *csound, void *userData, unsigned char *buf, int nBytes); void csoundOutValueHandler(CSOUND *csound, const char *channelName, MYFLT value); int midiInRead(CSOUND *csound, void *userData, unsigned char *buf, int nBytes) { CsoundXOThreadObject *self; CircularBuffer *buffer; int bytesRemaining; unsigned char *ptr; int bytesRead; bytesRead = 0; self = (CsoundXOThreadObject*)csoundGetHostData(csound); buffer = self->circularBuffer; bytesRemaining = nBytes; ptr = buf; while( CircularBuffer_read(buffer, ptr) ) { if( --bytesRemaining <= 0 ) break; // printf("read %d\n", (unsigned char) *ptr); bytesRead++; ptr++; } return bytesRead; } void QueueMessage(CsoundXOThreadObject *self, Message *msg) { // hold up csoundLockMutex(self->queueLock); Message *last = NULL; Message *m = self->firstMessage; // find the end for( ; m != NULL; ) { last = m; m = m->next; } // append if(last == NULL) { self->firstMessage = msg; } else { last->next = msg; } // mark queue as non-empty csoundWaitThreadLock(self->flushLock, (size_t) 0); // wake up from pause csoundNotifyThreadLock(self->pauseLock); // keep on truckin' csoundUnlockMutex(self->queueLock); } void thread_func(void *data) { CsoundXOThreadObject *self = (CsoundXOThreadObject *)data; PyThreadState *tstate = PyThreadState_New(PyInterpreterState_New()); //self->interp); PyEval_AcquireThread(tstate); self->tstate = tstate; // SETUP int done = 0; int retval; // PERFORMANCE do { if(self->endThread) break; while( 1 ) { csoundLockMutex(self->queueLock); if( self->firstMessage == NULL ) { csoundUnlockMutex(self->queueLock); break; } do { Message *msg; msg = self->firstMessage; if(msg == NULL) break; self->firstMessage = msg->next; switch( msg->type ) { case kStop: // printf("kStop\n"); done = 1; break; case kPause: // printf("kPause\n"); self->paused = 1; break; case kRewind: // printf("kRewind\n"); // blocking function Py_BEGIN_ALLOW_THREADS csoundRewindScore(self->csound); Py_END_ALLOW_THREADS break; case kPlay: // printf("kPlay\n"); self->paused = 0; break; case kLineEvent: // printf("kLineEvent\n"); csoundInputMessage(self->csound, msg->lineEvent); break; case kSeekToScoreTime: // printf("kSeekToScoreTime %f\n", (MYFLT)msg->scoreTime); csoundSetScoreOffsetSeconds(self->csound, (MYFLT)msg->scoreTime); break; } DestroyMessage(msg); } while(!done); csoundUnlockMutex(self->queueLock); if(self->paused) { // does not block csoundWaitThreadLock(self->pauseLock, (size_t) 0); } // // mark queue as empty csoundNotifyThreadLock(self->flushLock); if(!self->paused) { break; } // are we done? if(done) goto EndOfPerf; // we are paused // the following function blocks - so we surround it // with the macro Py_BEGIN_ALLOW_THREADS csoundWaitThreadLockNoTimeout(self->pauseLock); Py_END_ALLOW_THREADS csoundNotifyThreadLock(self->pauseLock); } Py_BEGIN_ALLOW_THREADS retval = csoundPerformKsmps(self->csound); Py_END_ALLOW_THREADS } while(!retval && !done); EndOfPerf: // Release the thread. No Python API allowed beyond this point. PyEval_ReleaseThread(tstate); // You can either delete the thread state, or save it // until you need it the next time. PyThreadState_Delete(tstate); csoundDestroy(self->csound); pthread_exit(NULL); } void CsoundXOThread_freeArguments(CsoundXOThreadObject *self) { if( self->arguments != NULL ) { free(self->arguments[0]); free(self->arguments); } } // initialize the module static int CsoundXOThread_init(CsoundXOThreadObject *self, PyObject *args, PyObject *kwds) { self->paused = 1; self->arguments = NULL; self->argLength = 0; self->messageBufferIndex = 0; self->endThread = 0; self->csound = csoundCreate((void*)self); self->circularBuffer = CircularBuffer_init(); csoundPreCompile(self->csound); csoundSetExternalMidiReadCallback(self->csound, midiInRead); self->queueLock = csoundCreateMutex(0); self->pauseLock = csoundCreateThreadLock(); if (!self->pauseLock) { return 0; } self->flushLock = csoundCreateThreadLock(); if (!self->flushLock) { return 0; } self->firstMessage = NULL; QueueMessage( self, CreatePauseMessage() ); // THIS IS NECESSARY FOR THE CALLBACKS! self->interp = PyThreadState_GET()->interp; // self->temp = temp; self->threadPtr = csoundCreateThread( (void *)&thread_func, (void*)self ); return 0; } static int CsoundXOThread_dealloc(CsoundXOThreadObject *self) { CsoundXOThread_freeArguments(self); self->endThread = 1; // make sure we unpause (if paused) csoundNotifyThreadLock(self->pauseLock); // about to enter a blocking function Py_BEGIN_ALLOW_THREADS csoundJoinThread(self->threadPtr); Py_END_ALLOW_THREADS CircularBuffer_destroy(self->circularBuffer); return 0; } static PyObject *CsoundXOThread_stop(CsoundXOThreadObject *self) { QueueMessage( self, CreateStopMessage() ); return Py_None; } static PyObject *CsoundXOThread_pause(CsoundXOThreadObject *self) { QueueMessage( self, CreatePauseMessage() ); return Py_None; } static PyObject *CsoundXOThread_play(CsoundXOThreadObject *self) { QueueMessage( self, CreatePlayMessage() ); return Py_None; } static PyObject *CsoundXOThread_rewind(CsoundXOThreadObject *self) { QueueMessage( self, CreateRewindMessage() ); return Py_None; } static PyObject *CsoundXOThread_scoreEvent(CsoundXOThreadObject *self, PyObject *args) { char *score; if(PyArg_ParseTuple(args, "s", &score)) { QueueMessage( self, CreateLineEventMessage(score) ); return Py_None; } return NULL; } static PyObject *CsoundXOThread_seek(CsoundXOThreadObject *self, PyObject *args) { //PyObject *arg; float time; // printf("CsoundXOThread_seek\n"); if(PyArg_ParseTuple(args, "f", &time)) { //printf("time=%f\n", time); QueueMessage( self, CreateSeekToTimeMessage(time) ); return Py_None; } return NULL; } static PyObject *CsoundXOThread_compile(CsoundXOThreadObject *self, PyObject *args) { PyObject *list; if(PyArg_ParseTuple(args, "O", &list)) { PyObject *listItem; int i; int len = PyList_Size(list); self->argLength = len + 1; CsoundXOThread_freeArguments(self); self->arguments = (char**)malloc(sizeof(char*) * self->argLength); char *cs = "csound"; self->arguments[0] = malloc(sizeof(char) * strlen(cs) + 1); strcpy(self->arguments[0], cs); for(i = 0; i < len; i++) { listItem = PyList_GetItem(list, i); if(listItem) { // TODO: should probably test if its a string self->arguments[i+1] = PyString_AsString(listItem); } } int r; r = csoundCompile(self->csound, self->argLength, self->arguments); return Py_BuildValue("i", r); } return NULL; } void csoundOutValueHandler(CSOUND *csound, const char *channelName, MYFLT value) { CsoundXOThreadObject *self; self = (CsoundXOThreadObject*)csoundGetHostData(csound); PyObject *arglist; //printf("csoundOutValueHandler %s %f\n", channelName, value); // The following test checks to see which thread // this function is being called from int locked = 0; PyThreadState *tstate2; if( self->paused == 0 ) { //printf("Acquire\n"); tstate2 = PyThreadState_New(self->interp); PyEval_AcquireThread(tstate2); locked = 1; } // send the buffer arglist = Py_BuildValue("(sf)", channelName, value); PyEval_CallObject(self->outValueCallback, arglist); Py_DECREF(arglist); if(locked) { // Release the thread. No Python API allowed beyond this point. PyEval_ReleaseThread(tstate2); // You can either delete the thread state, or save it // until you need it the next time. PyThreadState_Delete(tstate2); } } void csoundMessageHandler(CSOUND *csound, int attr, const char *format, va_list valist) { CsoundXOThreadObject *self; PyObject *arglist; PyObject *result; self = (CsoundXOThreadObject*)csoundGetHostData(csound); // The following test checks to see which thread // this function is being called from int locked = 0; PyThreadState *tstate2; if( self->paused == 0 ) { //printf("Acquire\n"); tstate2 = PyThreadState_New(self->interp); PyEval_AcquireThread(tstate2); locked = 1; } // The following correctly formats // the output of the message buffer char messageData[256]; vsnprintf(messageData, 256, format, valist); va_end(valist); if( strchr( messageData, '\n' ) ) { // see if we have anything in our buffer... if( self->messageBufferIndex > 0 ) { // just go ahead and output what's there - flush it // term string self->messageBuffer[ self->messageBufferIndex ] = '\n'; self->messageBuffer[ self->messageBufferIndex+1 ] = '\0'; // send the buffer arglist = Py_BuildValue("(s)", self->messageBuffer); result = PyEval_CallObject(self->consoleCallback, arglist); Py_DECREF(arglist); } // send messageData arglist = Py_BuildValue("(s)", messageData); result = PyEval_CallObject(self->consoleCallback, arglist); Py_DECREF(arglist); self->messageBufferIndex = 0; } else { char *dest, *source; int remainingCharsToCopy; int msgDataLength; msgDataLength = strlen(messageData); remainingCharsToCopy = msgDataLength; source = messageData; while( remainingCharsToCopy > 0 ) { int charsLeft = MESSAGE_BUFFER_SIZE - self->messageBufferIndex; dest = self->messageBuffer + self->messageBufferIndex; if( charsLeft >= remainingCharsToCopy ) { // we have enough room to copy the entire msg data strncpy( dest, source, remainingCharsToCopy ); self->messageBufferIndex += remainingCharsToCopy; remainingCharsToCopy = 0; } else { // we do not have enough room, so fit what we can strncpy( dest, source, charsLeft ); self->messageBuffer[ MESSAGE_BUFFER_SIZE ] = '\0'; // term string // update counts and pointers source += charsLeft; self->messageBufferIndex = 0; remainingCharsToCopy -= charsLeft; // and output self->messageBuffer arglist = Py_BuildValue("(s)", self->messageBuffer); result = PyEval_CallObject(self->consoleCallback, arglist); Py_DECREF(arglist); } } } if(locked) { // Release the thread. No Python API allowed beyond this point. PyEval_ReleaseThread(tstate2); // You can either delete the thread state, or save it // until you need it the next time. PyThreadState_Delete(tstate2); } } static PyObject *CsoundXOThread_setMessageCallback(CsoundXOThreadObject *self, PyObject *args) { PyObject *result = NULL; PyObject *temp; if (PyArg_ParseTuple(args, "O:setMessageCallback", &temp)) { if (!PyCallable_Check(temp)) { PyErr_SetString(PyExc_TypeError, "parameter must be callable"); return NULL; } Py_XINCREF(temp); // Add a reference to new callback Py_XDECREF(self->consoleCallback); // Dispose of previous callback self->consoleCallback = temp; // Remember new callback csoundSetMessageCallback(self->csound, csoundMessageHandler); /* Boilerplate to return "None" */ Py_INCREF(Py_None); result = Py_None; } return result; } static PyObject *CsoundXOThread_setOutValueCallback(CsoundXOThreadObject *self, PyObject *args) { PyObject *result = NULL; PyObject *temp; if (PyArg_ParseTuple(args, "O:setOutValueCallback", &temp)) { if (!PyCallable_Check(temp)) { PyErr_SetString(PyExc_TypeError, "parameter must be callable"); return NULL; } Py_XINCREF(temp); // Add a reference to new callback Py_XDECREF(self->outValueCallback); // Dispose of previous callback self->outValueCallback = temp; // Remember new callback //printf("CsoundXOThread_setOutValueCallback\n"); csoundSetOutputValueCallback(self->csound, csoundOutValueHandler); /* Boilerplate to return "None" */ Py_INCREF(Py_None); result = Py_None; } return result; } static PyObject *CsoundXOThread_sampleRate(CsoundXOThreadObject *self) { float result = csoundGetSr(self->csound); return Py_BuildValue("f", result); } static PyObject *CsoundXOThread_controlRate(CsoundXOThreadObject *self) { float result = csoundGetKr(self->csound); return Py_BuildValue("f", result); } static PyObject *CsoundXOThread_ksmps(CsoundXOThreadObject *self) { int result = csoundGetKsmps(self->csound); return Py_BuildValue("i", result); } static PyObject *CsoundXOThread_numChannels(CsoundXOThreadObject *self) { int result = csoundGetNchnls(self->csound); return Py_BuildValue("i", result); } static PyObject *CsoundXOThread_scoreTime(CsoundXOThreadObject *self) { double result = csoundGetScoreTime(self->csound); return Py_BuildValue("f", result); } static PyObject *CsoundXOThread_version(CsoundXOThreadObject *self) { int result = csoundGetVersion(); return Py_BuildValue("i", result); } static PyObject *CsoundXOThread_apiVersion(CsoundXOThreadObject *self) { int result = csoundGetAPIVersion(); return Py_BuildValue("i", result); } static PyObject *CsoundXOThread_messageLevel(CsoundXOThreadObject *self) { int result = csoundGetMessageLevel(self->csound); return Py_BuildValue("i", result); } static PyObject *CsoundXOThread_setMessageLevel(CsoundXOThreadObject *self, PyObject *args) { long msgLevel; if(PyArg_ParseTuple(args, "l", &msgLevel)) { //long messageLevel = PyInt_AsLong(msgLevel); csoundSetMessageLevel(self->csound, msgLevel); } return Py_None; } static PyObject *CsoundXOThread_midiEvent(CsoundXOThreadObject *self, PyObject *args) { PyObject *list; if(PyArg_ParseTuple(args, "O", &list)) { PyObject *listItem; int i; int len = PyList_Size(list); for(i = 0; i < len; i++) { listItem = PyList_GetItem(list, i); if(listItem) { unsigned char val = (unsigned char)PyInt_AsLong(listItem); // printf("append %d\n", val); CircularBuffer_append(self->circularBuffer, val); } } } return Py_None; } static PyObject *CsoundXOThread_opcodes(CsoundXOThreadObject *self) { opcodeListEntry *list; int num = csoundNewOpcodeList(self->csound, &list); // TOOD: build a list printf("num=%d\n", num); int i; for(i = 0; i < num; i++) { opcodeListEntry *item = list + (sizeof(opcodeListEntry) * i); //item->opname, item->outypes, item->intypes); } printf("dispose\n"); csoundDisposeOpcodeList(self->csound, list); return Py_None; } // threaded version // public members static PyMemberDef CsoundXOThread_members[] = { {NULL} /* Sentinel */ }; static char CsoundXOThread_doc[] = "CsoundXOThread - Csound Thread python bindings."; static PyMethodDef CsoundXOThread_methods[] = { // Version Info {"version", (PyCFunction)CsoundXOThread_version, METH_NOARGS, "Returns the version number times 1000 (5.00.0 = 5000)."}, {"apiVersion", (PyCFunction)CsoundXOThread_apiVersion, METH_NOARGS, "Returns the API version number times 100 (1.00 = 100)."}, // Output Control {"messageLevel", (PyCFunction)CsoundXOThread_messageLevel, METH_VARARGS, "Returns the Csound message level (from 0 to 231)."}, {"setMessageLevel", (PyCFunction)CsoundXOThread_setMessageLevel, METH_VARARGS, "Sets the Csound message level (from 0 to 231)."}, // Performance Info {"sampleRate", (PyCFunction)CsoundXOThread_sampleRate, METH_NOARGS, "get sample rate of csound instance"}, {"controlRate", (PyCFunction)CsoundXOThread_controlRate, METH_NOARGS, "get control rate of csound instance"}, {"ksmps", (PyCFunction)CsoundXOThread_ksmps, METH_NOARGS, "get ksmps of csound instance"}, {"numChannels", (PyCFunction)CsoundXOThread_numChannels, METH_NOARGS, "Returns the number of audio output channels."}, {"scoreTime", (PyCFunction)CsoundXOThread_scoreTime, METH_NOARGS, "Returns the current score time in seconds since the beginning of performance."}, {"opcodes", (PyCFunction)CsoundXOThread_opcodes, METH_NOARGS, "Get a list of opcodes."}, // Performing {"compile", (PyCFunction)CsoundXOThread_compile, METH_VARARGS, "Compiles Csound input files (such as an orchestra and score) as directed by the supplied command-line arguments, but does not perform them. Returns a non-zero error code on failure."}, {"setMessageCallback", (PyCFunction)CsoundXOThread_setMessageCallback, METH_VARARGS, "set callback of csound instance"}, {"setOutValueCallback", (PyCFunction)CsoundXOThread_setOutValueCallback, METH_VARARGS, "set output value callback"}, {"stop", (PyCFunction)CsoundXOThread_stop, METH_NOARGS, "stop playback"}, {"pause", (PyCFunction)CsoundXOThread_pause, METH_NOARGS, "pause playback"}, {"rewind", (PyCFunction)CsoundXOThread_rewind, METH_NOARGS, "rewind playback"}, {"play", (PyCFunction)CsoundXOThread_play, METH_NOARGS, "play playback"}, {"scoreEvent", (PyCFunction)CsoundXOThread_scoreEvent, METH_VARARGS, "score event"}, {"seek", (PyCFunction)CsoundXOThread_seek, METH_VARARGS, "seek to time"}, {"midiEvent", (PyCFunction)CsoundXOThread_midiEvent, METH_VARARGS, "write MIDI Event"}, {NULL} }; static PyTypeObject CsoundXOThreadObjectType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "csoundxo.CsoundXOThread", /* tp_name */ sizeof(CsoundXOThreadObject), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)CsoundXOThread_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ CsoundXOThread_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ CsoundXOThread_methods, /* tp_methods */ CsoundXOThread_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)CsoundXOThread_init /* tp_init */ }; //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // Initialize // //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Initialize the module PyMODINIT_FUNC initcsoundxo(void) { PyObject* m; PyEval_InitThreads(); CsoundXOThreadObjectType.tp_new = PyType_GenericNew; if(PyType_Ready(&CsoundXOThreadObjectType) < 0) return; m = Py_InitModule3("csoundxo", NULL, "XXX"); if(m == NULL) return; Py_INCREF(&CsoundXOThreadObjectType); PyModule_AddObject(m, "CsoundXOThread", (PyObject *)&CsoundXOThreadObjectType); }