Hi. I am working through code examples from Eli Fieldsteel’s book and came across what I think is a strange quirk, not a quark, but a quirk:
Code Example 2.20: Use of trigger-type arguments to create retiggerable fixed-duration envelope
(
x = { |t_gate = 0|
var sig, env;
env = EnvGen.kr(
Env.new(
[0, 1, 0],
[0.02, 0.3],
[0, -4],
),
t_gate,
);
sig = SinOsc.ar(350) * 0.3;
sig = sig * env;
sig = sig ! 2;
}.play;
)
x.set(\t_gate, 1); // evaluate repeatedly
x.free; // free when finished
I then start working through Companion Code 2.2 and it gives an ASR envelope:
(
// An ADSR envelope without the decay segment. Similar to 'linen', but capable of sustaining at its sustain level.
Env.asr(
attackTime: 1,
sustainLevel: 1,
releaseTime: 1,
curve: 0
).plot;
)
That is fine and evaluation works well. And there are examples of several envelopes which work with the t_gate example. However, I use this modified example below which I have edited to make it work. But if you comment out the lines with gate and make t_gate active, it no longer works.
(
x = {//|t_gate=1|
|gate=0|
var sig,env;
env = EnvGen.kr(
Env.asr(1,1,1,0
),
//t_gate,
gate,
);
sig = LFSaw.ar(330)*0.3;
sig = sig*env;
sig = sig!2;
}.scope;
)
x.set(\t_gate,1);
x.set(\gate,1);
x.set(\gate,0);
x.free;
There are two types of envelope: timed envelopes (where the releaseNode is nil) and gated envelopes (where the releaseNode points to the levels entry at which the envelope should sustain, IIRC, didn’t check just now).
When the gate input falls to 0, a gated envelope will stop what it’s doing and immediately proceed from the releaseNode forward. It doesn’t wait for the current envelope segment to finish.
A t_gate input, when set to 1, will remain at 1 for only one control block duration ~= 1.45 ms at 44.1 kHz. Your envelope has a 1 second attack, so the envelope has time to rise only to 0.00145 before the gate closes and the envelope releases. That’s quieter than -50 dB, so you’re not going to hear very much.
If you want to use a t_gate with a gated envelope, a good companion UGen is Trig1.kr, which will hold the trigger for a given amount of time.
Thanks for the reply. So many questions. But to confirm and ask another question, are your numbers as follows:
44,100 * 1.45ms = 44,100 * 0.00145 = 63.945
~64 samples = driver block size as might show up in the Post window when you boot the server?
I apologize if this should be in a new post. This made me wonder, does this have anything to do with a function evaluated and making a sound with an open envelope have anything to do with the sample size of the running server? Using as similar example from above, now changing the sustain level to 100, why does the evaluated code below have a gentler sounding attack compared to when the t_gate = 1 using x.set? See the code below:
(
x = {|t_gate=1|
//|gate=0|
var sig,env;
env = EnvGen.kr(
Env.asr(1,100,1,0
),
t_gate,
//gate,
);
sig = LFSaw.ar(330)*0.3;
sig = sig*env;
sig = sig!2;
}.scope;
)
x.set(\t_gate,1);
x.free;
So far, I have found this to be the case with similar evaluated functions and x.set.
I appreciate all the answers I am getting here as there is so much material to cover in the docs, online, and the book. It is hard to even follow every little detail even in the book. I found the part in the book before Example Code 2.20 where it talks about the releaseNode. And by reading it again and looking at the auto-complete that comes up when typing code, it started to make sense.
So, while trying to figure out this problem, I did not really understand jamshark70’s reply about the block size having an effect on this use of the Env.asr envelope. I could not find a good example that demonstrated the issues I was having. Here is some more code to demo what I am talking about:
(
x = {|t_gate=1|
//|gate=0|
var sig,env;
env = EnvGen.kr(
Env.asr(1,50,10,-4 // play with the #'s for differences!
),
t_gate,
//gate,
);
sig = LFSaw.ar(330)*0.3;
sig = sig*env;
sig = sig!2;
}.scope;
)
x.set(\t_gate,1);
x.set(\gate,1);
x.set(\gate,0);
x.free;
With a regular envelope with attack, sustain, release, usually you get a duration in seconds, for times, and a level for sustain. Fairly standard. But if you experiment with the numbers for Env.asr above, 1,50,1,-4 gives a decent volume level for the sound. But make it 10,50,1,-4 and the volume level is nowhere near the same. It is much lower. With an ASR envelope on a regular synth, according to my understanding, you would still get a rise to the same level but it would take 10 seconds instead of 1 but, you would get there.
So, I did not understand much about what jamshark70 said above. So, I went looking in the Env.asr stuff and did not find an answer. I went online looking for an ASR envelope explanation for a standard system. Nothing new there. Then I went Cmd+D on EnvGen and was brought to the Browser help and there it was, no definitive answer but this:
Discussion:
NOTE: The actual minimum duration of a segment is not zero, but one sample step for audio rate and one block for control rate. This may result in asynchronicity when in two envelopes of different number of levels, the envelope times add up to the same total duration. Similarly, when modulating times, the new time is only updated at the end of the current segment - this may lead to asynchronicity of two envelopes with modulated times.
So, now I feel I can start to understand a little bit about what is going on with this situation. I feel a little like this guy with his TV show, 'cause SC has lots of it: https://youtu.be/2zIqyiUlD4Q?si=Jb8M4ZnxmJ4le6cB
I have only 3 minutes for a reply right now… could say more later perhaps.
IMO you’re adjusting the wrong parameters.
The thing you want to do is to make the gate longer. Then you can control how long it sustains independent of attack and release time.
Trying to use attack/release and sustain level to control volume, while maintaining only a 1.45 ms gate-open time, is a guaranteed recipe for confusion.
(
{ |t_gate = 1|
var env = EnvGen.kr(
// set these for the fade times you want
Env.asr(0.05, 1, 0.1),
Trig1.kr(t_gate, 0.25), // here is the sustain duration!
);
LFSaw.ar(330) * env
}.plot(0.35)
)
This is true, but it’s not the reason why I mentioned control blocks. The reason is because of the definition of a t_ trigger control input. t_ inputs are inherently control rate – there is no way to have an audio rate trigger input. (Why? This, I’m not sure.) t_ inputs are also designed so that, when you set it to a non-zero value, it will to reset to zero as quickly as possible after outputting the non-zero value for the minimum possible amount of time. At control rate, this minimum possible time is the duration of a control block. (It doesn’t matter if you call it t_gate – it’s still a short-as-possible trigger.)
So the issue you were experiencing has nothing really to do with any EnvGen limitations.
Let’s look at what happens with asr if you use a gate that is longer than the attack time – here, using linear envelope segments to make the math simpler:
(
p = {
var t_trig = NamedControl.tr(\t_trig, 1);
var g = Trig1.kr(t_trig, 0.2);
[
g,
EnvGen.kr(Env.asr(0.1, 1, 0.1, \lin), g)
]
}.plot(0.4)
)
What happened to the sustain segment? The sustain part can only begin after the attack completes, but the gate is so short that the attack was not allowed to complete. So EnvGen does the standard thing: when the gate releases, it starts moving toward zero from its current value, which in this case is halfway through the attack segment.
With that background, we can take a look at the original case of a short-as-possible trigger serving as a gate:
(
p = {
var t_trig = NamedControl.tr(\t_trig, 1);
var g = t_trig; // no gate mod
[
g,
EnvGen.kr(Env.asr(0.1, 1, 0.1, \lin), g)
]
}.plot(0.4)
)
p.value[1].maxItem // 2nd plot max
-> 0.014512471854687
And, as was initially reported, the envelope barely rises above 0. (If the attack goes from 0 to 1 in 0.1 seconds, its slope is 10 units per second. One control block at 44.1 kHz with default settings = 64/44100 = 0.0014512471655329 seconds, times 10 units/second = 0.01451247something units, matching the empirical result.)
The original examples use a curve factor = -4, which will rise more quickly at the beginning.
It’s true that you can raise the sustain level of the ASR, and this will increase the slope of the attack, thus increasing the peak level that will be reached within that control block. But…
That is true only if you hold the gate open for the entire attack time. But your examples are not doing this. That is, if your attack time is 1 and you hold the gate open for 1 second you should reach the normal maximum. If you increase the attack time to 10 seconds and you hold the gate open for only 1 second, the EG will not reach the maximum. This is true in SC, VCV Rack, Vital, Serum, Surge etc etc.
{ }.play or .scope inserts an envelope automatically. This envelope softens the beginning of the sound. When you x.set(\t_gate, 1) later, this envelope is already open so the later sounds are not softened.
Awesome stuff. Thank you for taking the time to explain this. I just had a quick read and will have to go back to explore it more. I think the problem I was having before is the idea of gates and envelopes together. And your explanation of holding the gate open to make the envelope effective made that more clear to me. There is still a lot more to learn with things but I am getting there.
I was thinking about the GATE+TRIG options on the Roland SH-01A last night and that relates to this. I will have to play with that some too.