DynGen - Dynamic UGen

DynGen is a meta-UGen which allows to write DSP code on the fly using EEL2 as JIT-compiled language. This is essentially the SC equivalent of the gen~ object from Max MSP.

An example on how to use it for halfing the amplitude of a given signal.

// start the server
s.boot;

// registers a dyngen script on the server with identifier \simple
~simple = DynGenDef(\simple, "out0 = in0 * 0.5;").add;

// spawn a synth which evaluates our script
(
Ndef(\x, {DynGen.ar(
	1, // numOutputs
	~simple, // script to use - can also be DynGenDef(\simple) or \simple
	SinOsc.ar(200.0)), // ... the inputs to the script
}).scope;
)

This example isn’t too interesting, but we can also use DynGen for oversampling and single sample feedback really easily - e.g. here is the code and a sound snippet for two cross-phase-modulated SinOscs, 128 times oversampled

(
~complex = DynGenDef(\complex, "
twopi = 2*$pi;

phaseA += 0;
phaseB += 0;

freqA = in0;
freqB = in1;
modIndexA = in2;
modIndexB = in3;

oversample = 128;

osSrate = srate * oversample;
incA = freqA / osSrate;
incB = freqB / osSrate;

sumA = 0;
sumB = 0;

// calculate subsaples
loop(oversample,
    phaseA += incA;
    phaseB += incB;
    // wrap phases between [0, 1)
    phaseA -= floor(phaseA);
    phaseB -= floor(phaseB);

    // apply cross-phase modulation
    phaseA = phaseA + modIndexA * sin(twopi * phaseB);
    phaseB = phaseB + modIndexB * sin(twopi * phaseA);

    // accumulate (for downsampling)
    sumA += sin(twopi * phaseA);
    sumB += sin(twopi * phaseB);
);

// scale down b/c of os
out0 = sumA / oversample;
out1 = sumB / oversample;
").add;
)

(
Ndef(\y, {
	var sig = DynGen.ar(2, ~complex, 
		\freqA.ar(200.0),
		\freqB.ar(pi*100),
		\modA.ar(0.02, spec: [-0.1, 0.1]) * 0.05 * Env.perc(releaseTime: \releaseTime.kr(0.2)).ar(gate: Impulse.ar(\offsetKick.kr(4.0))),
		\modB.ar(0.0, spec: [-0.1, 0.1]) * 0.05,
	);
	sig * 0.1;
}).play.gui;
)

It is also easy to write modulatable delay lines

(
~delayLine = DynGenDef(\delayLine, "
buf[in1] = in0;
out0 = buf[in2];
").add;
)

(
Ndef(\z, {
	var bufSize = SinOsc.ar(4.2).range(1000, 2000);
	var writePos = LFSaw.ar(2.0, 0.02).range(1, bufSize);
	var readPos = LFSaw.ar(pi, 0.0).range(1, bufSize);
	var sig = DynGen.ar(1, ~delayLine,
		SinOsc.ar(100.0),
		writePos.floor,
		readPos.floor,
	);
	sig.dup * 0.1;
}).play;
)

When the script of a DynGenDef is updated, it will automatically replace the running UGen, such that livecoding of DSP is now possible.

The UGen is still considered beta, so the interface is not stable and is open to change.
I still have some open design questions, maybe the community can help me out here.
I haven’t tried if it works on Windows, so would be great to get some feedback for it.

Looking forward to hear some new sounds and see some interesting pseudo UGens popping up :slight_smile:

Massive thanks to @Spacechild1 for helping me how to handle NRT/RT synchronization in a good way and giving the initial idea for this during the symposium.

14 Likes

I’ve hit the :heart: button but that represents about 0.0001% of my actual reaction to this post.

:exploding_head:

hjh

Yeah!!! (and some more characters)

Great work!

I just spammed your repo with a few issues :smiley: I think the API is great, but I have a few suggestions. In particular, I don’t think that the audio inputs should be provided as varargs, see DynGen UGen inputs should not be varargs · Issue #21 · capital-G/DynGen · GitHub.

1 Like

Actually, I’m not too happy about the add method, see concerns about the `add` method · Issue #22 · capital-G/DynGen · GitHub

In short, I would prefer something like:

// returns the DynGenDef bound to symbol \foo, creating it on demand
DynGenDef(\foo);

// get \foo def and send code to the Server
DynGenDef(\foo).load("some code");

// get \foo def and tell the Server to read the given file
DynGenDef(\foo).readFile("myCode.txt");

I don’t care too much about the actual method names, but more about the general pattern.