Continuous Panning of Multiple Pbind Nodes?

I’ve been struggling with this for a while.

Already-running nodes can be continuously panned with set event types. So I can play a buffer and change how it’s panned during playback. No problem there.

And the pan position of each synth (node) instantiated by a Pbind can be set using the note event type.

But, still using a Pbind, how can I both instantiate a particular pan position and have that position change while that node is still running, along with any other nodes that might get instantiated by the same Pbind?

In other words, is there a way to continuously control the global pan position of all the nodes in the event stream of a Pbind?

(Note: I’m talking about pan position here, but the question would apply to any argument that one would want to set both on instantiation of a node by a Pbind and while nodes are already running in the Pbind’s event stream)

Thanks for any thoughts. I hope this is clear…

I use techniques like this quite often - here are a few things to look in to:

  1. Pmono and PmonoArtic are both ways you can run a single synth, and use subsequent pattern values to only set synth arguments.
  2. These don’t work great for groups, since you’ll still be only sitting synths individually. For a group context, you could try using a Bus, and passing it as bus.asMap in your Pbind to the parameters you want to modulate. You can set the value of the bus either using a synth (for continuous LFO-like behavior) or from another pattern via a (type: \bus) event. I also find it works well to create a set of global LFO-style modulators using Ndef's - these can easily be mapped to Synth args, and I get the power of both setting their input params via a pattern (Pbind(\id, Ndef(\lfo).group, \inputValue, Pseq([0.0, 0.5, 1.0], inf))).
  3. Here’s a little snippet I wrote the other day - this is one way of splitting a single base string (in this case a Pbind) into parallel streams using the same values:
(
Ppar([
	Pfunc(_[0]),
	Pfunc(_[1])
])
<> Ptuple([
	Pbind(\amp, 0.5, \dur, 1),
	Pbind(\amp, 1, \dur, 2/3)
], inf)
<> Pbind(
	\degree, Pseq([0, 3, 7, 9, 12], inf)
)
).trace.play

In this case, both of the streams in the Ptuple take values from the input Pbind. If they are synchronized, it’ll be the same value, but if they’re out of synth, each new event from either Pbind will grab a new values from the input stream. The Ppar splits the stream of [event, event] objects into two separate streams of events that can have different durations.
If it’s not totally clear: this would allow you to have a common set of parameters (either a value stream, or just something like bus.asMap) provided by the bottom-most Pbind, but still feeding multiple independent streams of events.
4. I’m working on a class now that will allow interleaving of static values and modulators (usually in the form of inline functions) in a pattern as event parameters, e.g.:

Pbind(
    \amp, PsynthArg(Pseq([ 0.1, 0.3, { SinOsc.kr(4).range(0.4, 0.6) }], inf))
)

The MiSCelanious quark can also do variations on this kind of thing in very elegant ways.

1 Like

Thanks a lot, Scott! This is really helpful! Plenty to dig into here.
I’ll be spending some time working through your various suggestions and considering which will work best in this case.
Your in depth response is much appreciated!

Actually for this specific demand I would go for a strategy not explained in miSCellaneous. You can define dedicated spat synths. With this kind of modularisation you are flexible concerning the type of movement.

(
SynthDef(\sawPerc, { |out, freq = 400, att = 0.005, rel = 0.05, amp = 0.1|
	OffsetOut.ar(out, EnvGen.ar(Env.perc(att, rel), doneAction: 2) * Saw.ar(freq, amp))
}).add;

// stereo movement with SinOsc, try with LFDNoise3 or other too
SynthDef(\spat_2ch, { |out = 0, in, moveFreq = 0.3, movePhase = 0|
	var inSig = In.ar(in, 1), movedSig;
	movedSig = Pan2.ar(inSig, SinOsc.ar(moveFreq, movePhase));
	OffsetOut.ar(0, movedSig);
}).add;

s.scope;
)



( 
// bus and spat synth, start with one here
b = Bus.audio(s, 1);
x = Synth(\spat_2ch, [in: b]);
)


// play source pattern, pattern-generated synths are before spat fx
(
p = Pbind(
	\instrument, \sawPerc,
	\out, b,
	\dur, 0.05,
	\midinote, Pn(Plazy { Pseries(rrand(40.0, 70), 1, rrand(5, 30)) })
).play
)

// cleanup
x.free;
p.stop;
b.free;

Possible extensions:

.) multiple random movements (n > 1), fed with Pbind chords (each voice automatically get its own movement, as busses are assigned in order)

( 
// n busses and spat synths
n = 2;
b = { Bus.audio(s, 1) } ! n;
x = { |i| Synth(\spat_2ch, [in: b[i], movePhase: i * pi]) } ! n;
)


// play source pattern, pattern-generated synths are before spat fx
(
p = Pbind(
	\instrument, \sawPerc,
	\out, b,
	\dur, 0.2,
	\midinote, Pn(Plazy { Pseries(rrand(40.0, 70), 1, rrand(5, 30)) }) + [-12, 12]
).play
)

// cleanup
x.do(_.free);
p.stop;

Other extensions:

.) more than 2 channels (PanAz instead of Pan2)
.) other kind of movement: you could define the movement as a control input to the spat synth, that’s most flexible, so you can experiment with new movements while running the Pbind by exchanging a movement synth.

Greetings

Daniel

1 Like

Just to add: Scott’s second suggestion goes into a similar direction as my approach. That would assume that the sound-generating synth also has a spat parameter. All in all - and this is for good and bad as it’s difficult to decide what to take - there is a huge number of ways to combine discrete control of patterns with continous control that can itself be pseudo-continuous (steps approximating a continuum) or really continuous (synths). The tutorial “Event Patterns and LFOs” of miSCellaneous lib tries to summarize some of those.

1 Like

Many thanks, Daniel! This is also extremely helpful. As you indicate here, I think your suggestion and Scott’s 2nd suggestion seem like the most promising for what I’m trying to achieve. I’ll also take a look at the “Event Patterns and LFOs” tutorial to help me get a better grasp on all this. Wonderful to have such in depth and useful answers to my question. Cheers!