supercollider

Why does EnvGen restart on every loop iteration and how to prevent this behavior?


How can I use EnvGen in a loop in such a way that it won't restart at every iteration of the loop?

What I need it for: piecewise synthesis. I want e.g. 50ms of a xfade between first and second Klang, then a 50ms xfade between second and third Klang, then a 50ms xfade between third and fourth Klang and so on, and I want this concatenation as a whole to be modulated by an envelope.

Unfortunately the EnvGen seems to restart from the beginning on every iteration of the loop that plays the consecutive Klang pairs. I want a poiiiiinnnnnnnnnng, but no matter what I try all I get is popopopopopopopopo.

2019 EDIT:

OK, since nobody would answer the "how to achieve the goal" question, I am now downgrading this question to a mere "why doesn't this particular approach work", changing the title too.

Before I paste some code, a bit of an explanation: this is a very simplified example. While my original desire was to modulate a complicated, piecewise-generated sound with an envelope, this simplified example only "scissors" 100ms segments out of the output of a SinOsc, just to artificially create the "piecewise generation" situation.

What happens in this program is that the EnvGen seems to restart at every loop iteration: the envelope restarts from t=0. I expect to get one 1s long exponentially fading sound, like plucking a string. What I get is a series of 100ms "pings" due to the envelope restarting at the beginning of each loop iteration.

How do I prevent this from happening?

Here's the code:

//Exponential decay over 1 second
var envelope = {EnvGen.kr(Env.new([1,0.001],[1],curve: 'exp'), timeScale: 1, doneAction: 2)};

var myTask = Task({

    //A simple tone
    var oscillator = {SinOsc.ar(880,0,1);};

    var scissor;

    //Prepare a scissor that will cut 100ms of the oscillator signal
    scissor = {EnvGen.kr(Env.new([1,0],[1],'hold'),timeScale: 0.1)};
    10.do({

        var scissored,modulated;

        //Cut the signal with the scisor
        scissored = oscillator*scissor;

        //Try modulating with the envelope. The goal is to get a single 1s exponentially decaying ping.
        modulated = {scissored*envelope};

        //NASTY SURPRISE: envelope seems to restart here every iteration!!!
        //How do I prevent this and allow the envelope to live its whole
        //one-second life while the loop and the Task dance around it in 100ms steps?
        modulated.play;

        0.1.wait;
    });
});

myTask.play;

(This issue, with which I initially struggled for MONTHS without success, actually caused me to shelve my efforts at learning SuperCollider for TWO YEARS, and now I'm picking up where I left off.)


Solution

  • You way of working here is kind of unusual.

    With SuperCollider, the paradigm shift you're looking for is to create SynthDefs as discrete entities:

    s.waitForBoot ({
        b = Bus.new('control');
        
        SynthDef(\scissors, {arg bus;
            var env;
            env = EnvGen.kr(Env.linen);
            //EnvGen.kr(Env.new([1,0.001],[1],curve: 'exp'), timeScale: 1, doneAction: 2);
            
            Out.kr(bus, env);
        }).add;
        
        
        SynthDef(\oscillator, {arg bus, out=0, freq=440, amp = 0.1;
            
            var oscillator, scissored;
            oscillator = SinOsc.ar(freq,0,1);
            scissored = oscillator * In.kr(bus) * amp;
            
            Out.ar(out, scissored);
        }).add;
        
        s.sync;
        
        Task({
            Synth(\scissors, [\bus, b]);
            s.sync;
            10.do({|i|
                
                Synth(\oscillator, [\bus, b, \freq, 100 * (i+1)]);
                0.1.wait;
            });
        }).play
    });
    
    

    I've changed for a longer envelope and a change in pitch, so you can hear all the oscillators start.

    What I've done is I've defined two SynthDefs and a bus.

    The first SynthDef has an envelope, which I've lengthened for purposes of audibility. It writes the value of that envelope out to a bus. This way, every other SynthDef that wants to use that shared envelope can get it by reading the bus.

    The second SynthDef has an a SinOsc. We multiply the output of that by the bus input. This uses the shared envelope to change the amplitude.

    This "works", but if you run it a second time, you'll get another nasty surprise! The oscillator SynthDefs haven't ended and you'll hear them again. To solve this, you'll need to give them their own envelopes or something else with a doneAction. Otherwise, they'll live forever.Putting envelopes on each individual oscillator synth is also a good way to shape the onset of each one.

    The other new thing you might notice in this example is the s.sync; lines. A major feature of SuperCollider is that the audio server and the language are separate processes. That line makes sure the server has caught up, so we don't try to use server-side resources before they're ready. This client/server split is also why it's best to define synthdefs before using them.

    I hope that the long wait for an answer has not turned you off permanently. You may find it helpful to look at some tutorials and get started that way.