Here’s simple trick I just worked out.
Problem:
I’m generating Events with arrayed parameters. These will get multi-channel expanded by the Event system into multiple voices. For example, something like:
Pbind(
\degree, [0,2]
)
…produces two Synths, one for each specified \degree
value.
How MANY voices I ultimately get is dependent on the array sizes of my Event values (specifically: the maximum size). This voice count may change over time in my pattern, and may not be simple to determine a priori (e.g. maybe I’m generating these events based on complex combinations of Pbinds/patterns, or playing them live via a MIDI device).
So, what if I want each voice to have a unique value - for example, I want each one to output to a different Bus? I can’t simply add a list of buses like (bus: [b1, b2, b3, b4...])
- as per above, it’s not easy to determine my voice count, so I can’t tell how many buses to add. I want something that will lazily supply values for any number of voices, but a value that itself doesn’t cause a multi-channel expansion.
Solution:
Set your Event value to a Routine
that incrementally yields new values. When the Event system is preparing values for the Synth it’s creating, it calls .asControlInput
on each item. .asControlInput
, by default, forwards to Object:value
- and for Routine
, .value
forwards to .next
, which fetches a new value. So, every time your Routine
is embedded in a new OSC message, a new value is requested - this gets us our desired behavior: lazy supply different values for each voice without forcing multichannel expansion.
~buses = 12.collect { Bus.audio(s, 2) };
Pbind(
\degree, [0, 2],
\out, Pfunc({
Routine({
~buses.do {
|bus|
bus.asControlInput.yield
}
})
})
);
You can see in my resulting OSC messages:
[ "#bundle", 16697629428674429394,
[ 9, "default", 1060, 0, 1, "velocity", 88.9, "freq", 261.626, "pan", 0, "amp", 0.1, "out", 76 ],
[ 9, "default", 1061, 0, 1, "velocity", 88.9, "freq", 329.628, "pan", 0, "amp", 0.1, "out", 78 ]
]
This works well for regular \note
events, and for any Event
key that shadows a Synth parameter (since these are the ones that are unpacked with a asControlInput
call). Outside of these cases, it may not work as anticipated.
This works for implementing something like voice groups as well:
~voiceGroups = 12.collect { Group() };
Pbind(
\degree, [0, 2, 4],
\group, Pfunc({
Routine({
~voiceGroups.do {
|b|
b.asControlInput.yield
}
})
})
).play;
Another example: I have one overall “chord” - I want to lazily pull notes from this depending on how many events I’m producing.
Pbind(
\chord, [0, 2, 4, 5, 9],
\degree, Pfunc({
|e|
Routine({
e[\chord].do(_.yield)
})
}),
\amp, Pclump(
Prand([2, 3, 4, 5], inf),
Pseq([0.2], inf)
)
).play
In this example, the \amp
key is determining how many voices we have, and the degree
values are pulled lazily from \chord
- if amp returns 2 values, then we get the first two notes in the chord.
We can easily make a utility Pdefn
that will transform arrayed inputs into “lazy” arrays:
(
Pdefn(\lazyExpand, Pfunc({
|array|
Routine({
array.do(_.yield)
})
}));
Pbind(
\amp, Pseq([
0.5 ! 1,
0.5 ! 2,
0.5 ! 3,
], inf),
\degree, Pdefn(\lazyExpand) <> Pseq([ [0, 1, 2] ], inf)
).trace.play
)