Making transitions with language or server side sequencing

Hey, i know this is nothing new but either way i cant solve that problem:

Take these two microsound SynthDefs for testing, the first one is using \trig.tr named SynthDef(\trig_test) and the second one is using Impulse.ar named SynthDef(\impulse_test). Everything else is identical:

(
var statelessWindow = { |levels, times, curve, phase|
	var x = 0;
	var window = times.size.collect({ |i|
		var x2 = x + times[i];
		var result = (phase >= x) * (phase < x2) * phase.lincurve(x, x2, levels[i], levels[i+1], curve[i]);
		x = x2;
		result;
	}).sum;
	window = window * (phase < 1);
	window;
};

SynthDef(\trig_test, {
	var trig = \trig.tr(0);
	var freq = \freq.kr(440);
	var phase = Sweep.ar(trig, freq);
	var window = statelessWindow.([0, 1, 0], [0.01, 0.99], [4.0, -4.0], phase);
	var sig = sin(phase * 2pi);
	sig = sig * window;
	sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(0.25));
	OffsetOut.ar(\out.kr(0), sig);
}).add;

SynthDef(\impulse_test, {
	var tFreq = \tFreq.kr(1);
	var trig = Impulse.ar(tFreq);
	var freq = \freq.kr(440);
	var phase = Sweep.ar(trig, freq);
	var window = statelessWindow.([0, 1, 0], [0.01, 0.99], [4.0, -4.0], phase);
	var sig = sin(phase * 2pi);
	sig = sig * window;
	sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(0.25));
	OffsetOut.ar(\out.kr(0), sig);
}).add;
)

Now play SynthDef(\impulse_test) with two different values for \tFreq:

// works fine!!!
x = Synth(\impulse_test, [\tFreq, 10]);
x.free;

// works fine!!!
x = Synth(\impulse_test, [\tFreq, 50]);
x.free;

Now the same with SynthDef(\trig_test) inside a Pmono:

// works fine!!!
(
x = Pmono(\trig_test,
	\trig, 1,
	\dur, 10.reciprocal,
	\freq, 440,
	\amp, 0.25,
).play;
)

x.stop;

// nothing new, sounds aweful at audio rate!!!
(
x = Pmono(\trig_test,
	\trig, 1,
	\dur, 50.reciprocal,
	\freq, 440,
	\amp, 0.25,
).play;
)

x.stop;

Now lets do a hybrid approach: use SynthDef(\impulse_test) inside Pmono:

// works fine!!!
(
x = Pmono(\impulse_test,
	\dur, 1,
	\tFreq, 50,
	\freq, 440,
	\amp, 0.25,
).play;
)

x.stop;

// sequencing is not in sync 
// and also not per grain, because \dur and \tFreq are independent from each other
(
x = Pmono(\impulse_test,
	\dur, 1,
	\tFreq, Pseq([10, 50], inf),
	\freq, 440,
	\amp, 0.25,
).play;
)

x.stop;

// now sequencing is per grain but still not in sync
(
x = Pmono(\impulse_test,
	\dur, Pdup(15, Pseq([10, 50], inf)).reciprocal,
	\tFreq, 1 / Pkey(\dur),
	\freq, 440,
	\amp, 0.25,
).play;
)

x.stop;

So lets recapture:
1.) sequencing with Impulse.ar at audio rate does work.
2.) sequencing with \trig.tr and Pmono at audio rate doesnt work.
3.) doing a hybrid approach of both: language and server side sequencing is not in sync and you can only sequence per grain if you bind \dur and \tFreq together.

I think this is straight forward so far but now the problem:

Lets take the following setup up, which is a simplified version of what im using for composing.
Im using Pdefs with Pmonos inside Pspawner and the function ~runTrans enables me to make transitions for parameter sets over a specific time:

// functions for beeing used to make transitions and organize rhythm
(
~pParTracks = { |...tracks|
	var fxTracks = tracks.collect { PatternProxy(()) };
	var evPatternFns = tracks.collect { |trackArgs, n|
		var dataPatternName = trackArgs[0];
		var durs = trackArgs[1];
		fxTracks[n] <> Pdef(dataPatternName) <> Pbind(\dur, durs)
	};
	var rhythmPars = Ppar(evPatternFns);
	(\rhythm: rhythmPars, \fxs: fxTracks);
};

~mapTrans = {|parEnvs, transDur= 1|
    var penvs = parEnvs.select{|v|v.class===Penv}.collect{|penv|
        penv.times = penv.times*transDur
    };
    var busses = parEnvs
    .select{|v,k| penvs.keys.includes(k).not}.collect{Bus.control(s,1)};

    {
        busses.collect{|bus, parName|
            Out.kr(bus, EnvGen.kr(parEnvs[parName], timeScale:transDur));
        };
        Line.kr(0,1, transDur, doneAction:2);
        Silent.ar;
    }.play.onFree{
        busses do: _.free
    };

    busses.collect(_.asMap) ++ penvs
};

~runTrans = {|transProxy, transDef, transDur|
	transProxy.source = Pbind(*~mapTrans.(transDef, transDur).asKeyValuePairs);
};
)

// test SynthDef
(
SynthDef(\test, {
	var trig = \trig.tr(0);
	var gainEnv = EnvGen.ar(Env.perc(\atk.kr(0.01), \rel.kr(0.5)), trig, doneAction: Done.none);
	var sig = SinOsc.ar(\freq.kr(440));
	sig = sig * gainEnv * \amp.kr(0.25);
	sig = Pan2.ar(sig, \pan.kr(0));
	Out.ar(\out.kr(0), sig);
}).add;
)

// Patterns

(
Pdef(\patternA,
	Pmono(\test,
		\trig, 1,
		\freq, 440,
		\pan, -1,
	);
);

Pdef(\patternB,
	Pmono(\test,
		\trig, 1,
		\freq, 880,
		\pan, 1,
	);
);
)

// compose a piece inside Pspawner

(
Pdef(\player,
	Pspawner({ |sp|

		var partA, partB;

		\partA.postln;

		// play both Pdefs with a different rhythm pattern
		partA = ~pParTracks.(
			[\patternA, 0.25 * Pseq([3, 1, 2, 2], inf)],
			[\patternB, 0.25 * Pseq([2, 1, 2, 1, 2], inf)]
		);
		sp.par(Pfindur(8, partA.rhythm));

		// make individual transitions for \freq over 8 secs for both Pdefs
		~runTrans.(partA.fxs[0], (
			freq: Env([440, 220], 1, \exp),
		), 8);

		~runTrans.(partA.fxs[1], (
			freq: Env([880, 1760], 1, \exp),
		), 8);

		sp.wait(8);

		\partB.postln;

		// play both Pdefs with a different rhythm pattern
		partB = ~pParTracks.(
			[\patternA, 0.25 * Pseq([3, 3, 2], inf)],
			[\patternB, 0.25 * Pseq([2, 1, 3, 2], inf)]
		);
		sp.par(Pfindur(2, partB.rhythm));

		sp.wait(2);

		\done.postln;
		sp.wait(2);
		sp.suspendAll;
	});
).play;
)

Now lets combine these two things: How do i go about using SynthDef(\impulse_test) which does get its triggers from Impulse.ar and not from \trig.tr

1.) to use audiorate modulation of the trigger frequency.
2.) be able to sequence all the parameters per grain (so Pmono with Impulse.ar is not an option).
3.) do transitions for parameter sets with a function like ~makeTrans.

Ive already implemented Demand rate Ugens for masking the triggers of Impulse.ar with rhythmic sequences which could be used inside Synths but whats about transitions / interpolation between states then, when not using Patterns? this is crucial for compositon IMO.

How would you go about that?

Or asked another way, how would you replicate my Pspawner example with SynthDef(\impulse_test)?

1 Like