Passed Envelopes via Pbind \time

hey, i was wondering how i get the same behaviour via Pbind with the passed_Env SynthDef compared to the synthDef_Env SynthDef and how to approach this topic in general. any other advices? thanks :slight_smile:

(
// create a new event type called hasEnv
// which checks every parameter whose key ends in Env or env:
// - convert non-env values to envs (e.g 0 becomes Env([0,0],[dur]))
// - stretch envelope to last for the event's sustain (converted from beats to seconds)
~utils = ();
~utils.hasEnv = {
    // calc this event's duration in seconds
    var durSeconds = ~dur * ~legato / thisThread.clock.tempo;
    // find all parameters ending in env or Env
    var envKeys = currentEnvironment.keys.select{|k|"[eE]nv$".matchRegexp(k.asString)};
    envKeys.do{|param|
        var value = currentEnvironment[param];
        if (value.isArray.not) { value = [value] };
        value = value.collect {|v|
            // pass rests along...
            if (v.isRest) { v } {
                // convert non-env values to a continuous, fixed value env
                if (v.isKindOf(Env).not) { v = Env([v, v], [1]) }
            };
            // stretch env's duration
            v.duration = durSeconds;
        };
        currentEnvironment[param] = value;
    };
};

Event.addParentType(\hasEnv,(
    finish: ~utils[\hasEnv]
));
)


(
SynthDef(\synthDef_Env, {
	arg out=0, freq=500, mRatio=1, cRatio=1, index=1, iScale= 5,
	amp=0.2, widthMod=1, atk=0.01, rel=3, cAtk=4, cRel=(-4), width=0.25, time=1;

	var sig, mod, car;
	var iEnv, gainEnv;

	iEnv = EnvGen.kr(
		Env.new(
			[index, index * iScale, index],
			[atk, rel],
			[cAtk, cRel]
		)
	);

	gainEnv = EnvGen.kr(
		Env.perc(atk, rel, curve: [cAtk, cRel]),
		doneAction: 2
	);
	
	mod = SinOsc.ar(freq * mRatio, mul: iEnv);
	car = SinOsc.ar(freq * cRatio, mod.wrap(0, 4pi));

	sig = car * gainEnv * amp;

	sig = Limiter.ar(sig, 0.95);
	sig = LeakDC.ar(sig);
	Out.ar(out, sig);
}).add;
)

(
SynthDef(\passed_Env, {
	arg t_gate=1, out=0, freq=500, mRatio=1, cRatio=1, index=1, iScale= 5,
	amp=0.2, widthMod=1, atk=0.01, rel=3, cAtk=4, cRel=(-4), width=0.25, time=1;

	var sig, mod, car;
	var gainEnv = \gainEnv.kr(Env.newClear(8).asArray);
	var iEnv = \iEnv.kr(Env.newClear(8).asArray);

	// freq Envelope
	iEnv = EnvGen.kr(iEnv, timeScale:time);

	// amp envelope
	gainEnv = EnvGen.kr(gainEnv, t_gate, timeScale:time, doneAction:2);

	mod = SinOsc.ar(freq * mRatio, mul: iEnv);
	car = SinOsc.ar(freq * cRatio, mod.wrap(0, 4pi));

	sig = car * gainEnv * amp;

	sig = Limiter.ar(sig, 0.95);
	sig = LeakDC.ar(sig);
	Out.ar(out, sig);
}).add;
)

(
Pdef(\synthDef_Env,
	Pbind(
		\instrument, \synthDef_Env,

		\midinote, Pseq([
			Pseq([38,41,48,50],1),
			Pseq([43,46,53,58],1),
			Pseq([38,41,48,50],1),
			Pseq([43,46,58,53],1)
		],inf),

		\dur, 0.875,
		\legato, 0.80,
		\atk, 0.01,

		//Phase Modulation
		\mRatio, 1,
		\cRatio, 1,
		\index, 1,
		\iScale, 5,

		\amp, 0.15,
		\out, 0,
	)
).play;
)

