Control hardware and server synth with one Pbind?

I’m trying to sync an external hardware synth and an internal synth using the same note events.

Here’s a boiled down example of what I’m trying to do:

MIDIClient.init;
m = MIDIOut(1).latency = 0.01;  
(
SynthDef(\tone, {|out=0, gate=1, freq=440|	
	var env = EnvGen.ar(Env.adsr(releaseTime:0.1 ), gate, doneAction:2);
	Out.ar(out, SinOsc.ar(freq!2, mul: env, mul:0.3));
}).add;
)

a = Pbind(\degree, Prand([1, 2, 3], inf), \dur, Pwhite(1/4, 1, inf));
(
(a <> (instrument: \tone)).play; 
(a <> (type: \midi, midiout: m)).play; 
)

I want the instrument notes and the midi notes sent out with the same timing and pitch. Duplicating the Pbind isn’t really an option since there’s some randomness involved.

The above code plays two different patterns in parallel, when I want both patterns to be identical.

Is this possible? Am I on the wrong path?

Thanks!

there is a thread over here that has some interesting ideas that might be relevant

Another approach is to use Pspawner

(
Pdef(\ptrn,
	Pspawner({arg ps;
		
		var degree = Prand([1, 2, 3], inf).asStream;
		var dur = Pwhite(1/4, 1, inf).asStream;
		
		inf.do({
			var d = degree.next;
			ps.par(Pn((instrument: \tone, degree:d), 1));
			ps.par(Pn((type: \midi, midiout: m, \degree: d), 1));
			ps.wait(dur.next);
		})
	})
).play;
)
Pdef(\ptrn).stop;

Hi,

not on the wrong path, but it’s one of those pattern questions, that sound very easy, but are tricky in detail. But first, there are two problems in the posted code:

If you want to set the latency, you’d have to do it in a different way, otherwise latency isn’t set (check with m.latency after evaluating the above line). Also I wouldn’t set the latency to such a low value, because the server latency is still 0.2 (however you could set both to 0.05 or so). With my setup I need MIDIOut(0), but of course that can be different on yours, e.g.

MIDIClient.init;
m = MIDIOut(0);
m.latency = 0.05;
s.latency = 0.05;

The second thing is the doubled definition of SinOsc’s mul arg in the SynthDef, e.g. say

(
SynthDef(\tone, {|out=0, gate=1, freq=440|
	var env = EnvGen.ar(Env.adsr(releaseTime: 0.1), gate, doneAction:2);
	Out.ar(out, SinOsc.ar(freq!2, mul: env));
}).add;
)

Now the problem of “doubling” the event data - it’s (again) a matter of differentiating Streams and Patterns. Some possibilities:

(1) Data sharing

See the chapter in James’ pattern guide:
http://doc.sccode.org/Tutorials/A-Practical-Guide/PG_06g_Data_Sharing.html

I’d use a Ptpar and the lag key to adjust the correct latency, the delay in the Ptpar ensures correct order of evaluation, with lag (lagtime in seconds) you can compensate, this value (0.025) compensates fine on my Mac using SimpleSynth via midi and IAC.

(
a = Pbind(
	// _ is partial application syntax, you could also write:
	// collect { |x| ~degree = x }
	\degree, Prand([1, 2, 3], inf).collect(~degree = _),
	\dur, Prand([2, 1, 1]/5 , inf).collect(~dur = _),
	\lag, 0.025
);

b = Pbind(
	// takes over the values from other stream when played
	\degree, Pfunc { ~degree },
	\dur, Pfunc { ~dur },
	\amp, 0.5,
	\type, \midi,
	\midiout, m,
)
)

Ptpar([0, a, 0.001, b]).play

(2) Using Pchain with Streams

Maybe a surprising solution (at least for me, first time I’ve done it this way - or I had forgotten it … see Pchain help file, last examples). If you chain the Streams, define the source with a Pstutter, then each random value will be produced twice, for the both streams that need it. ‘lag’ is also taken over, so redefine it in the midi pattern to keep the difference.

(
a = Pbind(
	\degree, Pstutter(2, Pxrand([1, 2, 3], inf)),
	\dur, Pstutter(2, Prand([2, 1, 1]/5 , inf)),
	\lag, 0.025
).asStream;


b = Pbind(
	\lag, 0,
	\amp, 0.5,
	\type, \midi,
	\midiout, m
) <> a
)

Ptpar([0, a, 0.001, b]).play

(3) Using PSdup from miSCellaneous lib

PSx patterns behave like Streams, they remember last items (events), PSdup is just duplicating the data of another PSx pattern, here PS(p). This solution looks very similar to (2), but you don’t have to use Pstutter.

(
p = Pbind(
	\degree, Prand([1, 2, 3], inf),
	\dur, Prand([2, 1, 1]/5 , inf),
	\lag, 0.025
);

a = PS(p);

b = Pbind(
	\lag, 0,
	\amp, 0.5,
	\type, \midi,
	\midiout, m
) <> PSdup(a)
)

Ptpar([0, a, 0.001, b]).play

PSx patterns are admittedly strange - but especially in a case like this they provide a quite straight solution.

… and (2) can be simplified a bit, we can stutter the whole event pattern:

(
a = Pstutter(2, 
	Pbind(
		\degree, Pxrand([1, 2, 3], inf),
		\dur, Prand([2, 1, 1]/5 , inf),
		\lag, 0.025
	)
).asStream;


b = Pbind(
	\lag, 0,
	\amp, 0.5,
	\type, \midi,
	\midiout, m
) <> a
)

Ptpar([0, a, 0.001, b]).play

If you want to set the latency, you’d have to do it in a different way, otherwise latency isn’t set (check with m.latency after evaluating the above line).

Both syntaxes work fine.

m = MIDIOut(0).latency_(0.05);
m.latency;
-> 0.05

n = MIDIOut(0).latency = 0.05;
n.latency;
-> 0.05

any_expression.method_(value) and any_expression.method = (value) compile to the same method call method_.

I think it’s clearer to write setter_ notation; in my first example above, it’s obvious that the return value should be the MIDIOut object, while in the second, it isn’t clear whether the return should be a MIDIOut or 0.05 ((x = 5).postln vs (Point(1, 2).x = 3).postln).

It’s correct that MIDIOut latency and server latency should match.

My preferred solution for the problem of “one pattern, multiple actions” is to make an event type that does both things.

(
Event.addEventType(\midisynth, { |server|
	Event.eventTypes[\note].value(server);
	Event.eventTypes[\midi].value(server);
});
)

MIDIClient.init;
m = MIDIOut(0).latency_(0.2);

p = Pbind(\type, \midisynth, \midiout, m, ...).play;

hjh

That’s indeed true, I must have seen a ghost syntax :slightly_smiling_face:

James’ solution seemed the most straightforward for me and worked perfectly.
Thank you all very much for your replies!