Using a mute groups with a Pbind?

I was wondering how to implement “mute groups” in supercollider with patterns. E.g., create a group, and various patterns that play in the group, and always send a \gate, 0 message before creating a new synth.

// So, here's a synth with a gate argument:
SynthDef(\adsr, {
	arg freq=200, gate=1, amp=0.2;
	var env = EnvGen.ar(Env.adsr(), gate, doneAction:2);
	Out.ar(0, SinOsc.ar(freq, mul:amp)!2 * env);
}).add;

// The group to mute
~muteGroup = Group.new(s);

// Start playing something on that group
x = Synth(\adsr, [\freq, 800, \amp, 0.05], ~muteGroup);

// How do I make this Pbind call ~muteGroup.set(\gate, 0) 
// each time in creates a new synth?? 
// (If it works, then x should be automatically 
// released when the Pbind starts):

Pbind(
	\group, ~muteGroup
	,\instrument, \adsr
	,\degree, Pseq([0, 4, 7], inf)
	,\dur, 4
)]).play;

Perhaps Pfunc or something would be useful, but I’m not sure how to integrate that here?

Edit: maybe it is as simple as this:

Pbind(
	\group, ~muteGroup
	,\pattern, Pfunc{ ~muteGroup.set(\gate,0); } // release all synths in this group
	,\instrument, \adsr
	,\degree, Pseq([0, 4, 7], inf)
	,\dur, 4
).play;

Okay, here’s an alternative attempt - I think this might be a bit more flexible, as it would be super easy to adapt to more advanced voice-stealing logic (by e.g. stopping the oldest note, or the note that’s closest in pitch, etc). The one trick is \sendGate, false - if you don’t do this, the Events will try to free themselves again even after they have been gated due to voice stealing. I’m not 100% sure this is correct, but it’s a good start I think.
Incidentally, this doesn’t require that notes be playing in the same server Group.

~muteGroups = ();
Pdef(\pattern, Pbind(
	\dur, 1/4,
	\degree, Prand([0, 2, 5, 7, 10], inf),
	\octave, Pseq([3, 4, 5], inf),
	\muteGroup, Pseq([\a, \b, \c, \d], inf),
	\sustain, 10,
	\sendGate, false,
));

Pdef(\muteGroup).clear;
Pdef(\muteGroup, Pfunc({
	|event|
	if (~muteGroups[event.muteGroup].notNil and: { ~muteGroups[event.muteGroup][\isPlaying] }) {
		"Already playing something from muteGroup % - releasing the old note".format(
			event.muteGroup
		).postln;
		
		~muteGroups[event.muteGroup].set(\gate, 0);
	};
	~muteGroups[event.muteGroup] = event;
	event; // pass the event along
}));


Pdef(\player, Pdef(\muteGroup) <> Pdef(\pattern)).play;
1 Like

One more attempt, because I’ve really wanted this in the past - turns out it’s pretty straight-forward… This does proper voice-stealing, of the sort you find on a real polysynth - including both soft voice limits where we gate, and a hard voice limit where we free immediately (presumably because otherwise there would be a dropout).

~muteGroups = ();
~softLimit = 4;
~hardLimit = 5;
~strategies = (
	\oldest: {
		|playing, current|
		playing.minItem({ |e| e.startTime })
	},
	\newest: {
		|playing, current|
		playing.maxItem({ |e| e.startTime })
	},
	\nearest: {
		|playing, current|
		playing.minItem({ 
			|e|
			(e.use(_.freq) - current.use(_.freq)).abs
		})
	},
	\random: {
		|playing, current|
		playing.choose
	}
);

Pdef(\pattern, Pbind(
	\dur, 1/4,
	\degree, Prand([0, 2, 5, 7, 10], inf),
	\octave, Pseq([3, 4, 5], inf),
	\muteGroup, \a,
	\release, 3,
	\sustain, 10,
	\sendGate, false,
));

Pdef(\muteGroup).clear;
Pdef(\muteGroup, Pfunc({
	|event|
	var voiceToFree, voices = ~muteGroups[event.muteGroup];
	
	event[\startTime] = thisThread.clock.beats;
	
	voices = voices.select(_[\isPlaying]); // purge any non-playing voices	
	while { voices.size > (~softLimit - 1) } {
		voiceToFree = ~strategies[\nearest].(voices, event);
		if (voices.size > (~hardLimit - 1)) {
			"Way too many voices, freeing voice at %".format(voiceToFree.nodeID).postln;
			voiceToFree.free; // free immediately
		} {
			"Gating voice at %".format(voiceToFree.nodeID).postln;
			voiceToFree.set(\gate, 0); // let envelope free in due course
		};
		voices.remove(voiceToFree);
	};
	
	voices = voices.add(event);
	~muteGroups[event.muteGroup] = voices;
	
	event; // pass the event along
}));


Pdef(\player, Pdef(\muteGroup) <> Pdef(\pattern)).play;

The ddwVoicer quark has implemented voice stealing for about 15 years :wink: with pattern support too (\type, \voicerNote) and fingered portamento (MonoPortaVoicer), and “global controls” similar to synth knobs. To me, it’s an essential class (if I hadn’t written it, I’d be seriously limited in SC)… just advertising :stuck_out_tongue_winking_eye:

hjh

1 Like