Pdef(\synthDef_Env).stop;

(
Pdef(\passed_Env,
	Pbind(
		\type, \hasEnv,
		\instrument, \passed_Env,

		\midinote, Pseq([
			Pseq([38,41,48,50],1),
			Pseq([43,46,53,58],1),
			Pseq([38,41,48,50],1),
			Pseq([43,46,58,53],1)
		],inf),

		\dur, 0.875,
		\legato, 0.80,
		\atk, 0.01,
		\sus, 3,

		\time, Pfunc { |ev| ev.use { ~sus.value } / thisThread.clock.tempo },

		\gainEnv, Pfunc{|e|
			var rel = (1 - e.atk);
			var cAtk = exprand(2,4);
			var cRel = exprand(-2,-4);
			Env([0,1,1,0],[e.atk,e.sus,rel],[cAtk,0,cRel])
		},

		//Phase Modulation
		\mRatio, 1,
		\cRatio, 1,
		\index, 1,
		\iScale, 5,

		\iEnv, Pfunc{|e|
			var rel = (1 - e.atk);
			var cAtk = exprand(2,4);
			var cRel = exprand(-2,-4);
			Env([e.index, e.index * e.iScale, e.index],[e.atk,rel],[cAtk, cRel])
		},

		\amp, 0.15,
		\out, 0,
		\finish, ~utils[\hasEnv]
	)
).play;
)

Pdef(\passed_Env).stop;

First thing, wrap the Envs in arrays. It’s documented somewhere.

Actually that was wrong advice – in a Pfunc, it’s not necessary to wrap in an array.

I’ll have to look more carefully, a bit later.

hjh

OK, let me try another wild guess.

The hasEnv event type scales the envelope to match the event duration – time-scaling, one time.

Then you pass the number of seconds to time, to use as a time scaler – time-scaling, again.

So I guess the result time would be the square of the actual time.

If you’re using \time and timeScale:, then make sure the envelope passed in has a duration of 1.

hjh

thanks for your explanations :slight_smile: now im just scaling once via hasEnv but the result is still different. the passend envelope Pbind does have a shorter release. Im just not sure how to scale atk=0.01 and rel=3 to match the time-scaling of 1. The envelope duration of the SynthDef_Env is longer then the \dur of the Pbind, or?

(
SynthDef(\synthDef_Env, {
	arg out=0, freq=500, mRatio=1, cRatio=1, index=1, iScale= 5,
	amp=0.2, widthMod=1, atk=0.01, rel=3, cAtk=4, cRel=(-4), width=0.25, time=1;

	var sig, mod, car;
	var iEnv, gainEnv;

	iEnv = EnvGen.kr(
		Env.new(
			[index, index * iScale, index],
			[atk, rel],
			[cAtk, cRel]
		)
	);

	gainEnv = EnvGen.kr(
		Env.perc(atk, rel, curve: [cAtk, cRel]),
		doneAction: 2
	);

	mod = SinOsc.ar(freq * mRatio, mul: iEnv);
	car = SinOsc.ar(freq * cRatio, mod.wrap(0, 4pi));

	sig = car * gainEnv * amp;

	sig = Limiter.ar(sig, 0.95);
	sig = LeakDC.ar(sig);
	Out.ar(out, sig);
}).add;
)

(
SynthDef(\passed_Env, {
	arg out=0, freq=500, mRatio=1, cRatio=1, index=1, iScale= 5,
	amp=0.2, widthMod=1, atk=0.01, rel=3, cAtk=4, cRel=(-4), width=0.25, time=1;

	var sig, mod, car;
	var gainEnv = \gainEnv.kr(Env.newClear(8).asArray);
	var iEnv = \iEnv.kr(Env.newClear(8).asArray);

	// index Envelope
	iEnv = EnvGen.kr(iEnv);

	// amp envelope
	gainEnv = EnvGen.kr(gainEnv, doneAction:2);

	mod = SinOsc.ar(freq * mRatio, mul: iEnv);
	car = SinOsc.ar(freq * cRatio, mod.wrap(0, 4pi));

	sig = car * gainEnv * amp;

	sig = Limiter.ar(sig, 0.95);
	sig = LeakDC.ar(sig);
	Out.ar(out, sig);
}).add;
)

