rendered paste body#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);
}