Noob: how to control an arg in a polyphonic synth controlled via MIDI?

Another thing I am currently trying to learn how to accomplish in SC is to build a polyphonic synthesizer that (a) I can play via MIDI, and (b) whose parameters (= args of its SynthDef) I can change while playing it – with the ultimate goal to use MIDI controllers for changing its parameters.

If I understood the concept behind this explanation correctly, each voice of my MIDI-controlled polyphonic synthesizer is actually a synth node on the server with its own ID, so I can’t address each individual synth via storing it one variable and using VARIABLE.set(KEYWORD_VALUE_PAIRS):

But if I ensure that all voices (synths) are put into a group, I can assign this group to a variable and change args of the enclosed synths using VARIABLE.set(KEYWORD_VALUE_PAIRS), no?

So I created a new group:
g = Group.new;

Then I added a SynthDef (please see end of this post for complete code) and in my MIDI function ensured that the synths (= all voices) are created in the target group g:

MIDIFunc.noteOn({
	|vel,num|
	//put a synth into the played index of array
	notes[num] = Synth(\tone, 
		[\freq, num.midicps, \amp, vel/127, \gate, 1],
		target: g
	);
});

When playing, this sounds and looks successfull:

Now to the frustrating part: I want to set the arg named feedback for all sounding and all future notes I will be playing. So I did this:
g.set(\feedback, 3);

Unfortunately, this only works for the currently sounding notes. I guess I must have misunderstood something very thoroughly, but for the life of me I can’t figure out what it is. Please, how can I accomplish my goal? Thanks much in advance for any help!

Complete Code
g = Group.new;

(
SynthDef(\tone,{
	arg freq = 300, amp= 0.1, feedback=0.7, gate=0;
	var sig, env;
	env = EnvGen.kr(Env.asr(0.1,1,1),gate,doneAction:2);
	sig = SinOscFB.ar(freq,feedback*env*amp,amp);
	sig = sig*env;
	sig=Pan2.ar(sig,0);
	Out.ar(0,sig*0.1);
}).add
)

(
var notes;
notes = Array.newClear(128);

MIDIFunc.noteOn({
	|vel,num|
	notes[num] = Synth(\tone, 
		[\freq, num.midicps, \amp, vel/127, \gate, 1],
		target: g
	);
});

MIDIFunc.noteOff({
	|vel,num|
	notes[num].release;
});
)

g.set(\feedback, 3);
P.S.

I need go to bed now, and as the working week starts again tomorrow morning, it will take some time until I have a chance to react to & digest any reaction. But I had to get this question out of my system before I can sleep. My journey with SC has been quite a rollercoaster ride: First I think I understood a concept (joy!), then I try to apply it to the things I want to achieve, only to find out I have not understood the concept (frustration). Perhaps I bite off more than I can chew. I dont know. So please pardon all the questions I am asking, and thank you for your continued patience & help!

In your other thread, I suggested control buses.

This is a good solution for polyphony too – with one other consideration.

In a polyphonic arrangement, some parameters are independent for each voice while other parameters are common to all voices. This is so typical in hardware synths that we don’t even notice – it’s just “natural” that the mod wheel applies identically to every voice while MIDI note number is different for each. (Related: I’m not sure that it has a clearly defined meaning to set a value independently in an existing voice – how do you decide which voice?)

You can have a control bus per shared input (meaning, spend a little time planning which are shared) and map them for new synths, while not using a bus for independent inputs.

hjh

1 Like

This isn’t quite the usual Sc approach, but if you’re happy with a fixed number of voices then a simple graph works nicely.

Share parameter across voices:

arg numHarm = 1;
Splay.ar(Voicer.ar(5, { arg e; Blip.ar(e.p.midicps, numHarm) * e.w * e.z }))

Parameter per voice:

var f = { arg e; Blip.ar(e.p.midicps, NamedControl.kr("numHarm" ++ e.v, 1)) * e.w * e.z };
Splay.ar(Voicer.ar(5, f))

(Here .p is pitch, .w is gate, .z is pressure, .v is voice number.)

Ps. Voicer.ar just gives names to control buses, you configure processes to put midi data (or whatever control source you like, an Sc pattern &etc.) onto control buses separately.

Pps. Tiny demonstration video of above: https://vimeo.com/643332351

Quick PPS, Voicer.ar is an extension (from… somewhere…). Also it conflicts with my ddwVoicer quark, which is an alternate approach to polyphony with voice stealing and shared controls.

hjh

I’m not sure this idiom needs a library, it’s very simple!

However, in case useful, I write Voicer something like:

Voicer {
    *ar {
        arg numVoices, voiceFunc;
        ^(0 .. numVoices - 1).collect({ arg v; voiceFunc.value(Param.new.fromArray(v, In.kr(13000 + (v * 10), 9))) })
    }
}

and Param something like:

Param {
    var <>v, <>w,<>x,<>y,<>z,<>o,<>rx,<>ry,<>p,<>px;
    fromArray {
        arg voice, paramArray;
        var a = paramArray;
        v = voice; w = a[0]; x = a[1]; y = a[2]; z = a[3]; o = a[4]; rx = a[5]; ry = a[6]; p = a[7]; px = a[8]
    }
}

This particular arrangement is rather biased towards certain kinds of controllers (i.e. Haken, Wacom, Sensel, tablets &etc.).

Ps. The naming can also be the other way about, as a function of voice index, i.e.

16.collect({ arg i; SinOsc(KeyPitch(i).midicps, 0) * KeyDown(i) * KeyVelocity(i) }).splay

Definitions given layout above:

KeyDown { *new { arg voice; ^In.kr(13000 + (voice * 10) + 0, 1) } }
KeyTimbre { *new { arg voice; ^In.kr(13000 + (voice * 10) + 2, 1) } }
KeyVelocity { *new { arg voice; ^In.kr(13000 + (voice * 10) + 3, 1) } }
KeyPitch { *new { arg voice; ^In.kr(13000 + (voice * 10) + 7, 1) } }

Pps. For completeness, below is one way to copy Midi noteOn and noteOff event data to control buses, there’s lot’s of others.

var copyMidi = {
    arg server;
    var numVoices = 16;
    var nextVoice = 0;
    var voiceTable = Array.newClear(128); // keyNumber -> voice
    MIDIIn.noteOn = {
        arg sourceId, channel, keyNumber, keyVelocity;
        var baseIndex = 13000 + (nextVoice * 10);
        server.sendMsg('/c_set', baseIndex + 0, 1); // w
        server.sendMsg('/c_set', baseIndex + 3, keyVelocity / 127); // z
        server.sendMsg('/c_set', baseIndex + 7, keyNumber); // p
        voiceTable.put(keyNumber, nextVoice);
        nextVoice = nextVoice + 1 % numVoices
    };
    MIDIIn.noteOff = {
        arg sourceId, channel, keyNumber, keyVelocity;
        var voice = voiceTable.at(keyNumber);
        server.sendMsg('/c_set', 13000 + (voice * 10) + 0, 0) // w
    };
};

if(MIDIClient.initialized, copyMidi.value(Server.default))
1 Like