|
- #include <Python.h>
- #include <SDL/SDL.h>
- #include <numpy/arrayobject.h>
- #include <mpg123.h>
-
- /* SDL error handling */
- PyObject *SDLError;
-
- /* MPG123 error handling */
- PyObject *MP3Error;
-
- /* The synthesis context, used mainly
- * for storing audio settings and state.
- */
- typedef struct {
- PyObject_HEAD
- int
- samplerate,
- channels;
- int active;
- PyObject *callback;
- } PyYSynthContext;
-
- void PyYSynthContext_Dealloc(PyYSynthContext *self)
- {
- puts("_ysynth: Dealloc context");
- if (self->active) {
- /* Allow any running Python audio routines
- * to finish their current chunk, then lock out
- * the audio callback and shut the audio device
- * down.
- * It is important to first unlock the GIL otherwise
- * we're at risk of causing deadlock.
- */
- Py_BEGIN_ALLOW_THREADS
- SDL_LockAudio();
- SDL_CloseAudio();
- SDL_UnlockAudio();
- Py_END_ALLOW_THREADS
- 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*/
- };
-
- /* SDL Audio callback */
- void _sdl_audio_callback(void *user, Uint8 *stream, int len)
- {
- PyYSynthContext *ctx = (PyYSynthContext*)user;
- PyObject *r, *args;
- NPY_AO *channels;
- PyGILState_STATE gstate;
- int local_len = len / sizeof(Sint16) / 2;
- Sint16 *local_stream = (Sint16*)stream;;
- npy_int dims[2] = {local_len, 2};
-
- /* 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();
-
- /* Setup NumPy array for use within the Python synthesizer */
- channels = PyArray_New(&PyArray_Type, 2, dims, NPY_INT16, NULL, NULL, 2, 0, NULL);
-
- if (channels)
- args = Py_BuildValue("(O)", channels);
-
- /* Finally execute the callback */
- if (args)
- r = PyObject_CallObject(ctx->callback, (PyObject*)args);
-
- /* Print stacktrace if necessary */
- if (!r) {
- if (PyErr_Occurred())
- PyErr_Print();
- } else {
- /* Copy array data to local_stream */
- memcpy(local_stream, channels->data, len);
-
- /* Free the call's return value, probably Py_None */
- Py_DECREF(r);
- }
-
- /* Free arguments */
- Py_XDECREF(args);
-
- /* Free the channels */
- Py_XDECREF(channels);
-
- /* And leave the GIL */
- PyGILState_Release(gstate);
- return;
- }
-
- PyDoc_STRVAR(hello_doc, "Print hello message from Python module");
-
- /* 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 C callback for us, only this Python thread
- * can be executing at this time.
- */
- 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;
- }
-
- /* MP3 Support */
- typedef struct {
- PyObject_HEAD
- mpg123_handle *mh;
- int open, eof;
- long rate;
- int
- channels,
- encoding;
- } PyYMP3Context;
-
- void PyYMP3Context_Dealloc(PyYMP3Context *ctx)
- {
- puts("_ysynth: Dealloc mp3 context");
-
- /* Close if necessary */
- if (ctx->open)
- mpg123_close(ctx->mh);
-
- /* Finally free the MP3 decoder handle */
- if (ctx->mh) {
- mpg123_delete(ctx->mh);
- ctx->mh = NULL;
- }
-
- return;
- }
-
- PyTypeObject PyYMP3Context_Type;
-
- PyTypeObject PyYMP3Context_Type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "_ysynth.ymp3 context", /*tp_name*/
- sizeof(PyYMP3Context), /*tp_basicsize*/
- 0, /*tp_itemsize*/
- /* methods */
- (destructor)PyYMP3Context_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(_ysynth_mp3_open_doc, "Open MP3 file and return context,"
- " raises exception if file does not exists\n"
- "Args:\n"
- " filename: Path string to file\n"
- "Returns:\n"
- " MP3 context");
-
- /* Open MP3 file */
- static PyObject *_ysynth_mp3_open(PyObject *self, PyObject *args)
- {
- PyObject *r = NULL;
- char *filename;
- PyYMP3Context *ctx;
- mpg123_handle *mh;
- int err;
-
- if (PyArg_ParseTuple(args, "s;filename", &filename)) {
- /* Try to open MP3 */
- mh = mpg123_new(NULL, &err);
- if (!mh) {
- PyErr_SetString(MP3Error, mpg123_plain_strerror(err));
- return NULL;
- }
-
- /* Setup float samples */
- mpg123_param(mh, MPG123_ADD_FLAGS, MPG123_FORCE_FLOAT, 0.);
-
- /* Open the actual MP3 file */
- if (mpg123_open(mh, filename) != MPG123_OK) {
- PyErr_SetString(MP3Error, mpg123_strerror(mh));
- mpg123_delete(mh);
- return NULL;
- }
-
- /* Create object */
- ctx = PyObject_NEW(PyYMP3Context, &PyYMP3Context_Type);
- ctx->open = 1;
- ctx->eof = 0;
- ctx->mh = mh;
- r = (PyObject*)ctx;
-
- if (!ctx) {
- mpg123_close(mh);
- mpg123_delete(mh);
- return NULL;
- }
-
- /* Check-out file stream format */
- mpg123_getformat(mh, &ctx->rate, &ctx->channels, &ctx->encoding);
-
- if (ctx->encoding != MPG123_ENC_FLOAT_32) {
- PyErr_SetString(MP3Error,
- "Got non-float encoder while forcing float");
- Py_DECREF(ctx);
- return NULL;
- }
-
- /* Now don't allow the format to change.
- *
- * FIXME: Later on we probably do want to change this, as to
- * allow YSynth to handle any changes in samplerate instead of
- * allowing MPG123's crude resampler to do the job.
- */
- mpg123_format_none(mh);
- mpg123_format(mh, ctx->rate, ctx->channels, ctx->encoding);
- }
-
- return r;
- }
-
- PyDoc_STRVAR(_ysynth_mp3_close_doc, "Close MP3 file context\n"
- "Args:\n"
- " context: MP3 context\n"
- "Returns:\n"
- " None, but raises an MP3Error if the file has already been closed");
-
- /* Close MP3 file */
- static PyObject *_ysynth_mp3_close(PyObject *self, PyObject *args)
- {
- PyObject *r = NULL;
- PyYMP3Context *ctx;
-
- if (PyArg_ParseTuple(args, "O!", &PyYMP3Context_Type, (PyObject**)&ctx)) {
- if (ctx->open) {
- ctx->open = 0;
- mpg123_close(ctx->mh);
- r = Py_None;
- Py_INCREF(r);
-
- } else {
- PyErr_SetString(MP3Error, "File already closed");
- r = NULL;
- }
- }
-
- return r;
- }
-
- PyDoc_STRVAR(_ysynth_mp3_get_format_doc, "Get MP3 format configuration\n"
- "Args: None\n"
- "Returns:\n"
- " Tuple containing: (samplerate, channels)");
-
- /* Read MP3 data */
- static PyObject *_ysynth_mp3_get_format(PyObject *self, PyObject *args)
- {
- PyObject *r = NULL;
- PyYMP3Context *ctx;
-
- if (PyArg_ParseTuple(args, "O!", &PyYMP3Context_Type, (PyObject**)&ctx)) {
- if (ctx->open) {
- r = Py_BuildValue("(ii)", ctx->rate, ctx->channels);
- } else {
- PyErr_SetString(MP3Error, "File has been closed");
- }
- }
-
- return r;
- }
-
- PyDoc_STRVAR(_ysynth_mp3_read_doc, "Read MP3 data\n"
- "Args:\n"
- " context: Open MP3 context\n"
- " samples: Number of samples to decode\n"
- "Returns:\n"
- " A NumPy array of shape (samples, channels)\n"
- " This function raises an MP3Error on decode failure.\n"
- " It shall output silence on EOF");
-
- /* Read MP3 data */
- static PyObject *_ysynth_mp3_read(PyObject *self, PyObject *args)
- {
- PyObject *r = NULL;
- PyYMP3Context *ctx;
- int samples, bytes_read;
- int err;
- npy_int dims[2];
- NPY_AO *stream;
-
- if (PyArg_ParseTuple(args, "O!i", &PyYMP3Context_Type, (PyObject**)&ctx, &samples)) {
- if (ctx->open) {
- /* Allocate output array */
- dims[0] = samples;
- dims[1] = ctx->channels;
- stream = PyArray_New(&PyArray_Type, 2, dims, NPY_FLOAT32, NULL, NULL, 4, 0, NULL);
-
- if (!stream)
- return NULL;
- r = (PyObject*)stream;
-
- /* EOF Generates a silence stream */
- if (ctx->eof) {
-
- memset(PyArray_DATA(stream), 0x0, PyArray_NBYTES(stream));
- } else {
-
- /* Read the next set of MP3 samples */
- err = mpg123_read(ctx->mh, PyArray_DATA(stream),
- PyArray_NBYTES(stream), &bytes_read);
-
- /* Handle any weird errors */
- if (err != MPG123_OK) {
- if (err == MPG123_DONE) {
- ctx->eof = 1;
- } else {
- Py_DECREF(stream);
- PyErr_SetString(MP3Error, mpg123_strerror(ctx->mh));
- return NULL;
- }
- }
-
- /* Fill any missing data with silence, and of course complain
- * whenever necessary
- */
- if (bytes_read != PyArray_NBYTES(stream)) {
- if (!ctx->eof)
- fprintf(stderr, "warning: MPG123 did not return"
- " enough data but did not set MPG123_DONE"
- " either..\n");
- memset(PyArray_BYTES(stream) + bytes_read, 0x0,
- PyArray_NBYTES(stream) - bytes_read);
- }
- }
- } else {
- PyErr_SetString(MP3Error, "File has been closed");
- }
- }
-
- return r;
- }
-
- /* TODO: Let's use the same strategy NumPy uses for its built-ins, provide
- * the function signature in the docstrings
- */
-
- PyDoc_STRVAR(_ysynth_mp3_seek_doc, "Seek to position in MP3\n"
- "Args:\n"
- " context: Open MP3 context, providing somehting else\n"
- " will result in an exception\n"
- " position: Integer position in samples to seek to\n"
- "Returns:\n"
- " None, this functions raises an MP3Error when errors are encountered");
-
- /* Seek MP3 data */
- static PyObject *_ysynth_mp3_seek(PyObject *self, PyObject *args)
- {
- PyObject *r = NULL;
- PyYMP3Context *ctx;
- int sample;
- int err;
-
- if (PyArg_ParseTuple(args, "O!i;context, position", &PyYMP3Context_Type,
- (PyObject**)&ctx, &sample)) {
- if (ctx->open) {
- err = mpg123_seek(ctx->mh, sample, SEEK_SET);
- if (err < 0) {
- PyErr_SetString(MP3Error, mpg123_strerror(ctx->mh));
- } else {
- r = Py_None;
- Py_INCREF(r);
- }
- } else {
- PyErr_SetString(MP3Error, "File has been closed");
- }
- }
-
- 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},
- {"_ysynth_mp3_open", (PyCFunction)_ysynth_mp3_open,
- METH_VARARGS, _ysynth_mp3_open_doc},
- {"_ysynth_mp3_close", (PyCFunction)_ysynth_mp3_close,
- METH_VARARGS, _ysynth_mp3_close_doc},
- {"_ysynth_mp3_get_format", (PyCFunction)_ysynth_mp3_get_format,
- METH_VARARGS, _ysynth_mp3_get_format_doc},
- {"_ysynth_mp3_read", (PyCFunction)_ysynth_mp3_read,
- METH_VARARGS, _ysynth_mp3_read_doc},
- {"_ysynth_mp3_seek", (PyCFunction)_ysynth_mp3_seek,
- METH_VARARGS, _ysynth_mp3_seek_doc},
- {"hello", (PyCFunction)pymod_hello,
- METH_VARARGS, hello_doc},
- {NULL, NULL} /* sentinel */
- };
-
- /* Shutdown SDL/MPG123 */
- void exitpymod(void)
- {
- mpg123_exit();
- /* XXX: Should we check for successful initialisation of SDL? */
- SDL_Quit();
- }
-
-
- PyDoc_STRVAR(module_doc, "Simple test module");
-
- /* Module initialisation, called by Python on import */
- PyMODINIT_FUNC
- initpymod(void)
- {
- PyObject *m;
- int err;
-
- /* Setup SDL audio without signal handlers */
- if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE) < 0)
- return;
-
- #if 1
- /* Setup MPG123 */
- if ((err = mpg123_init()) != MPG123_OK) {
- fprintf(stderr, "Failure while initialising mgp123: %s\n",
- mpg123_plain_strerror(err));
- return;
- }
- #endif
-
- /* 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);
-
- MP3Error = PyErr_NewException("_ysynth.MP3Error", NULL, NULL);
- Py_INCREF(MP3Error);
- PyModule_AddObject(m, "MP3Error", MP3Error);
-
- /* Setup types */
- if (PyType_Ready(&PyYSynthContext_Type) < 0)
- return;
-
- if (PyType_Ready(&PyYMP3Context_Type) < 0)
- return;
-
- /* Our module requires the GIL to be available */
- if (!PyEval_ThreadsInitialized()) {
- puts("Initialising multithreading for Python");
- PyEval_InitThreads();
- }
-
- import_array();
-
- return;
- }
|