Gated synths: tb303 / PR or external option pack?

Hi,

I am working on an option pack for gated synths. I do understand why SP synths should normally by self-terminating, but for some situations a gated synth may come in handy. For me, the most notable situation for a gated synth is playing a MIDI keyboard.

I have prepared most of them, but I have 2 questions for the core dev team:

  • I am struggling with the tb303. I think that the sonic-pi-tb303.scsyndef file that is shipped with SP can not have been compiled from the defintion in retro.clj, as the definition in that file uses the env-adsr-ng envelope which does not support all the shape params that the shipped synth evidently does. If I am right, can the source file for the synthdef be found somewhere?

  • Once finished, what should be done with them? I could zip them up, put them on some web page, and create a reference here in the forum. I could also put them in the proper places in the code tree, update synthinfo.rb (just pointing out the particularities of the gate and referring to the non-gated docs for the rest of the options), and prepare a PR. I’d much prefer the PR way, but I’d like to get your opinion about this.

Cheers, Ben

Just an update and a ping: I think I have built a working gated tb303, so I could go ahead now. Any info whether to submit a PR or keep it outside the main code tree is much appreciated :slight_smile:

Hiya,

gated synths are definitely something to consider. How do you operate it and also how do you ensure that it is ultimately terminated - or is it possible to trigger many of them and create a CPU-leak?

I’d love to see some code snippets of how you solved these isses!

Hi Sam!

Infact, these gated synths do not terminate, unless the gate is pulled to 0. So, if you play them and don’t catch the handle, there will be (at least to my knowledge) no way of closing the gate and terminating them. So … dangerous stuff. That’s why I wanted your opinion about whether it should be PR or ext. option pack.

Usage would be something like this:

load_synthdef "~/music/sonic_pi/synthdefs/compiled/sonic-pi-winwood_lead_gated.scsyndef"
use_synth 'sonic-pi-winwood_lead_gated'
a = play 60
sleep 4
control a, gate: 0

In this example, if the gate is duely closed, then the synth terminates. So, the run in above the example ends:

=> Completed run 4
=> All runs completed
=> Pausing SuperCollider Audio Server

But there is no timeout for auto-terminating or any such mechanism. What I have done is the following: In core.clj I have added a gated-shaped-adsr.

(defn gated-shaped-adsr
  "Gated adsr envelope with shape"
  ([attack
    decay
    sustain
    release
    attack_level
    decay_level
    sustain_level
    env_curve
    release_node]
   (gated-shaped-adsr attack decay sustain release attack_level decay_level sustain_level env_curve release_node 0))
  ([attack
    decay
    sustain
    release
    attack_level
    decay_level
    sustain_level
    env_curve
    release_node
    min]
  [min 4 release_node -99
   attack_level  attack  env_curve 0
   decay_level   decay   env_curve 0
   sustain_level sustain env_curve 0
   min           release env_curve 0] )

  ([attack
    decay
    sustain
    release
    init_level
    attack_level
    decay_level
    sustain_level
    release_level
    env_curve
    release_node]
  [init_level 4 release_node -99
   attack_level  attack  env_curve 0
   decay_level   decay   env_curve 0
   sustain_level sustain env_curve 0
   release_level release env_curve 0] ))

Basically, this adds the release node where the envelope is held until the gate is released. Then, for example, the beep synthdef in basic.clj (in a different file, like basic_gated.clj or so) looks like this:

(defsynth sonic-pi-beep_gated [note 52
                        note_slide 0
                        note_slide_shape 1
                        note_slide_curve 0
                        amp 1
                        amp_slide 0
                        amp_slide_shape 1
                        amp_slide_curve 0
                        pan 0
                        pan_slide 0
                        pan_slide_shape 1
                        pan_slide_curve 0
                        attack 0
                        decay 0
                        sustain 0
                        release 1
                        attack_level 1
                        decay_level -1
                        sustain_level 1
                        env_curve 1
                        gate 1
                        out_bus 0]
(let [decay_level (select:kr (= -1 decay_level) [decay_level sustain_level])
        note        (varlag note note_slide note_slide_curve note_slide_shape)
        amp         (varlag amp amp_slide amp_slide_curve amp_slide_shape)
        amp-fudge   1
        pan         (varlag pan pan_slide pan_slide_curve pan_slide_shape)
        freq        (midicps note)
        snd         (sin-osc freq)
        env         (env-gen:kr (core/gated-shaped-adsr attack decay sustain release attack_level decay_level sustain_level env_curve 3) gate :action FREE)]
    (out out_bus (pan2 (* amp-fudge env snd) pan amp))))

Here, the new gated-shaped-adsr is used, the release node is set to 3 and the env-gen gets an additional param gate.

I you prefer the full fiiles (and the compiled ones), just give me a shout … Re PR or not, either option is fine with me.

Cheers, Ben

Hi @bmx,

yeah, that was my assumption. It’s the main reason I haven’t introduced long running synths yet. It needs to be something that works in a classroom with 10 year old kids and a teacher that doesn’t need to understand that synths that are triggered but not caught and explicitly terminated will cause the system to slowly run out of CPU.

I think there might be a solution along the lines of live_audio which is actually a long running synth but only one of them can exist at once - and each have a unique name. It definitely needs a lot of careful thought.

Happy to discuss ideas on how to solve this though.

Hi Sam,

That’s what I was thinking, too. I’ll be running a Girl’s Day (for girls in technology) soon, and I certainly wouldn’t like to be looking for gone astray synths with them :wink:

I haven’t looked into the live_audio code, but I’m not sure if this would be a solution. One fun thing with gated synths is mapping them to a keyboard and hitting many keys at a time.

Anyway, I think it’s clear then. For the time being, I think an option pack is enough and a deep solution is currently not worth the while. I’ll wrap the stuff together, put it in a repo on GitHub, and make a reference to it here (unless you suggest a better a place for a link to that pack). If against expectations gated synths become a roaring success and you get nagged for including them in main, then there’ll time enough to think about how to do it, and I’ll be happy to help.

On a side topic: Is there a way to list all running synths? This could be a way of emergency catching runaway synths.

Hello, I have solved the gate problem, changing it to Line.kr(1,0, value), doneAction: 2).
For value I have used the sum of attack, decay, sustain and release, so that the gate works exactly
the total time that the note is sounding.

This is the code for a synthesizer:

(
SynthDef(\Mybassfoundation,{|out= 0 note = 52 amp = 0.1 cutoff= 1000 rq=0.5 pan=0.0 attack=0.01 decay= 0.0 sustain=0.9 release=0.05 |
var osc, filter, env, filterenv;
osc = Saw.ar(note.midicps);
filterenv = EnvGen.ar(Env.adsr(0.0,0.5,0.2,1),1,doneAction:2);
filter = RLPF.ar(osc,cutofffilterenv+100,rq);
env = EnvGen.ar(Env.adsr(attack,decay,sustain,release),Line.kr(1,0,attack+decay+sustain+release),doneAction: 2);
Out.ar(out,Pan2.ar(filter
envamp2,pan));
}).writeDefFile(“/home/raulmate/Documentos/Synth_Supercollider/”)
)

Hi, thanks a lot. I may be dense, but I can’t see how this solves the gate problem. I suggest that the gate problem is not so much a technical problem but a design decision which deals with the question of how to prevent less expert users from filling up processor and memory with runaway synths. Also, I fail to find the actual gate in your synthdef, so it won’t wait for the gate to be set to 0, will it? :wink:

Hello, in principle there has been an error in the code, the correct one is:

‘’’
(
SynthDef(\Mybassfoundation,{|out= 0 note = 52 amp = 0.1 cutoff= 1000 rq=0.5 start_pan= 0 finish_pan= 0 attack= 0.01 decay= 0.0 sustain= 0.9 release= 0.05 |
var osc, filter, env, filterenv, gatevalue;
gatevalue = attack+decay+sustain+release;
osc = Saw.ar(note.midicps);
filterenv = EnvGen.ar(Env.adsr(0.0,0.5,0.2,0.2),gatevalue,doneAction:2);
filter = RLPF.ar(osc,cutofffilterenv+100,rq);
env = EnvGen.ar(Env.adsr(attack,decay,sustain,release),Line.kr(1,0,gatevalue),doneAction: 2);
Out.ar(out,Pan2.ar(filter
envamp2,Line.kr(start_pan,finish_pan,gatevalue)));
}).add
)
‘’’

The gate is the Ugen Line.kr(), it is used to vary the value of a variable between two values, in a determined space of time.
In this case it substitutes the value of gate, and Line.kr(1,0, time), determines how long the gate duration will be with value 1, to
to take the value 0.

On the other hand, in this new version, I have varied the bread, using another Line.kr(), it can be used to modify the bread or whatever
stable. start_pan sets the starting pan, and finish_pan the ending pan.

In my field tests with sonic pi, the CPU consumption does not exceed 1.5%

Kind regards :smiley:

[

Hmmh, I still can’t see how the gate can be controlled once the synth has started. There is no gate param that can be manipulated from the outside.

BTW: I think you are trying to markup your code. You need 3 backticks at the beginning and the end for code block, like this: ```

(
SynthDef(\Mybassfoundation,{|out= 0 note = 52 amp = 0.1 cutoff= 1000 rq=0.5 start_pan= 0 finish_pan= 0 attack= 0.01 decay= 0.0 sustain= 0.9 release= 0.05 |
var osc, filter, env, filterenv, gatevalue;
gatevalue = attack + decay + sustain + release;
osc = Saw.ar(note.midicps);
filterenv = EnvGen.ar(Env.adsr(0.0,0.5,0.2,0.2),gatevalue,doneAction: 2);
filter = RLPF.ar(osc,cutoff * filterenv + 100,rq);
env = EnvGen.ar(Env.adsr(attack,decay,sustain,release),Line.kr(1,0,gatevalue),doneAction: 2);
Out.ar(out,Pan2.ar(filter * env * amp * 2,Line.kr(start_pan,finish_pan,gatevalue)));
}).add;
)

Original
env = EnvGen.ar(Env.adsr(0.01,0.0,0.9,0.05), gate , doneAction:2);

Modifying gate
env = EnvGen.ar(Env.adsr(0.01,0.0,0.9,0.05), Line.kr(1,0, gatevalue ), doneAction:2);

gatevalue = attack + decay + sustain + release

The Code from the above post has fixed editing issues that were removing asterisks when editing the post.
I’ve tested the synth both on supercollider, using a midi keyboard; as with sonic pi through a loop, and I can affirm that the gate is set to zero.

In fact, when using a keyboard, the note stops sounding even if it is still pressed on the keyboard, because time elapsed is greater than that set by the gatevalue variable, which is the sum of attack, decay, sustain, and release.

Finally I have made a test to the contrary, modifying the Line.kr(1,0,gatevalue) to Line.kr(0,1,gatevalue) and in that In this case, the note begins to sound when the determined time elapses and even if you stop pressing the key, it continues to sound.

Greetings

Hi Raul, maybe we are just not talking about the same thing :slight_smile: If when playing it with a keyboard, the note stops before the key is released, then it’s not a gated synth – at least it does not work as I would expect, which would be similar to playing a conventional synthesizer’s keyboard. Instead, that is the normal behavior that you would get from a terminating, non-gated EnvGen, that standard Sonic Pi synths as well.

Just another idea regarding where to put the gated synths: How about making a PR, that includes the source and compiled versions somewhere under etc/synthdefs/, but not including the synths in synthinfo.rb. This way more experienced users could easily find and use them, while the gated synths remain inaccessible for less experienced users.