(
Pdef(\synthDef_Env,
	Pbind(
		\instrument, \synthDef_Env,

		\midinote, Pseq([[38,41,48,50]],inf),

		\strum, 0.25,
		\strumEndsTogether, true,

		\dur, 2,
		\legato, 0.80,
		\atk, 0.01,

		//Phase Modulation
		\mRatio, 1,
		\cRatio, 1,
		\index, 1,
		\iScale, 5,

		\amp, 0.15,
		\out, 0,
	)
).play;
)

Pdef(\synthDef_Env).stop;

(
Pdef(\passed_Env,
	Pbind(
		\type, \hasEnv,
		\instrument, \passed_Env,

		\midinote, Pseq([[38,41,48,50]],inf),

		\strum, 0.25,
		\strumEndsTogether, true,

		\dur, 2,
		\legato, 0.80,
		\atk, 0.01,

		\gainEnv, Pfunc{|e|
			var rel = 1 - e.atk;
			var cAtk = 4;
			var cRel = (-4);
			[Env.perc(e.atk, rel, curve: [e.cAtk, cRel])]
		},

		//Phase Modulation
		\mRatio, 1,
		\cRatio, 1,
		\index, 1,
		\iScale, 5,

		\iEnv, Pfunc{|e|
			var rel = (1 - e.atk);
			var cAtk = 4;
			var cRel = (-4);
			[Env([e.index, e.index * e.iScale, e.index],[e.atk,rel],[cAtk, cRel])]
		},

		\amp, 0.15,
		\out, 0,
		\finish, ~utils[\hasEnv]
	)
).play;
)

Pdef(\passed_Env).stop;

This… here I said “Actually that was wrong advice – in a Pfunc, it’s not necessary to wrap in an array.” – If advice is retracted, it might be recommended not to continue to follow it :wink:

(Or perhaps this is forum-as-mailing-list static: If you are reading the forum primarily through e-mail, then you might miss revisions to posts.)

As far as I can see, it’s doing exactly what you requested:

  • In the synthdef_env case, you’re not applying any scaling to the envelopes. Event duration is given as 2, but the total envelope duration is 3.01. So, notes will overlap. (And, because the envelope is 3.01 seconds long, the amount of overlap will depend on the tempo.)

  • In the passed_env case, your hasEnv function calculates the total sustain in seconds = 2 * 0.8 / tempo – assuming tempo = 1, that’s 1.6 seconds – and then overrides every envelope to be exactly 1.6 seconds.

So, in the first case, overlap is allowed because the envelope duration is permitted to be different from the note’s sounding duration.

In the second case, your function strictly enforces adherence to the event sustain time.

So, you would have to make sure that the event sustain time matches what you want. (Note also that, because of the envelope scaling function, you don’t have to normalize the envelope durations: 1 - e.atk is only confusing matters further.)

First:

~utils.hasEnv = {
	// calc this event's duration in seconds
	var durSeconds = ~sustain.value / thisThread.clock.tempo;

Then:

(
~pEnv = Pbind(
	\type, \hasEnv,
	\instrument, \passed_Env,
	
	\midinote, Pseq([[38,41,48,50]],inf),
	
	\strum, 0.25,
	\strumEndsTogether, true,
	
	\dur, 2,
	\atk, 0.01,
	\rel, 3,
	\sustain, Pfunc { |ev| (ev.atk + ev.rel) * thisThread.clock.tempo },
	
	\gainEnv, Pfunc{ |e|
		var cAtk = 4;
		var cRel = (-4);
		Env.perc(e.atk, e.rel, curve: [e.cAtk, cRel])
	},
	
	//Phase Modulation
	\mRatio, 1,
	\cRatio, 1,
	\index, 1,
	\iScale, 5,
	
	\iEnv, Pfunc{ |e|
		var cAtk = 4;
		var cRel = (-4);
		Env([e.index, e.index * e.iScale, e.index], [e.atk, e.rel], [cAtk, cRel])
	},
	
	\amp, 0.15,
	\out, 0,
	\finish, ~utils[\hasEnv]
).asStream;
)

e = ~pEnv.next(Event.default);
e.play;

// one of the notes is:
[ "#bundle", 16467309835142869659, 
  [ 9, "passed_Env", 1021, 0, 1, "out", 0, "freq", 146.832, "mRatio", 1, "cRatio", 1, "index", 1, "iScale", 5, "amp", 0.15, "atk", 0.01, "gainEnv", [ 0, 2, -99, -99, 1, 0.01, 0, 0, 0, 3, 5, -4, ], "iEnv", [ 1, 2, -99, -99, 5, 0.01, 5, 4, 1, 3, 5, -4 ]
]

… and segment durations 0.01 and 3 can be found in the envelopes.

hjh

thank you so much :slight_smile:
1.) when i have an envelope with one more segment i just add it to \sustain ?
for example:

\sustain, Pfunc { |ev| (ev.atk + e.sus + ev.rel) * thisThread.clock.tempo },

2.)
when i have a variable declared in the SynthDef and also in a passed envelope via Pfunc is this a problem or even necessary?
from your windowed sync example, syncRatio would be declared in the SynthDef and in the Pfunc with e.SyncRatio right?

In the SynthDef:

var syncRatio = 2;
syncFreq = freq * syncRatio * syncEg;

in the Pbind:

\syncEgTop, 20,
\syncRatio, 2,
\syncDcy, 0.5,

\syncEg, Pfunc{|e|
Env([e.syncEgTop / e.syncRatio, 1], [e.syncDcy], \exp)
},

Sure, but there’s a point where this is becoming an inconvenient data representation.

You could build the Env object first, and then just do \sustain, Pfunc { |ev| ev[\gainEnv].duration }.

If the envelope exists under the name \gainEnv, and the other parameter exists under a totally different names such as \syncRatio… why would one expect a conflict?

The whole point of giving them different names is so that they can be addressed, and sent values, independently. The fact that one of them represents an envelope has nothing to do with way that values are matched up to control names.

hjh

You could build the Env object first, and then just do \sustain, Pfunc { |ev| ev[\gainEnv].duration }.

thanks :slight_smile:

If the envelope exists under the name \gainEnv, and the other parameter exists under a totally different names such as \syncRatio… why would one expect a conflict?

the conflict i had i mind was declaring a variable in the SynthDef syncRatio with a certain dependency and also in the Pfunc e.syncRatio as part of the Envelope and set the value for both via \syncRatio in the Pbind

I’m tempted to say… If you create a name collision, then you’ll endure the effects of the name collision.

Of course the event can have only one object assigned to syncRatio – so, if you repurpose syncRatio also as part of the envelope definition, then the value for both purposes would be the same. If you need them to be independent, then they shouldn’t share a name.

Now, if the envelope picks up a time value from syncRatio (which… confusing relationships like this are a great way to create bugs, I’d suggest against it) and then your scaling function modifies the Env object, this will not affect the event’s syncRatio. Nor should it:

a = 1;  // event's syncRatio
b = a;  // use that to populate something else

b = b * 5;  // and modify the something else

Now, what is a? Obviously it’s still 1. But we switch context to an event, and… are the rules different? No. They’re not.

hjh

thank you very much :slight_smile: