DynGen - Dynamic UGen

I think that feature would be pretty straightforward to add to my Voicer quark. (Cue up my standard editorial comment about the ways that we make our code harder to extend by relying directly on low level abstractions, instead of building higher level abstractions… I already have a higher level abstraction to fit this requirement into :wink: )

I’d imagine you can get away with quite a bit, apart from timestamps (which will not be possible to respect, if there’s a NRT part of synth instantiation).

That’s not a complaint, btw – really impressed with this. Just noting a boundary condition (and there’s a lot of space inside the boundary).

hjh

I don’t fully understand the timestamp aspect - DynGen includes two UGens - the default DynGen which offloads the compilation to a NRT thread and therefore introduces a delay of at least one block size, and DynGenRT which compiles blocking on the RT thread and is therefore available from the first sample but may introduce dropouts. See DynGenRT help for more information.

The audio sample/snippet in the previous post actually uses DynGenRT to demonstrate that one can actually use DynGenRT w/o dropouts, but if you don’t need starting from the first sample it is always better to use DynGen.

I don’t see any harm in developing ways to use DynGen with accurate timestamp resolution and no risk of dropouts.

I’m of course aware of DynGenRT – it’s mentioned in the same note that I quoted earlier. But, if I’m onstage, I would like to reduce the risk of xruns as much as possible, and I’m perfectly fine with developing a structure to do that transparently with DynGen. (As noted above, I don’t think it will take long, though it will have to wait until after a late November show.)

hjh

1 Like

This is amazing! Thanks for sharing!

I have one little question (I am not familiar with EEL2)

The help file states that ’ each variable within EEL2 is already an array which allows us to use it as a delay line.’ As a matter of fact, I see in your examples that you use buffers directly instantiated in the script, like in:

buffer[writeIndex] = in0;

But what is the default size of such arrays? Are they instantiated with a default max initial size? Or are they resized at runtime if needed?

Thanks for your kind help!

I had a go at converting a schroeder reverb I found on the max forums, but it sounds like repeated definitions of the allpass are sharing the same buffer or something (it’s giving a weird pitch shifter effect)

anyone have any tips?

(
~genDef = DynGenDef(\funcs, 
        "
        function wrap(x, low, high)local(range, result)
        (
            range = high - low;
            result = range != 0 ? (x - floor((x - low) / range) * range) : low;

            result;
        );

        function mstosamps(ms)( srate * ms * 0.001 );

        function linear_interp(x, y, a) ( x + a * (y-x));

        function lpf_op_simple(in, damp) local(init, prev, lpf)(
            lpf = linear_interp(in, prev, damp);
            prev = lpf;
            lpf;
        );

        function allpass_delay(in, gain, delay_samps, buf) local(init, ptr, max_delay, read_pos, frac, a, b, tap, sig, out)
        (
            !init ? (ptr = 0; init = 1;);
            max_delay = mstosamps(3000);

            read_pos = wrap(ptr - delay_samps, 0, max_delay);
            frac = read_pos - floor(read_pos);
            a = wrap(floor(read_pos), 0, max_delay);
            b = wrap(a + 1, 0, max_delay);
            tap = linear_interp(buf[a|0], buf[b|0], frac);

            sig = (in - tap) * gain;
            out = tap + sig;

            buf[ptr] = sig;
            ptr = wrap(ptr + 1, 0, max_delay);

            out;
        );

        function fbcomb(in, gain, delay_samps, damp, buf) local(init, ptr, read_pos, max_delay, frac, a, b, tap, out)
        (
            
            !init ? (ptr = 0; init = 1;);
            max_delay = mstosamps(3000);

            read_pos = wrap(ptr - delay_samps, 0, max_delay);
            frac = read_pos - floor(read_pos);
            a = wrap(floor(read_pos), 0, max_delay);
            b = wrap(a + 1, 0, max_delay);
            tap = linear_interp(buf[a|0], buf[b|0], frac);

            tap = lpf_op_simple(tap, damp);

            out = (in - tap) * gain;

            buf[ptr] = out;
            ptr = wrap(ptr+1, 0, max_delay);

            out;
        );
        
        size=in1; damp=in2;

        ap1_buf[0]+=0;
        ap2_buf[0]+=0;
        ap3_buf[0]+=0;

        sig = lpf_op_simple(in0, damp);
        ap1 = allpass_delay(sig, 0.7, 347 * size, ap1_buf);
        ap2 = allpass_delay(ap1, 0.7, 113 * size, ap2_buf);
        ap3 = allpass_delay(ap2, 0.7, 370 * size, ap3_buf);
        
        sig = ap3;

        x1_buf[0]+=0;
        x2_buf[0]+=0;
        x3_buf[0]+=0;
        x4_buf[0]+=0;

        x1 = fbcomb(sig, 0.773, 1687, damp, x1_buf);
        x2 = fbcomb(sig, 0.802, 1601, damp, x2_buf);
        x3 = fbcomb(sig, 0.753, 2053, damp, x3_buf);
        x4 = fbcomb(sig, 0.733, 2251, damp, x4_buf);

        s1 = x1 + x3;
        s2 = x2 + x4;
        o1 = s1 + s2;
        o2 = ((s1 + s2) * -1);
        o3 = ((s1 - s2) * -1);
        o4 = s1 - s2;

        left=o1+o3;
        right=o2+o4;

        out0=left;
        out1=right;
    "
).send;

Ndef(\schroeder).clear;
Ndef(\schroeder, {
    var test = Decay.ar(Impulse.ar(1), 0.25, LFCub.ar(1200, 0, 0.1));
    var sig = DynGen.ar(2, ~genDef, test, 100, 0.5).sanitize;
    sig;

});

Ndef(\schroeder).play;

);

I tihnk you might want to address EEL2 questions to the Cockos forum:

this is the subforum for jsfx/EEL2

I think it might be better to make a new thread for eel ideas then - moving this stuff off forum kills the momentum behind the project imo (plus there’s already a FAUST tag on here)

1 Like

Good thought - please do!

I have noticed that the memory of EEL2 is a bit more tricky than one expects and my understanding/example was wrong - see https://github.com/capital-G/DynGen/issues/51

I am still working on v0.3.0 which will break existing code b/c I will change how you pass audio inputs and parameters to DynGen - see https://github.com/users/capital-G/projects/4 for more information about the progress of v0.3.0.

I think it would be nice to keep to have a community within this forum which explores the possibilities of DynGen and EEL2 code.
I also intend to make this more interesting for usage within SC, which may cause that this diverges from JSFX (which at some point also could be added, but is not my priority currently).

1 Like

Ahhh that would be why my reverb attempt was doing weird pitch-shifting feedback. Thanks for this, I don’t think I saw the memory boundaries point in the official eel doc haha