I added a couple new unit generators. The ‘mult’ generator does what you’d expect. It’s a utility module for multiplying two signals together. I also added a ‘tape’ ugen, which is variable speed sampler basically. Here it is with its speed param being driven by a sine ugen:
from unittest import TestCase
from pippi import dsp, fx, ugens
class TestUgens(TestCase):
def test_ugen_tape(self):
buf = dsp.read('tests/sounds/living.wav')
graph = ugens.Graph()
graph.add_node('s1', 'sine', freq=100)
graph.add_node('s2', 'sine', freq=0.1)
graph.add_node('t', 'tape', buf=buf)
graph.connect('s1.output', 't.speed', 0.1, 1)
graph.connect('s2.output', 's1.freq', 10, 2000)
graph.connect('t.output', 'main.output', mult=0.5)
out = graph.render(10)
out = fx.norm(out, 1)
out.write('tests/renders/ugens_tape.wav')
I’d like everything available as a ugen eventually (as an option at least) but I’ll probably put the ugen system aside again for the moment as it does mostly everything I want it to do for the piece I’m working on now. I’d also like to get back to testing the relay trigger system wired into the passive mixer I’ve been building, so maybe this’ll be on hold for a bit.
A thing I have in mind for this ugen system is to make it easier to write with feedback: making small graphs of ugens to use as per-note / per-event filters & waveshapers in the astrid instruments I’m working on for rain.
I’m still a bit unsure of a change to the API I made to support the tape ugen. All the unit generators have the same interface:
struct ugen_t {
void * params;
lpfloat_t (*get_output)(ugen_t * u, int index);
void (*set_param)(ugen_t * u, int index, void * value);
void (*process)(ugen_t * u);
void (*destroy)(ugen_t * u);
};
The params member is just an opaque blob the ugen can do as
it pleases with internally. The rest of the members of a ugen struct
point to functions that implement the ugen. They all take the ugen
instance as an argument to have access to the params blob.
-
get_outputtakes the index of an output and returns the current value of that output. All the ugens have amainoutput, but they also can map any or all params to outputs, or provide a suite of outputs as they please. These additional outputs can be used when making connections in the graph. Like stacking banana cables. -
set_paramtakes the param index and a void pointer to the value to set. Using a void pointer here is the change I made to support the tape ugen. It lets a param update swap in a new internal buffer. (In theory I guess if I made a pointer table ugen you could modulate the buffer selection with a sinewave or something but that goofery will wait for another day.) -
processjust does whatever per-frame processing the ugen might need to do, and then copies the latest values to all of its outputs. -
destroyis just cleanup, freeing internal ugen resources as needed.
All of the graph functionality is implemented in cython with dict lookup
tables right now. When connections are made strings from python are
converted to enum ints for the indexes in the get_output
and set_param methods. All the ugens define an enum of
constants for output channels and params. (Like
USINEIN_FREQ and USINEOUT_MAIN)
I’m pretty happy with the python interfaces for this (the API for the test scripts above and below) but if I want to add ugens as first class citizens in astrid too, then I’ll need to move all of the graph stuff into libpippi. That way the graph doesn’t have to touch python at all but can still be orchestrated from python. It would be nice to have for rain but I’ll save it for later since there’s other stuff I’d like to do this month.