All pastes #935797 Raw Edit

Anonymous

public text v1 · immutable
·published 2008-03-09 22:31 UTC
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);

}