Using one Ndef to control multiple parameters in Pbind/Pmono

I’m trying to change multiple parameters of a Pbind with the same LFO - I want to change a reverb effect to make it sound like a room is growing and shrinking. I thought this would be relatively easy but I can’t quite work out what I need to do.


(
SynthDef(\reverb, {
        arg in, predelay=0.1, revTime=1.8, lpf=4500, mix=0.15, amp=1, out=0, maxRoomSize=0.005;
        var dry, wet, temp, sig;

predelay = predelay.clip(0.05, 0.1);

dry = In.ar(in, 2);

temp = In.ar(in, 2);

wet = 0;

temp = DelayN.ar(temp, 0.2, predelay); 
16.do{
        temp = AllpassN.ar(temp, 0.05, {Rand(0.001, maxRoomSize)}!2, revTime); 
        temp = LPF.ar(temp, lpf);
        wet = wet + temp;
};
sig = XFade2.ar(dry, wet, mix*2-1, amp);
sig = LeakDC.ar(sig);
Out.ar(out, sig);
}).add;
)

~reverbBus = Bus.audio(s, 2);

(
SynthDef(\test, {
arg amp=0.1, out=0, freq=220, atk=0.05, rel=0.1;
var sig, env;

env = EnvGen.ar(Env.perc(atk, rel), doneAction: 2);
sig = VarSaw.ar(freq, LFNoise0.kr(atk+rel).range(0, 2pi));

sig = sig * env * amp!2;

Out.ar(out, sig);

}).add;
)


(
Pdef(\test, 
Pbind(
\instrument, \test, 
\dur, 0.8,
\freq, 220,
\rel, 0.01,
\atk, 0.01,
\out, ~reverbBus,
\amp, 0.25
)).play;
)

(
Pdef(\reverbtest,
Pmono(\reverb,
\dur, 1,
\mix, Ndef(\lfo).asMap,
\in, ~reverbBus,
\amp, 1
)).play;
)

Ndef(\lfo, {arg rate=0.5; var lfo; lfo = SinOsc.ar(rate).linlin(-1,1,0.05,0.75)}); // this works on its own

What I would like to do, however, is to have the same lfo also controlling predelay (between 0.05 and 0.15 or so) and revTime (between 1.0 and 2.0) in addition to mix (0.05, 0.75). I tried the following ideas and they didn’t work:

// different Ndefs for each parameter eg. 


Ndef(\mix, {var mix; mix = Ndef(\lfo).linlin(-1,1,0.05,0.75)});
Ndef(\predelay, {var predelay; predelay = Ndef(\lfo).linlin(-1, 1, 0.05, 0.15)});
Ndef(\lfo, {arg rate=0.5; var lfo; lfo = SinOsc.ar(rate)});

(
Pdef(\reverbtest,
Pmono(\reverb,
\dur, 1,
\mix, Ndef(\mix).asMap,
\predelay, Ndef(\predelay).asMap,
\in, ~reverbBus,
\amp, 1
)).play;
)

// Pkey

Ndef(\lfo, {arg rate=0.5; var lfo; lfo = SinOsc.ar(rate).linlin(-1,1,0.05,0.75)});

(
Pdef(\reverbtest,
Pmono(\reverb,
\dur, 1,
\mix, Ndef(\lfo).asMap,
\delaytime, Pkey(\mix.linlin(0.05, 0.75, 0.05, 0.15)),
\in, ~reverbBus,
\amp, 1
)).play;
)



I have a feeling this is not a super difficult problem and that I have overlooked/misunderstood something. Any help would be much appreciated - thanks!

1 Like

I don’t know if it’s good practice to bring back old questions, but this question is simple and unsanwered: just use Ndef.kr(\lfo) instead of Ndef(\lfo) ^^

(
SynthDef(\saw, { arg out=0, gate=1, amp=0.1, pan=0, freq=200;
	var sig;
	sig = LFSaw.ar(freq);
	sig = RLPF.ar(sig, \lpfr.kr(1.1) * freq, \rq.kr(0.5));
	sig = sig * EnvGen.kr(\adsr.kr(Env.adsr(0.1,0.1,0.8,0.1)), gate, doneAction:2);
	sig = Pan2.ar(sig, pan, amp);
	Out.ar(out, sig);
}, metadata:(specs:(
	lpfr: ControlSpec(0.1,4,\lin, 0, 1)
))).add;
)


Ndef(\mix, {var mix; mix = Ndef.kr(\lfo).linlin(-1,1,0.05,3.75)});
Ndef(\predelay, {var predelay; predelay = Ndef.kr(\lfo).linlin(-1, 1, 100.05, 700.15)});
Ndef(\lfo, {arg rate=2.5; var lfo; lfo = SinOsc.ar(rate)});


(
	Pdef(\test,
		Pmono(\saw,
			\dur, 1,
			\lpfr, Ndef(\mix).asMap,
			\freq, Ndef(\predelay).asMap,
			\amp, 1
		)
	).play;
)
1 Like

Wow thank you so much! I sort of forgot about this question and am very excited to try out your answer, cheers!

1 Like

A nice idea and a nice solution! I encountered still some issues:

.) Actually there’s no reason to define a Pmono for the reverb, as with every entry the running synth is remapped. One Event would suffice (unless there’s another reason that hasn’t been mentioned), the Ndef mapping works in the same way. It would also be possible to write a Pmono with dur inf.

.) Order of execution is crucial. If it’s only one reverb synth it has to be started before the source Pbind (which, by default, adds to head).

.) With repeated running of the example the proxies seem to be in danger being messed up. It helps to always do Ndef.clear.

.) The usage of att and rel for VarSaw’s iphase arg might be a leftover, it causes clicks.

(
SynthDef(\reverb, {
        arg in, predelay=0.1, revTime=1.8, lpf=4500, mix=0.15, amp=1, out=0, maxRoomSize=0.005;
        var dry, wet, temp, sig;

	predelay = predelay.clip(0.05, 0.1);

	dry = In.ar(in, 2);

	temp = In.ar(in, 2);

	wet = 0;

	temp = DelayN.ar(temp, 0.2, predelay);
	16.do{
        temp = AllpassN.ar(temp, 0.05, {Rand(0.001, maxRoomSize)}!2, revTime);
        temp = LPF.ar(temp, lpf);
        wet = wet + temp;
	};
	sig = XFade2.ar(dry, wet, mix*2-1, amp);
	sig = LeakDC.ar(sig);
	Out.ar(out, sig);
}).add;

~reverbBus = Bus.audio(s, 2);

SynthDef(\test, {
	arg amp=0.1, out=0, freq=220, atk=0.05, rel=0.1;
	var sig, env;

	env = EnvGen.ar(Env.perc(atk, rel), doneAction: 2);
	sig = VarSaw.ar(freq);

	sig = sig * env * amp!2;

	Out.ar(out, sig);
}).add;
)




(
Ndef.clear;
Ndef(\mix, { var mix; mix = Ndef.kr(\lfo).linlin(-1, 1, 0, 1).poll(2, \mix) });
Ndef(\predelay, { var predelay; predelay = Ndef.kr(\lfo).linlin(-1, 1, 100.05, 700.15) });
Ndef(\lpf, { var lpf; lpf = Ndef.kr(\lfo).linlin(-1, 1, 100, 15000) });
Ndef(\lfo, { arg rate = 0.2; var lfo; lfo = SinOsc.ar(rate) });
)


(
(
	instrument: \reverb,
	dur: inf,
	mix: Ndef(\mix).asMap,
	predelay: Ndef(\predelay).asMap,
	lpf: Ndef(\lpf).asMap,
	in: ~reverbBus,
	amp: 1
).play;

// or


/*Pdef(\reverb,
	Pmono(\reverb,
		\dur, inf,
		\mix, Ndef(\mix).asMap,
		\predelay, Ndef(\predelay).asMap,
		\lpf, Ndef(\lpf).asMap,
		\in, ~reverbBus,
		\amp, 1
	)
).play;*/

)


(
Pdef(\test,
	Pbind(
		\instrument, \test,
		\dur, 0.3,
		\freq, Pseq([220, 200], inf),
		\rel, 0.01,
		\atk, 0.01,
		\out, ~reverbBus,
		\amp, 0.25
	)
).trace.play;
)

An alternative solution without proxies, writing to a multichannel bus using the methods ‘range’ and ‘linlin’ with expansion.

(
b = Bus.control(s, 3);
l = { |rate = 0.2| Out.kr(b, SinOsc.kr(rate).range([0, 100, 100], [1, 700, 15000])) }.play;

// or
// l = { |rate = 0.2| Out.kr(b, SinOsc.kr(rate).linlin(-1, 1, [0, 100, 100], [1, 700, 15000])) }.play;
)


(
r = (
	instrument: \reverb,
	dur: inf,
	mix: b.subBus(0).asMap,
	predelay: b.subBus(1).asMap,
	lpf: b.subBus(2).asMap,
	in: ~reverbBus,
	amp: 1
).play;

// or with Pmono array assignment

/*r = Pmono(\reverb,
	\dur, inf,
	#[mix, predelay, lpf], (0..2).collect { |i| b.subBus(i).asMap },
	\in, ~reverbBus,
	\amp, 1
).play;*/
)

(
p = Pbind(
	\instrument, \test,
	\dur, 0.3,
	\freq, Pseq([220, 200], inf),
	\rel, 0.01,
	\atk, 0.01,
	\out, ~reverbBus,
	\amp, 0.25
).trace.play;
)

As a member of the Pdef fan club, I have to intervene. With your code, you can’t stop the reverb. You can store the event player in a variable, but why bother when Pdef do it for you ? Also when you change a parameter of the event and execute, you have two reverb running.

In the last example I assigned Event and/or Pmono to the variable r. So it can be stopped in both of these variants (either with r.free or r.stop).
I can see the point of Ndef being nice as it avoids coping with buses etc. Pdef causes extra typing, assigning the player to a variable is obviously shorter, if doing a replacement again Pdef makes sense of course. But in the end it’s a matter of taste also.