Browse Source

YSynth conversion routine ported to NumPy.

master
Bas Weelinck 13 years ago
parent
commit
128b95d2dc
5 changed files with 126 additions and 86 deletions
  1. +9
    -2
      Makefile
  2. +32
    -0
      hoi.py
  3. +25
    -79
      pymod.c
  4. +53
    -0
      pymod2.c
  5. +7
    -5
      ysynth.py

+ 9
- 2
Makefile View File

@@ -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

+ 32
- 0
hoi.py View File

@@ -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)


+ 25
- 79
pymod.c View File

@@ -1,5 +1,6 @@
#include <python2.7/Python.h>
#include <Python.h>
#include <SDL/SDL.h>
#include <numpy/arrayobject.h>

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


+ 53
- 0
pymod2.c View File

@@ -0,0 +1,53 @@
#include <python2.7/Python.h>
#include <numpy/arrayobject.h>

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;
}


+ 7
- 5
ysynth.py View File

@@ -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


Loading…
Cancel
Save