The correct notations for Pbind and Event when their outputs are an array?

Dear users,

The method .play in the following two codes runs two Synth simultaneously when being evaluated:

// code 1:
(
fork{
	SynthDef(\test, { |delay = #[0, 0]| 
		var sig = SinOsc.ar * Env.perc.kr(doneAction: Done.freeSelf) * 0.1;
		sig = DelayC.ar(sig, 0.1, delay);
		FreeSelf.kr(TDelay.ar(sig, 0.2));
		OffsetOut.ar(0, sig)
	}
	).add;
	s.sync;
	3.do{
		(
			instrument: \test,
			delay: [1, -1] * 0.01.rand2
		).play;
		1.wait;
		s.queryAllNodes;
		1.wait
	}
}
)
// code 2:
(
fork{
	SynthDef(\test, { |delay = #[0, 0]| 
		var sig = SinOsc.ar * Env.perc.kr(doneAction: Done.freeSelf) * 0.1;
		sig = DelayC.ar(sig, 0.1, delay);
		FreeSelf.kr(TDelay.ar(sig, 0.2));
		OffsetOut.ar(0, sig)
	}
	).add;
	s.sync;
	{ s.plotTree }.defer;
	Pbind(
		\instrument, \test,
		\delay, [1, -1] * Pwhite(-0.02, 0.02, 3).trace(prefix: "delay: "),
		\dur, 2
	).play
};
)

I think it is because their outputs are an array with two items. I should read the help documents, but it would be beneficial if someone gave me some clues.

Anyway, I tried to rewrite each code in two ways as follows:

// code 3:
(
fork{
	SynthDef(\test, { |delay = #[0, 0]| 
		var sig = SinOsc.ar * Env.perc.kr(doneAction: Done.freeSelf) * 0.1;
		sig = DelayC.ar(sig, 0.1, delay);
		FreeSelf.kr(TDelay.ar(sig, 0.2));
		OffsetOut.ar(0, sig)
	}
	).add;
	s.sync;
	3.do{
		(
			instrument: \test,
			#[delay]: [1, -1] * 0.01.rand2
		).play;
		1.wait;
		s.queryAllNodes;
		1.wait
	}
}
)
// code 4:
(
fork{
	SynthDef(\test, { |delay = #[0, 0]| 
		var sig = SinOsc.ar * Env.perc.kr(doneAction: Done.freeSelf) * 0.1;
		sig = DelayC.ar(sig, 0.1, delay);
		FreeSelf.kr(TDelay.ar(sig, 0.2));
		OffsetOut.ar(0, sig)
	}
	).add;
	s.sync;
	3.do{
		(
			instrument: \test,
			delay: `[1, -1] * 0.01.rand2
		).play;
		1.wait;
		s.queryAllNodes;
		1.wait
	}
}
)
// code 5:
(
fork{
	SynthDef(\test, { |delay = #[0, 0]| 
		var sig = SinOsc.ar * Env.perc.kr(doneAction: Done.freeSelf) * 0.1;
		sig = DelayC.ar(sig, 0.1, delay);
		FreeSelf.kr(TDelay.ar(sig, 0.2));
		OffsetOut.ar(0, sig)
	}
	).add;
	s.sync;
	{ s.plotTree }.defer;
	Pbind(
		\instrument, \test,
		#[delay], [1, -1] * Pwhite(-0.02, 0.02, 3).trace(prefix: "delay: "),
		\dur, 2
	).play
};
)
// code 6:
(
fork{
	SynthDef(\test, { |delay = #[0, 0]| 
		var sig = SinOsc.ar * Env.perc.kr(doneAction: Done.freeSelf) * 0.1;
		sig = DelayC.ar(sig, 0.1, delay);
		FreeSelf.kr(TDelay.ar(sig, 0.2));
		OffsetOut.ar(0, sig)
	}
	).add;
	s.sync;
	{ s.plotTree }.defer;
	Pbind(
		\instrument, \test,
		\delay, `([1, -1] * Pwhite(-0.02, 0.02, 3)),
		\dur, 2
	).play
}
)

Codes 3, 4, and 5 work correctly.
However, the last code, code 6, needs to be corrected. The method ‘.play’ does not run two Synths, but it does not end after playing the third sound. Why does it happen? What is the correct notation for this?

I appreciate any help you can provide.

I am still not sure, but I seem to find a solution:

I now think that the correctly-working codes 3, 4, and 5 could be ridiculous and that the solutions below would be better, even though I could not explain the reason very clearly. However, it would still be grateful if someone could enlighten me.

// code 7:
(
fork{
	SynthDef(\test, { |delay = #[0, 0]| 
		var sig = SinOsc.ar * Env.perc.kr(doneAction: Done.freeSelf) * 0.1;
		sig = DelayC.ar(sig, 0.1, delay);
		FreeSelf.kr(TDelay.ar(sig, 0.2));
		OffsetOut.ar(0, sig)
	}
	).add;
	s.sync;
	3.do{
		(
			instrument: \test,
			delay: [[1, -1]] * 0.01.rand2
		).play;
		1.wait;
		s.queryAllNodes;
		1.wait
	}
}
)
// code 8:
(
fork{
	SynthDef(\test, { |delay = #[0, 0]| 
		var sig = SinOsc.ar * Env.perc.kr(doneAction: Done.freeSelf) * 0.1;
		sig = DelayC.ar(sig, 0.1, delay);
		FreeSelf.kr(TDelay.ar(sig, 0.2));
		OffsetOut.ar(0, sig)
	}
	).add;
	s.sync;
	{ s.plotTree }.defer;
	Pbind(
		\instrument, \test,
		\delay, [[1, -1]] * Pwhite(-0.02, 0.02, 3).trace(prefix: "delay: "),
		\dur, 2
	).play
};
)

Yes, or even [[1, -1]] * Pwhite(-0.01, 0.01, inf).

The reason for this is that arrays in Events are defined to spread out their members across multiple synths. This is for the common case of defining a single-note SynthDef and playing chords with it.

If you want the array to be assigned to a single synth’s control, then the array must be a member of an array – two array levels. This is admittedly confusing, but it’s necessary for SC to disambiguate in some way. It just wouldn’t work if [1, 2] sometimes meant multiple synths and sometimes array-arg assignment. The two cases must be specified differently.

About Ref (the backtick notation), the “fine print” here is: When you’re performing operations on patterns or streams, some operations “compose” into a new pattern/stream, while other operations don’t.

// for shorter notation, let's save a pattern in a var
p = Pwhite(0.0, 1.0, inf);

q = 2 * p;  // a Pbinop

r = q.asStream;  // a BinaryOpStream

r.next;  // 1.6003565788269

The * operation has “composed” upward into a “binary-op Pattern.” When Pbind calls asStream on this, then you get a stream that performs the operation – it all works transparently.

But:

q = `(2 * p);  // a Ref: `(a Pbinop)

r = q.asStream;  // a Ref: `(a Pbinop) -- not a stream!

r.next;  // a Pbinop -- this is not a valid synth input

So the Event contains not the result of the operation, but the object that defines the operation.

The double-array does compose.

Unfortunately, it isn’t obvious from reading SC code which operations compose and which don’t.

hjh

1 Like

Thank you very much! You are back! I hope all is well with you!