#include #include #include #include /* 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; }