From 128b95d2dca939d3d89aa0dd93fa2ec98d38a2ef Mon Sep 17 00:00:00 2001 From: Bas Weelinck Date: Mon, 30 Apr 2012 18:10:26 +0200 Subject: [PATCH] YSynth conversion routine ported to NumPy. --- Makefile | 11 ++++-- hoi.py | 32 +++++++++++++++++ pymod.c | 104 +++++++++++++----------------------------------------- pymod2.c | 53 ++++++++++++++++++++++++++++ ysynth.py | 12 ++++--- 5 files changed, 126 insertions(+), 86 deletions(-) create mode 100644 hoi.py create mode 100644 pymod2.c diff --git a/Makefile b/Makefile index 3c1205c..b31b81d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,12 @@ -CFLAGS += -Wall -Werror -pedantic -g +PYTHON_VERSION:=python-2.7 + +CFLAGS += $(shell pkg-config --cflags $(PYTHON_VERSION)) +CFLAGS += -Wall -pedantic -std=c99 -g +LFLAGS += $(shell pkg-config --libs $(PYTHON_VERSION)) LFLAGS += -lSDL -lSDL_mixer + .PHONY: default clean default: flanger @@ -13,5 +18,7 @@ flanger: hoi.c gcc -o flanger hoi.c $(LFLAGS) $(CFLAGS) pymodmodule.so: pymod.c - gcc -o pymodmodule.so pymod.c -fPIC $(CFLAGS) -shared -lpython2.7 -lSDL + gcc -o pymodmodule.so pymod.c -fPIC $(CFLAGS) $(LFLAGS) -shared -I/usr/lib/python2.7/site-packages/numpy/core/include +pymod2module.so: pymod2.c + gcc -o pymod2module.so pymod2.c -fPIC $(CFLAGS) $(LFLAGS) -shared -I/usr/lib/python2.7/site-packages/numpy/core/include diff --git a/hoi.py b/hoi.py new file mode 100644 index 0000000..f4f1318 --- /dev/null +++ b/hoi.py @@ -0,0 +1,32 @@ + +import pymod +from math import sin, pi + +samplerate = 44100 +channels = 2 + +x = pymod._ysynth_init(samplerate, channels) + +samples_processed = 0 + +def sine_wave(channels): + """ + Send simple 440Hz to your speakers + """ + + global samples_processed + + if not samples_processed: + print "First time callback occurred" + print "Array shape is: %s" % str(channels.shape) + + for i in xrange(channels.shape[0]): + channels[i][0] = channels[i][1]= sin(440.0 * (i + samples_processed) / + samplerate * (pi * 2.0)) * 20000 + + samples_processed += channels.shape[0] + + return + +pymod._ysynth_set_callback(x, sine_wave) + diff --git a/pymod.c b/pymod.c index 0b84b12..e441d57 100644 --- a/pymod.c +++ b/pymod.c @@ -1,5 +1,6 @@ -#include +#include #include +#include /* SDL error handling */ PyObject *SDLError; @@ -45,24 +46,16 @@ PyTypeObject PyYSynthContext_Type = { 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; + PyObject *r, *args; + NPY_AO *channels; PyGILState_STATE gstate; - int i, j; - int sample; 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 @@ -70,82 +63,31 @@ void _sdl_audio_callback(void *user, Uint8 *stream, int len) */ 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); + /* 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 { - 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; - } + /* 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); @@ -154,6 +96,8 @@ void _sdl_audio_callback(void *user, Uint8 *stream, int len) return; } +PyDoc_STRVAR(hello_doc, "Print hello message from Python module"); + /* Output hello message */ static PyObject *pymod_hello(PyObject *self, PyObject *args) { @@ -293,14 +237,14 @@ static PyMethodDef pymod_methods[] = { {NULL, NULL} /* sentinel */ }; -PyDoc_STRVAR(module_doc, "Simple test module"); - /* Shutdown SDL */ void exitpymod(void) { SDL_Quit(); } +PyDoc_STRVAR(module_doc, "Simple test module"); + /* Module initialisation, called by Python on import */ PyMODINIT_FUNC initpymod(void) @@ -332,6 +276,8 @@ initpymod(void) PyEval_InitThreads(); } + import_array(); + return; } diff --git a/pymod2.c b/pymod2.c new file mode 100644 index 0000000..af15f1b --- /dev/null +++ b/pymod2.c @@ -0,0 +1,53 @@ +#include +#include + +PyDoc_STRVAR(hello_doc, "Print hello message from Python module"); + +/* Output hello message */ +static PyObject *pymod_hello(PyObject *self, PyObject *args) +{ + PyObject *r = NULL; + + npy_int dims[2] = {940, 2}; + + if (PyArg_ParseTuple(args, "")) { + printf("Hello from Python module\n"); + + r = PyArray_New(&PyArray_Type, 2, dims, NPY_INT16, NULL, NULL, 2, 0, NULL); + } + + return r; +} + +/* Exported methods */ +static PyMethodDef pymod_methods[] = { + {"hello", (PyCFunction)pymod_hello, + METH_VARARGS, hello_doc}, + {NULL, NULL} /* sentinel */ +}; + +PyDoc_STRVAR(module_doc, "Simple test module"); + +/* Module initialisation, called by Python on import */ +PyMODINIT_FUNC +initpymod2(void) +{ + PyObject *m; + + puts("Hoi, module pymod2 loaded."); + m = Py_InitModule3("pymod2", pymod_methods, module_doc); + if (m == NULL) + return; + + /* Our module requires the GIL to be available */ + if (!PyEval_ThreadsInitialized()) { + puts("Initialising multithreading for Python"); + PyEval_InitThreads(); + } + + /* Initialise NumPy */ + import_array(); + + return; +} + diff --git a/ysynth.py b/ysynth.py index ca53370..2866ccb 100644 --- a/ysynth.py +++ b/ysynth.py @@ -41,7 +41,7 @@ class YSynth(object): _ysynth_shutdown(self.context) self.context = None - def default_callback(self, *channels): + def default_callback(self, channels): """ Default synthesis callback. """ @@ -51,13 +51,15 @@ class YSynth(object): return # Process audio graph - buf_len = len(channels[0]) + buf_len = channels.shape[0] self.chunk_size = buf_len next_chunk = next(self.graph) - for j in xrange(len(channels)): - for i in xrange(buf_len): - channels[j][i] = next_chunk[i] + # Do we need to mix from mono to stereo? + if len(next_chunk.shape) == 1: + next_chunk = np.dot(np.reshape(next_chunk, (-1, 1)), ((1, 1),)) + #print channels.shape, next_chunk.shape + np.round(next_chunk * 32767.5, 0, out=channels) # Advance sampleclock self.samples += buf_len