Turn off unused BinaryOpUgens in an array (Granular synthesizer)

Hi scsynth! This is my first post here and I could not find a topic about this problem.
I’m working on a client-side granular synthesizer which invokes BinaryOpUgens (grains), one after the other, contained in an array. Is there a way to turn off the unused grains? By turning off I mean interrupting/stoping calculation of a BinaryOpUgens to save on cpu à la Pure Data’s [switch~].

Howdy and welcome!

I don’t believe you can pause or sleep UGens inside a Synth graph sadly…

You can however pause and resume individual Synth Nodes using the .run method- so one possibility is to refactor your array of UGens as an array of Synths…

Hello! Welcome!

Do you mean server-side granular synthesis? If you’re working on the client side, you don’t need an array of Ugens; you would be creating synths. If each synth (grain) uses a simpler SynthDef, you’re good; it’s already optimized.

If you’re doing this inside the SynthDef, it depends on what you’re doing whether you can refactor it. Maybe you can paste the code here. But as semiquaver said, you can’t change the graph. You need all signals running.

@semiquaver
Hello, thanks! I’ll look into this .run method and most likely put the server side experimentation aside, thanks for the suggestion.

@smoge
Hello, thank you! Yes I meant server-side. Yeah I’ll definitely look into the client-side way of granulation :slight_smile: Thank you.

As for the code:

(

{
	arg fundamental = 40;
	var numGrains = 40;
	var sine = Signal.sineFill(8192, [0, 1]);
	var buffer = LocalBuf.newFrom(sine);
	var bufSize = BufFrames.kr(buffer);
	var fundTrig = Impulse.ar(fundamental);
	var grains = Array.fill( numGrains, { | i |
		var perGrainTrig = PulseDivider.ar(
			trig: fundTrig,
			div:numGrains,
			start: i);
		
		var perGrainPhase = Sweep.ar(
			trig: perGrainTrig,
			rate: bufSize * fundamental
		);
		
		var window = EnvGen.ar(
			envelope: Env(
				levels: [0, 1, 0],
				times: [0.5 / fundamental, 0.5 / fundamental]
			), gate: perGrainTrig
		);
		
		var grain = BufRd.ar(
			numChannels: 1,
			bufnum: buffer,
			phase: perGrainPhase
		) * window;
		
		grain;
		
	};
	);
	
	Mix.ar(
		array: grains
	)!2;
	
	
}.play;
)

The basic “template” is something like this:

(

SynthDef(\grain,
    { arg out=0, freq=440, sustain=0.05;
        var env;
        env = EnvGen.kr(Env.perc(0.01, sustain, 0.3), doneAction: Done.freeSelf);
        Out.ar(out, SinOsc.ar(freq, 0, env))
    }).add;
)


(
Tdef(\x, {
    loop({
        Synth(\grain, [\freq, rrand(900, 1140)]);
        rrand(0.001, 0.05).wait;
    })
})
)

Tdef(\x).play;

In your case, you would work with more SynthDefs, according to some logic.

It can be any Routine(s). For example, this one from Rational quark helfile, with 3 parallel routines modulated by another one, giving a sense of form. (It uses Rational numbers just to show they are fast enough )

(
var is, f, l, ds, p, ps, next_i;

s.waitForBoot {

    SynthDef(\blip, { | out, freq = 440, amp = 0.05, nharms = 10, pan = 0, gate = 1 |
        var audio = Blip.ar(freq, nharms, amp);
        var env = Linen.kr(gate, doneAction: 2);
        OffsetOut.ar(out, Pan2.ar(audio, pan, env) );
    }).add;

    is = [ 8%/15, 5%/9, 9%/16, 3%/5, 5%/8, 2%/3, 45%/64, 32%/45, 3%/4, 4%/5, 5%/6, 8%/9, 9%/10, 15%/16, 16%/15, 10%/9, 9%/8, 6%/5, 5%/4, 4%/3, 45%/32, 64%/45, 3%/2, 8%/5, 5%/3, 16%/9,9%/5, 15%/8 ];

    ds = Array.fill2D(25, 25, { | r, c | (c+11) %/ (r+13) }).flat.pow(pi) / pi;

    ps = Array.geom( 7, 4 %/ 1, 4 %/ 7 );

    f = rrand( 20, 16000 );

    l = {
        loop {
            next_i=is.choose;
            if ( f <= 200  ) { next_i = max(next_i,next_i.reciprocal) };
            if ( f >= 2600 ) { next_i = min(next_i,next_i.reciprocal) };

            " -> ".post;
            f = f * next_i.post.asFloat;
            " -> ".post;

            (
                instrument: \blip,
                freq:       f.post,
                nharms:     rrand(1,4),
                dur:        is.choose.asFloat,
                pan:        rrand(-0.75,0.75),
                amp:        rrand(0.01,0.1)
            ).play;

            " Hz\n    dur: ".post;
            (ds.choose.postln * p).asFloat.wait;

        }
    };


    fork {
        loop {
            p = ps.choose;
            Post << format("\n\n========== Tempo % ==========\n\n", p);
            rrand(3,13.0).wait;
        }
    };

    s.sync;

    { l.fork } ! 3
}
)

I might be wrong, but it looks like you could just use GrainBuf.

Thanks, yes I learned about the server-side approach of granular synthesis through the first example you sent. The second example is very interesting, I’m still learning about Tasks and Routine, thank you.

@jordan GrainBuf is really good but not flexible enough. I would like to dynamically and smoothly change the window and experiment with additive synthesis in the grain (inspired by Trainlet synthesis)

2 Likes

maybe helpful to learn about routines

2 Likes