Avoid polyphony in parallel Pbinds

I have a bunch of MIDI Pbinds running in parallel in a Ppar. Each one of them uses simple arrays of 1s and 0s in a Pseq for \amp, but, here’s the catch, I would love to avoid 2 voices/sounds playing at the same time. Can you think of a way to do this? For simplicity, let’s say there are 3 Pbinds, though there could be more, and their respective \amp key is driven by these 3 Pseqs:

Pseq([1,0,0,0,1,0,0,1,0,1], inf)
Pseq([0,1,0,0,1,1,0,1,1,1], inf)
Pseq([0,0,1,0,0,1,0,0,1,0], inf)

Order is not important here, none of them is more important than the rest, so I’d be happy to find a way to avoid the overlap/poliphony without giving one Pbind more protagonism, if that makes sense.

Happy holidays, everyone

Ppar is designed for polyphony, so it seems like your phrasing your code wrong. What is it your trying to accomplish here?

My goal is to play several patterns at the same time but restrict the overall result to 1 sound/voice per step. I could of course make sure that happens by carefully selecting arrays of 0s and 1s for each one of the \amp keys, but i’d like them to be randomly generated, so there most likely be polyphony here and there.

i thought i could get somewhere by using just 1 random array and then manipulating it to create accompanying ones but my approach made it so that there are never any rests, cause there are no zeroes left:

(
a = [0,0,1,0,0,1,0,0,1,0];
b = (a-1).abs
)

not to mention that’s ok for 2 Pbinds, but not for, say 5…

thanks

The simple/naive way would be to keep a variable that knows if a note has been played in this cycle, and then use it to suppress the others.

But this will give priority in the order in which the patterns wake up, contradicting your other requirement “without giving one Pbind more protagonism.”

To avoid prioritizing one pattern over another, then the earlier patterns to process need to know the results of patterns that haven’t processed yet = know the future, which for obvious reasons is troublesome.

The normal pattern model is to perform events immediately when they’re generated, which makes it impossible in this model to withhold events and make decisions based on future calculations. (I don’t mean “future” as in “next rhythmic division” – I mean, if you have 5 patterns arriving at the same sounding time, they still process one after the other – if pattern 0 is processing now, then pattern 1’s result is in the future.)

So you can’t do it with Ppar.

You would need to write something to collect all the events to play concurrently first, then decide which of them to perform. This is possible but will involve some details that might be a little tricky.

hjh

1 Like

There are a bunch of ways to approach this, but I would probably try to work with the data in array form. You can represent the data as a nested array, where each subarray represents a note in time, and the elements of each subarray are the amplitude of each voice at that point in time. Therefore, if you only want one random voice playing at a time, each subarray should be all 0s except for a single randomly placed 1. Then, if you call .flop on the whole thing, each of the subarrays now represents the time series for a single voice, and this is what you want to put in the Pseqs. I’m typing this on my phone, but here is a minimal example that should (hopefully!) work:

(
var voices = 10;
var notes = 10;
var data = {(0 ! (voices - 1) ++ [1]).scramble} ! notes;
data = data.flop;
data = data.collect{|arr| Pseq(arr, inf)};
// then put those Pseqs in whichever Pbinds you want
)

EDIT: that method doesn’t have any rests, but you could randomly replace some 1s with 0s to add rests.

I assume you mean pitches (or some other pbind key having the same value at one moment in time)?

Does this mean, 1 pitch per rhythmic value? Or one Pbind per pitch?

I’m also confused by your use of ‘polyphony’, do you mean it in the classical sense (monophonic, homophonic), or in the way some synths use the term (multiple oscillators per pitch)?

Based on what you’ve written I think you are describing a hocket - Hocket - Wikipedia?

To do that, you should not use Ppar as it is designed for having multiple voices. Instead, vary the \freq key.
There is also no need to set the amp to 0, just use Rest.
e.g. …

// melodies of each voice
~v1 = [0, 1, 2, 3, 4];
~v2 = ~v1 + 2;
~v3 = [4, 3, 2, 1, 0];

~vs = [~v1, ~v2, ~v3].flop.flat

Pbind(
	\scale, Scale.major,
	\root, 0,
	\degree, Pseq(~vs, inf),
	\octave, Pseq([3, 4, 5], inf),
	\dur, Pwrand([0.52, Rest()], [0.7, 0.3], inf),
	\legato, Pwhite(0.5, 2)
).trace.play;	

