#include #include /* SDL error handling */ PyObject *SDLError; typedef struct { PyObject_HEAD int samplerate, channels; int active; PyObject *callback; } PyYSynthContext; void PyYSynthContext_Dealloc(PyYSynthContext *self) { puts("_ysynth: Dealloc context"); if (self->active) { SDL_CloseAudio(); self->active = 0; } Py_XDECREF(self->callback); return; } PyTypeObject PyYSynthContext_Type; PyTypeObject PyYSynthContext_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ysynth.ysynth context", /*tp_name*/ sizeof(PyYSynthContext), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)PyYSynthContext_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*/ }; PyDoc_STRVAR(hello_doc, "Print hello message from Python module"); /* SDL Audio callback */ void _sdl_audio_callback(void *user, Uint8 *stream, int len) { PyYSynthContext *ctx = (PyYSynthContext*)user; PyObject *channels, *chan, *py_sample, *left, *right, *r = NULL; PyGILState_STATE gstate; int i, j; int sample; int local_len = len / sizeof(Sint16) / 2; Sint16 *local_stream = (Sint16*)stream;; /* This function is called from SDL's own thread, so we need to * register it with the Python interpreter before calling any * Python functions. */ gstate = PyGILState_Ensure(); do { if (ctx->callback) { /* Build list of doubles */ channels = PyTuple_New(ctx->channels); if (!channels) break; /* Fill tuple with channel lists */ for (i = 0; i < ctx->channels; i++) { /* Allocate new channel list */ chan = PyList_New(local_len); if (!chan) { Py_DECREF(channels); break; } PyTuple_SET_ITEM(channels, i, chan); /* Fill channel with 0.0 floats */ for (j = 0; j < local_len; j++) { py_sample = PyFloat_FromDouble(0.0); if (!py_sample) break; PyList_SET_ITEM(chan, j, py_sample); } if (j != local_len) break; } if (i != ctx->channels) break; /* Finally execute the callback */ r = PyObject_CallObject(ctx->callback, channels); } } while (0); /* Print stacktrace if necessary */ if (!r) { if (PyErr_Occurred()) PyErr_Print(); } else { left = PyTuple_GetItem(channels, 0); right = PyTuple_GetItem(channels, 1); /* Make sure we won't exceed list length.. the callee shouldn't change * the list length though */ local_len = local_len > PyList_Size(left) ? PyList_Size(left) : local_len; local_len = local_len > PyList_Size(right) ? PyList_Size(right) : local_len; /* Try to convert both lists to audio stream */ for (i = 0; i < local_len; i++) { /* With the courtesy of microsynth, float to sample conversion ;-) */ sample = (int)(32767.5 * PyFloat_AsDouble(PyList_GetItem(left, i))); /* Clip samples */ if (sample > 32767) sample = 32767; if (sample < -32768) sample = -32768; local_stream[i * 2] = (Sint16)sample; sample = (int)(32767.5 * PyFloat_AsDouble(PyList_GetItem(right, i))); /* Clip samples */ if (sample > 32767) sample = 32767; if (sample < -32768) sample = -32768; local_stream[i * 2 + 1] = (Sint16)sample; } /* Free the call's return value, probably Py_None */ Py_DECREF(r); } /* Free the channels */ Py_XDECREF(channels); /* And leave the GIL */ PyGILState_Release(gstate); return; } /* Output hello message */ static PyObject *pymod_hello(PyObject *self, PyObject *args) { PyObject *r = NULL;; if (PyArg_ParseTuple(args, "")) { printf("Hello from Python module\n"); Py_INCREF(Py_None); r = Py_None; } return r; } PyDoc_STRVAR(_ysynth_init_doc, "Initialise SDL audio and return context"); /* Initialise synthesizer */ static PyObject *_ysynth_init(PyObject *self, PyObject *args) { PyObject *r = NULL; PyYSynthContext *ctx; int samplerate, channels; SDL_AudioSpec fmt, fmt_obtained; if (PyArg_ParseTuple(args, "ii;samplerate, channels", &samplerate, &channels)) { ctx = PyObject_NEW(PyYSynthContext, &PyYSynthContext_Type); r = (PyObject*)ctx; ctx->callback = NULL; /* Setup SDL Audio device */ fmt.freq = samplerate; fmt.format = AUDIO_S16; fmt.channels = channels; fmt.samples = 512; fmt.callback = _sdl_audio_callback; fmt.userdata = r; /* Handle SDL error */ if (SDL_OpenAudio(&fmt, &fmt_obtained) < 0) { PyErr_SetString(SDLError, SDL_GetError()); Py_DECREF(r); return NULL; } SDL_PauseAudio(0); ctx->samplerate = fmt_obtained.freq; ctx->channels = fmt_obtained.channels; ctx->active = 1; } return r; } PyDoc_STRVAR(_ysynth_shutdown_doc, "Shutdown SDL audio device"); /* Shutdown SDL audio */ static PyObject *_ysynth_shutdown(PyObject *self, PyObject *args) { PyObject *r = NULL; PyYSynthContext *ctx; if (!PyArg_ParseTuple(args, "O!", &PyYSynthContext_Type, (PyObject**)&ctx)) { ctx->active = 0; SDL_CloseAudio(); Py_XDECREF(ctx->callback); r = Py_None; Py_INCREF(r); } return r; } PyDoc_STRVAR(_ysynth_set_callback_doc, "Set audio callback"); /* Set Python audio callback */ static PyObject *_ysynth_set_callback(PyObject *self, PyObject *args) { PyObject *r = NULL; PyYSynthContext *ctx; PyObject *callback; if (PyArg_ParseTuple(args, "O!O", &PyYSynthContext_Type, (PyObject**)&ctx, &callback)) { /* We don't have to lock SDL audio because Python's GIL will * have locked out the callback for us */ Py_XDECREF(ctx->callback); ctx->callback = callback; Py_INCREF(callback); r = Py_None; Py_INCREF(r); } return r; } PyDoc_STRVAR(_ysynth_get_callback_doc, "Get audio callback"); /* Get Python audio callback */ static PyObject *_ysynth_get_callback(PyObject *self, PyObject *args) { PyObject *r = NULL; PyYSynthContext *ctx; if (PyArg_ParseTuple(args, "O!", PyYSynthContext_Type, (PyObject**)&ctx)) { if (ctx->callback) { Py_INCREF(ctx->callback); r = ctx->callback; } else { r = Py_None; Py_INCREF(r); } } return r; } /* Exported methods */ static PyMethodDef pymod_methods[] = { {"_ysynth_init", (PyCFunction)_ysynth_init, METH_VARARGS, _ysynth_init_doc}, {"_ysynth_shutdown", (PyCFunction)_ysynth_shutdown, METH_VARARGS, _ysynth_shutdown_doc}, {"_ysynth_set_callback", (PyCFunction)_ysynth_set_callback, METH_VARARGS, _ysynth_set_callback_doc}, {"_ysynth_get_callback", (PyCFunction)_ysynth_get_callback, METH_VARARGS, _ysynth_get_callback_doc}, {"hello", (PyCFunction)pymod_hello, METH_VARARGS, hello_doc}, {NULL, NULL} /* sentinel */ }; PyDoc_STRVAR(module_doc, "Simple test module"); /* Shutdown SDL */ void exitpymod(void) { SDL_Quit(); } /* Module initialisation, called by Python on import */ PyMODINIT_FUNC initpymod(void) { PyObject *m; /* Setup SDL audio without signal handlers */ if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE) < 0) return; /* Register exit handler * XXX: Later on try to use Python's atexit module? */ atexit(exitpymod); puts("Hoi, module pymod loaded."); m = Py_InitModule3("pymod", pymod_methods, module_doc); if (m == NULL) return; /* Setup exceptions */ SDLError = PyErr_NewException("_ysynth.SDLError", NULL, NULL); Py_INCREF(SDLError); PyModule_AddObject(m, "SDLError", SDLError); /* Our module requires the GIL to be available */ if (!PyEval_ThreadsInitialized()) { puts("Initialising multithreading for Python"); PyEval_InitThreads(); } return; }