| @@ -0,0 +1,17 @@ | |||
| CFLAGS += -Wall -Werror -pedantic -g | |||
| LFLAGS += -lSDL -lSDL_mixer | |||
| .PHONY: default clean | |||
| default: flanger | |||
| clean: | |||
| rm -f flanger *.o | |||
| 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 | |||
| @@ -0,0 +1,137 @@ | |||
| #include <math.h> | |||
| #include <stdlib.h> | |||
| #include <SDL/SDL.h> | |||
| #include <SDL/SDL_mixer.h> | |||
| #define CHUNK_SIZE 4096 | |||
| #if 1 | |||
| #define FLANGER_BUFFER_SIZE 88200 | |||
| #else | |||
| #define FLANGER_BUFFER_SIZE 2000 | |||
| #endif | |||
| void handle_exit(void) | |||
| { | |||
| Mix_Quit(); | |||
| SDL_Quit(); | |||
| puts("Shutdown complete."); | |||
| } | |||
| static struct flanger { | |||
| int samples; | |||
| int frequency; | |||
| int | |||
| start_ms, | |||
| stop_ms; | |||
| int cur_delay; | |||
| int sweep_dir; | |||
| int sweep_speed; | |||
| Sint16 buf[FLANGER_BUFFER_SIZE]; | |||
| } flange_chan; | |||
| /* Perform flanging effect */ | |||
| void flanger_func(int chan, void *stream, int len, void *udata) | |||
| { | |||
| int i; | |||
| Sint16 *sbuf = stream; | |||
| struct flanger *flanger = udata; | |||
| int local_len = len / sizeof(Sint16); | |||
| /*printf("Samples: %d, Local Len: %d, Len: %d\n", flanger->samples, local_len, len);*/ | |||
| for (i = 0; i < local_len; i++) { | |||
| flanger->buf[(flanger->samples + i) % | |||
| FLANGER_BUFFER_SIZE] = sbuf[i]; | |||
| sbuf[i] = sbuf[i] / 2 + flanger->buf[(flanger->samples + | |||
| (FLANGER_BUFFER_SIZE - flanger->cur_delay / 1000 * 2) + i) % | |||
| FLANGER_BUFFER_SIZE] / 2; | |||
| if (!(i%2)) { | |||
| if (flanger->cur_delay >= flanger->frequency / | |||
| (1000 / flanger->stop_ms) * 1000) { | |||
| flanger->sweep_dir = -flanger->sweep_speed; | |||
| } else if (flanger->cur_delay <= flanger->frequency / | |||
| (1000 / flanger->start_ms) * 1000) { | |||
| flanger->sweep_dir = flanger->sweep_speed; | |||
| } | |||
| flanger->cur_delay += flanger->sweep_dir; | |||
| } | |||
| /*sbuf[i] = 0;*/ | |||
| } | |||
| flanger->samples = (flanger->samples + local_len) % FLANGER_BUFFER_SIZE; | |||
| return; | |||
| } | |||
| int main(int argc, char *argv[]) | |||
| { | |||
| char *mp3_file; | |||
| Mix_Music *mp3_stream; | |||
| /* Audio settings */ | |||
| int frequency = 44100; | |||
| Uint16 format = AUDIO_S16; | |||
| int channels = 2; | |||
| if (argc != 2) { | |||
| printf("Usage %s: <mp3-file>\n", argv[0]); | |||
| return EXIT_SUCCESS; | |||
| } | |||
| mp3_file = argv[1]; | |||
| /* Setup shutdown sequence */ | |||
| atexit(handle_exit); | |||
| /* Setup SDL & friends */ | |||
| SDL_Init(SDL_INIT_AUDIO); | |||
| Mix_Init(MIX_INIT_MP3); | |||
| /* Open audio */ | |||
| if (Mix_OpenAudio(frequency, format, channels, CHUNK_SIZE)) { | |||
| printf("Mix_OpenAudio failed: %s\n", Mix_GetError()); | |||
| /* Mix_FreeMusic(mp3_stream); */ | |||
| return EXIT_FAILURE; | |||
| } | |||
| /* Verify audio compatibility */ | |||
| Mix_QuerySpec(&frequency, &format, &channels); | |||
| printf("Device settings are: %d, %hd, %d\n", frequency, format, channels); | |||
| if (format != AUDIO_S16) { | |||
| puts("Selected device format incompatible :-(\n"); | |||
| return EXIT_FAILURE; | |||
| } | |||
| /* Load MP3 file */ | |||
| mp3_stream = Mix_LoadMUS(mp3_file); | |||
| if (!mp3_stream) { | |||
| printf("Mix_LoadMUS failed: %s\n", Mix_GetError()); | |||
| return EXIT_FAILURE; | |||
| } | |||
| /* Setup flangers */ | |||
| memset(flange_chan.buf, 0x0, sizeof(short) * FLANGER_BUFFER_SIZE); | |||
| flange_chan.frequency = frequency; | |||
| flange_chan.samples = 0; | |||
| flange_chan.start_ms = 1; | |||
| flange_chan.stop_ms = 20; | |||
| flange_chan.sweep_speed = 5; | |||
| flange_chan.cur_delay = 0; | |||
| flange_chan.sweep_dir = 1; | |||
| if (!Mix_RegisterEffect(MIX_CHANNEL_POST, flanger_func, NULL, &flange_chan)) { | |||
| printf("Mix_RegisterEffect failed: %s\n", Mix_GetError()); | |||
| return EXIT_FAILURE; | |||
| } | |||
| /* Start playback */ | |||
| if (Mix_PlayMusic(mp3_stream, 0) < 0) { | |||
| printf("Mix_PlayMusic failed: %s\n", Mix_GetError()); | |||
| Mix_FreeMusic(mp3_stream); | |||
| return EXIT_FAILURE; | |||
| } | |||
| while (Mix_PlayingMusic()) | |||
| SDL_Delay(100); | |||
| return EXIT_SUCCESS; | |||
| } | |||
| @@ -0,0 +1,337 @@ | |||
| #include <python2.7/Python.h> | |||
| #include <SDL/SDL.h> | |||
| /* 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; | |||
| } | |||
| @@ -0,0 +1,354 @@ | |||
| """ | |||
| Python audio synthesis framework. | |||
| """ | |||
| from pymod import _ysynth_init, _ysynth_init, _ysynth_set_callback | |||
| from math import cos, sin, pi, modf | |||
| from itertools import izip | |||
| __all__ = ['YSynth', 'Sin', 'Cos', 'Saw', 'RevSaw', 'Square', 'Pulse'] | |||
| class YSynth(object): | |||
| """ | |||
| YSynth synthesis and audio context. | |||
| """ | |||
| def __init__(self, samplerate=44100, channels=2): | |||
| """ | |||
| Setup a simple synthesizer. | |||
| samplerate: Samples/second | |||
| channels: Number of output channels. | |||
| e.g. 1 for mono, 2 for stereo | |||
| """ | |||
| self.context = _ysynth_init(samplerate, channels) | |||
| self.samples = 0 | |||
| self.volume = 0.75 | |||
| # XXX need to fetch 'acquired' values from ysynth context | |||
| self.samplerate = samplerate | |||
| self.channels = channels | |||
| self.graph = None | |||
| self.set_callback(self.default_callback) | |||
| def deinit(self): | |||
| """ | |||
| Shutdown synthesizer, afterwards this object becomes useless. | |||
| """ | |||
| if self.context: | |||
| _ysynth_shutdown(self.context) | |||
| self.context = None | |||
| def default_callback(self, *channels): | |||
| """ | |||
| Default synthesis callback. | |||
| """ | |||
| # Outputs silence if no graph available | |||
| if not self.graph: | |||
| return | |||
| # Process audio graph | |||
| buf_len = len(channels[0]) | |||
| for i in xrange(buf_len): | |||
| next_sample = next(self.graph) | |||
| for j in xrange(len(channels)): | |||
| channels[j][i] = next_sample | |||
| # Advance sampleclock | |||
| self.samples += 1 | |||
| def set_graph(self, graph): | |||
| graph.set_synth(self) | |||
| self.graph = iter(graph) | |||
| def get_graph(self): | |||
| return self.graph | |||
| def __del__(self): | |||
| """ | |||
| Deinitialise synth before being removed from mem | |||
| """ | |||
| print "Deinitialising" | |||
| self.deinit() | |||
| def set_callback(self, func): | |||
| """ | |||
| Set audio output function. | |||
| Without any callback the synthesizer will simply output silence. | |||
| The callback should adhere the following signature: | |||
| def callback(channels): | |||
| channels shall contain a list of lists and each list | |||
| contains a large block of 0.0 floats describing the next set of samples. | |||
| Those floats should be set to the chunk of audio. | |||
| """ | |||
| _ysynth_set_callback(self.context, func) | |||
| def get_callback(self): | |||
| return _ysynth_get_callback(self.context) | |||
| class YConstant(object): | |||
| """ | |||
| Unchanging signal output. | |||
| """ | |||
| def __init__(self, const): | |||
| self.const = float(const) | |||
| def __iter__(self): | |||
| return iter(self()) | |||
| def __call__(self): | |||
| while True: | |||
| yield self.const | |||
| def set_synth(self, synth): | |||
| pass | |||
| class YAudioGraphNode(object): | |||
| """ | |||
| Base audio graph node | |||
| This base class provides YSynth's DSL behaviours such as | |||
| adding, substracting and the sample iteration protocol. | |||
| """ | |||
| def __init__(self, *inputs): | |||
| self.inputs = [] | |||
| lens = 0 | |||
| for stream in inputs: | |||
| if not isinstance(stream, YAudioGraphNode): | |||
| stream = YConstant(stream) | |||
| self.inputs.append(stream) | |||
| self.inputs = tuple(self.inputs) | |||
| #for stream in inputs: | |||
| # if isinstance(stream, YAudioGraphNode): | |||
| # pass | |||
| # # Make sure all multi-channels share the same size | |||
| # if len(stream) != 1: | |||
| # if not lens: | |||
| # lens = len | |||
| # elif lens != len(stream): | |||
| # # TODO: Expand error info to contain sizes | |||
| # raise ValueError("Cannot combine different sized " | |||
| # "multi-channel streams") | |||
| #self.channels = lens | |||
| def __iter__(self): | |||
| """ | |||
| Initialise graph for synthesis, link to sampleclock. | |||
| """ | |||
| # XXX This function is not graph cycle safe | |||
| # FIXME: I need to unpack the channels from the input streams | |||
| # multiplex any mono-streams if there are multi-channel streams | |||
| # available, and then initialise every set of streams' component | |||
| # generator function by calling self with the correct arguments. | |||
| # Setup input streams | |||
| for stream in self.inputs: | |||
| stream.set_synth(self.synth) | |||
| # Setup self | |||
| # The self.samples variable is used for protecting against | |||
| # multiple next() calls, next will only evaluate the next | |||
| # set of input samples if the synth's sampleclock has changed | |||
| self.samples = self.synth.samples - 1 | |||
| # XXX self.last_sample is currently not initialised on purpose | |||
| # maybe this must be changed at a later time. | |||
| # Connect the actual generator components | |||
| sample_iter = iter(self(izip(*self.inputs))) | |||
| # Build the sample protection function | |||
| def sample_func(): | |||
| """ | |||
| Make sure multiple next() calls during the same | |||
| clock cycle yield the same sample value. | |||
| """ | |||
| while True: | |||
| if self.samples != self.synth.samples: | |||
| self.last_sample = next(sample_iter) | |||
| self.samples = self.synth.samples | |||
| yield self.last_sample | |||
| return sample_func() | |||
| def set_synth(self, synth): | |||
| """ | |||
| Set this component's synthesizer. | |||
| This is mainly useful for reading the synth's sampleclock. However every | |||
| active component requires a valid synthesizer. | |||
| """ | |||
| self.synth = synth | |||
| def get_synth(self, synth): | |||
| """ | |||
| Return this component's synthesizer. | |||
| """ | |||
| return self.synth | |||
| def __add__(self, other): | |||
| return Adder(self, other) | |||
| def __radd__(self, other): | |||
| return Adder(other, self) | |||
| def __sub__(self, other): | |||
| return Subtractor(self, other) | |||
| def __rsub__(self, other): | |||
| return Subtractor(other, self) | |||
| def __mul__(self, other): | |||
| return Multiplier(self, other) | |||
| def __rmul__(self, other): | |||
| return Multiplier(other, self) | |||
| def __div__(self, other): | |||
| return Divisor(self, other) | |||
| def __rdiv__(self, other): | |||
| return Divisor(other, self) | |||
| def __getitem__(self, delay): | |||
| """ | |||
| Return a delayed version of the output signal. | |||
| """ | |||
| return Delay(self, delay) | |||
| def __next__(self): | |||
| """ | |||
| Process next sample. | |||
| """ | |||
| def __call__(self): | |||
| raise NotImplementedError("You need to inherit this class") | |||
| def process(self, *streams): | |||
| raise NotImplementedError("You need to inherit this class") | |||
| # Basic signal arithmetic | |||
| class Adder(YAudioGraphNode): | |||
| def __call__(self, l): | |||
| for a, b in l: | |||
| yield a + b | |||
| class Subtractor(YAudioGraphNode): | |||
| def __call__(self, l): | |||
| for a, b in l: | |||
| yield a - b | |||
| class Multiplier(YAudioGraphNode): | |||
| def __call__(self, l): | |||
| for a, b in l: | |||
| yield a * b | |||
| class Divisor(YAudioGraphNode): | |||
| def __call__(self, l): | |||
| for a, b in l: | |||
| yield a / b | |||
| # Sample delay | |||
| class Delay(YAudioGraphNode): | |||
| def __call__(self, l): | |||
| buf = [0.0] * 4096 | |||
| samples = 0 | |||
| for sig, delay in l: | |||
| buf[samples] = sig | |||
| yield buf[(samples - delay) % 4096] | |||
| samples = (samples + 1) % 4096 | |||
| # Base oscillator class | |||
| class YOscillator(YAudioGraphNode): | |||
| def __call__(self, l): | |||
| def cycle_gen(): | |||
| """ | |||
| This function generates the oscillation cycle | |||
| upon which all basic decoupled oscillators base their output | |||
| signal. | |||
| A decoupled oscillator is an oscillator | |||
| with its own cycle generator. These oscillators respond well to | |||
| incoming frequency changes, but due to the limitations of floating | |||
| point are at risk of drifting out of phase, coupled oscillators | |||
| are always in phase with each other. | |||
| The generated cycle ranges from 0.0 to 1.0 exclusive. | |||
| """ | |||
| cycle = 0.0 | |||
| samples = 0 | |||
| for freq, in l: | |||
| cycle = freq * samples / float(self.synth.samplerate) | |||
| yield modf(cycle)[0] | |||
| samples = (samples + 1) % self.synth.samplerate | |||
| return self.oscillate(cycle_gen()) | |||
| def oscillate(self, l): | |||
| raise NotImplementedError("Inherit this class") | |||
| # Basic oscillators | |||
| class Sin(YOscillator): | |||
| """ | |||
| Sine wave oscillator | |||
| """ | |||
| def oscillate(self, l): | |||
| for pos in l: | |||
| yield sin(pos * 2 * pi) | |||
| class Cos(YOscillator): | |||
| """ | |||
| Cosine wave oscillator | |||
| """ | |||
| def oscillate(self, l): | |||
| for pos in l: | |||
| yield cos(pos * 2 * pi) | |||
| class Saw(YOscillator): | |||
| """ | |||
| Saw wave oscillator | |||
| """ | |||
| def oscillate(self, l): | |||
| for pos in l: | |||
| yield pos * 2.0 - 1.0 | |||
| class RevSaw(YOscillator): | |||
| """ | |||
| Reverse Saw wave oscillator | |||
| """ | |||
| def oscillate(self, l): | |||
| for pos in l: | |||
| yield -(pos * 2.0 - 1.0) | |||
| class Square(YOscillator): | |||
| """ | |||
| Square wave oscillator | |||
| """ | |||
| def oscillate(self, l): | |||
| for pos in l: | |||
| yield 1.0 if pos < 0.5 else -1.0 | |||
| class Pulse(YOscillator): | |||
| """ | |||
| Pulse oscillator | |||
| """ | |||
| def oscillate(self, l): | |||
| prev_pos = 1.0 | |||
| for pos in l: | |||
| yield 1.0 if pos < prev_pos else 0.0 | |||
| prev_pos = pos | |||