Starting node with sustained envelope AND setting its duration?

Hi list,

I am wondering what would be the best way to start a Synth featuring a
sustained envelope and set its duration in one go?

Background:
SynthDefs with sustained envelopes seem especially suitable for playing
via MIDI as well as via Patterns making use of the latter’s \duration argument.
When playing them via other means (eg. routines) I understand that I have to release
the \gate argument to end their notes after a specific duration, as for
example in
g = Synth(\mySynth);
followed after some time by
g.release(1) OR g.set(\gate, 0)

Fixed-duration envelope synths are more convenient as their duration can
be set at node creation, and no reference to that node has to be kept.

Thanks for all ideas!
best, Peter

I think the easiest way to auto-release a gated envelope is by an event. You can use events in routines.

hjh

You can defer code execution by a certain amount of time, using .defer:

// Will release synth after 1.35 seconds:
var s = Synth(\mySynth);
{ s.set(\gate, 0); }.defer(1.35);

This kinda emulates a MIDI keyboard player releasing a key after 1.35 seconds.

Somehow, duration is set at creation, and no reference is needed once the release function has been deferred.

Thanks James and Dindoleon,

but is there no way to set the duration as part of the Synth.new()
arguments for sustained envelopes?

best, P

I don’t think you can set a sustained envelope duration. By definition, it is time independent. But I’m not sure about this.

Here’s another (quite inelegant) workaround:

(
SynthDef(\sustainOrNot, { |out = 0, freq = 220, dur = 0, gate = 1|
	var snd, manualEnv, autoEnv;
	
	snd = SinOsc.ar(freq, mul: 0.2);

	autoEnv = EnvGen.kr(
		Env(
			[0, 1, 1, 0],
			[0.1, dur, 0.1],
		),
		doneAction: Select.kr((dur > 0), [0, 2])
	);
	
	manualEnv = EnvGen.kr(
		Env(
			[0, 1, 0],
			[0.1, 0.1],
			releaseNode: 1
		),
		gate,
		doneAction: Select.kr((dur > 0), [2, 0])
	);

	snd = snd * Select.kr((dur > 0), [manualEnv, autoEnv]);
	snd = snd!2;
	Out.ar(out, snd);
}).add;
)

If \dur > 0, synth will free itself after said duration, otherwise it will sustain until \gate is set to 0.

No. Synth is a very low level, thin abstraction over server side synth nodes. It only constructs messages – it doesn’t do anything fancy. Scheduling future messages counts as fancy.

You could do it with my ddwPlug quark though.

hjh

1 Like

You could do something like:

(
SynthDef(\test, {|atk = 1, rel = 2, gate = 1, autoRelease = 0|
	var sig = SinOsc.ar(110) * 0.2;
	var endSynth = Line.kr(1, 1 - autoRelease, dur: atk, doneAction: 0);
	var env = Env.asr(atk, 1, rel).kr(2, gate * endSynth);
	Out.ar(0, sig * env ! 2)
}).add
)

// normal operation, no autorelease
x = Synth(\test)
x.set(\gate, 0)

// with autorelease
x = Synth(\test, [autoRelease: 1])

But of course this means adjusting the synthdefs in question

Thanks again you two,

last thought: Couldn’t I set \gate to zero from within the synth node in
some way?

cheersz, P

That is what my code example is doing. This was done in a way where you could also use the synth in a sustaining way. If you only want to autorelease it can be simplified a bit. Of course, as you probably already know, this is much easier if you use Env.perc but I assume you don’t want to do that, maybe because you wish to use more elaborate envelopes?

The problem falls into the general category of plugging an arbitrary signal into a simple synth argument, without adjusting the SynthDef.

As of last January, there’s a quark for that (ddwPlug):

// with NO edits to the default synthdef,
// now gate gets a kr signal that holds a gate open for 2 seconds
x = Syn(\default, [gate: Plug { Trig1.kr(1, 2) }]);

// or, for improved efficiency
// so that you don't build the gate synth every time

SynthDef(\holdGate, { |out, time = 1|
	Out.kr(out, Trig1.kr(1, time))
}).add;