Now as only one voice has a new note at a time, it is impossible for them to play the same note at the same time. It might be possible for two or more voice to have the same pitch at the same time, but that would require them to enter at different times. To avoid that, make sure the melodies don’t overlap.

I was not familiar with Hockets at all (thanks for the tip) but i was thinking more in terms of what in drumming is called linear drumming. First definition from Google: “a drum technique wherein a player only strikes one instrument on a given beat. For instance, if the drummer hits the bass drum with a kick drum pedal, they cannot simultaneously strike a snare drum with a drumstick”.

My intention is not so much to do this with drum sounds, but the idea is similar. So if the MIDI patterns are triggering 4 synths, whatever they are, each of those would be equivalent to one of the instruments in a drum kit, played with this linear drumming technique = no more than 1 synth plays at a time (on the same step).

I understand that the use of the term polyphony in this case may be misleading. I originally thought I could phrase it as disjoint or mutually exclusive, but i think that may be even more confusing :slight_smile: Also, polyphony kinda works for me in the sense that i’m thinking of these parallel patterns as one integrated thing, a system, so it’s similar to how some synths handle voice allocation.

Thanks everybody for the suggestions!

Here’s what I use:

// voiceLimiter.scd
Pdef(\voiceLimiter, {
	var voices = Array(16);
	var func = {
		|e|

		e[\id].asArray.do {
			|id|
			voices = voices.add(id);
		};

		~count = ~count ?? { ~voices };
		max(0, voices.size - (~count ?? {4})).do {
			|i|
			s.sendMsg(\n_set, voices[i], \gate, - 1 - (~gateTime ?? {0.2}));
		};

		e.onFree({
			|node|
			voices.remove(node.nodeID);
		});
	};

	Pbind(
		\callback, Pfunc({
			|e|
			e[\callback].addFunc(func);
		})
	)
});

You’ll need to run this first for the onFree part to work: nodeObjEvent.scd · GitHub

You’d compose this with your pattern to do voice limiting:

Pdef(\player, 
  Pdef(\voiceLimiter).set(\voices, 1)
  <> Ppar([
       Pbind(...),
       Pbind(...),
       Pbind(...)
   ])
)

One thing to note is that — when you try to play a new event beyond the voice limit, it ONLY sends a \gate message to the old voices to stop it. This may not stop it right away, and would usually just enter the release part of the envelope, which means the actual voices present at a given time is greater than the voice limit. You can of course do whatever you want in place of that sendMsg - e.g free the synth immediately or whatever else.

Also, I’m removing the synth from the voices list ONLY when it’s actually freed (the onFree bit) because I mainly use this for limiting CPU usage, so I care about how many synths I’m actually running. If this is more about limiting voices for musical purposes, you might just remove them as soon as you send the message to end that synth, since from your perspective you’re no longer really worrying about them.

1 Like

If you are sequencing pitches on the same rhythmic clock, what I think you’re describing is easier than if you need the patterns to have arbitrary rhythms.

Here you can specify however many input pitch patterns for different instruments, which are selected randomly by the \selection key. A string “” represents a rest, if all voices have strings there will be silence. Hope this helps!

(
SynthDef(\sine, {
  var sig = SinOsc.ar(\freq.kr(220)* [1, 1.005]).sum * \amp.kr(0.1);
  sig = sig * Env.adsr(0.01, 0.3, 0.5, 0.7).kr(2, \gate.kr(1));
  Out.ar(\out.kr(0), sig!2);
}).add;
SynthDef(\saw, {
  var freq = \freq.kr(220);
  var sig = RLPF.ar(Saw.ar(freq * [1, 1.005]).sum, freq * LFNoise2.kr(2, 2, 4), 0.5).tanh * \amp.kr(0.1) * 1.3;
  sig = sig * Env.adsr(0.15, 0.9, 0.2, 0.5).kr(2, \gate.kr(1));
  Out.ar(\out.kr(0), sig!2);
}).add;
)
(
Pbind(
  \insts, [\sine, \saw],
  \sine, Pseq(["", 1, 2, ""], inf),
  \saw, Pseq([5, "", 3, 4, ""], inf),
  \selection, Pfunc { |event| 
    event[\insts].collect { |inst|
      [inst, event.at(inst)]
    } .reject { |arr| 
      arr[1].isString 
    } .choose ? [\none, Rest()] 
  } .trace,
  \degree, Pfunc({ |event| event[\selection][1] }),
  \instrument, Pfunc({ |event| event[\selection][0] })
).play;
)