x = Syn(\default, [gate: Plug(\holdGate, [time: 2])]);

Nobody’s using it yet because it doesn’t advertise specific use cases, but it’s useful for dozens of different things, among them polyphonic modulators (envelopes, LFOs, whatnot), this case where the signal is a gate, even signal routing (e.g. switching the effect synth that’s applied to source synths, per source synth). You can use Syn in place of Synth for normal uses, and once you’re in that habit, you can use Plug arguments whenever you want (or not at all). So it reduces the cognitive overhead to switch between fixed numeric values and signals in any synth at any time.

But it hasn’t caught on.

hjh

1 Like

Yes this does seem like the perfect (or a perfect) use case for Syn. I will keep the quark in mind for things like this.

Thans again everyone!
Thor’s vanilla solution

SynthDef(\test, {|atk = 1, rel = 2, gate = 1, autoRelease = 0|
var sig = SinOsc.ar(110) * 0.2;
var endSynth = Line.kr(1, 1 - autoRelease, dur: atk, doneAction: 0);
var env = Env.asr(atk, 1, rel).kr(2, gate * endSynth);
Out.ar(0, sig * env ! 2)
}).add
)

// normal operation, no autorelease
x = Synth(\test)
x.set(\gate, 0)

// with autorelease
x = Synth(\test, [autoRelease: 1])

quite suites my needs! Now I am trying to figure
out how I could specify a duration until gate is released that is longer than the
attack segment of the actual envelope. If anyone has an elegant
proposition i’d be happy to know.
cheersz, Peter

That is very easy, just create a synth input (you could name it ‘time’) and plug that into ‘endSynth’ instead of ‘atk’.

I just reread your original question.

If you want to have synthdefs which are ‘general’ and not customized to very specific use-case (which I think is recommendable for a variety of reasons), consider these examples:

( // The simple version without the autorelease synth input. I added a few extra inputs for flexibility
SynthDef(\simpleSynth, {|freq = 220, atk = 0.1, rel = 0.3, gate = 1, amp = 0.5, out = 0|
	var env = Env.asr(atk, 1, rel).kr(2, gate);
	var sig = SinOsc.ar(freq) * 0.2;
	sig = sig * env * amp ! 2;
	Out.ar(out, sig)
}).add
)

(
/// PLAY THE SYNTH FROM A MIDI KEYBOARD
MIDIClient.init;
MIDIIn.connectAll;
/// Create an array of size 128, initially all indices = nil. Each index corresponds to a midi note number in the range 0-127
~synths = {nil}!128;

MIDIdef.noteOn(\noteOn, {|vel, nn, chan| 
	~synths[nn] = Synth(\simpleSynth, [freq: nn.midicps]) 
});

MIDIdef.noteOff(\noteOff, {|vel, nn, chan| 
	~synths[nn].set(\gate, 0);
	~synths[nn] = nil
});
)

/// PLAY THE SYNTH WITH A PATTERN WITH DIFFERENT SUSTAIN TIMES USING EVENTS
/// See the help file for Event

(instrument: \simpleSynth, sustain: 0.1, amp: 0.5).play
(instrument: \simpleSynth, atk: 2, rel: 2, sustain: 4, freq: 52.midicps, amp: 0.5).play // 2 secs atk, 2 sec hold, 2 sec rel

/// USING A ROUTINE
(
{
	var x = Synth(\simpleSynth, [atk: 2, rel: 2, freq: 52.midicps]);
	4.wait;
	x.set(\gate, 0)
}.fork
)

/// USING A PATTERN
/// I highly recommend reading the Pattern Guide (search for it in the help browser)
(
Pbind(
	\instrument, \simpleSynth,
	\dur, Pseq([1]),
	\freq, 52.midicps,
	\atk, 2,
	\rel, 2,
	\amp, 0.5,
	\sustain, 4 // try changing the sustain value
).play
)

// OR

(instrument: \simpleSynth, atk: 2, rel: 2, dur: Pseq([1]), sustain: 4, freq: 52.midicps, amp: 0.5).asPattern.play