You’re welcome! First off, I made a misguided suggestion in my earlier post. Using Group /n_set isn’t the best idea since while the /n_sets to broadcast to the synths, every synth still has to be initialized with the correct parameters.
@carltesta brought up control buses, which are much better. Let’s get into those… Single-control version looks like this:
var cutoffBus;
cutoffBus = Bus.control(nil, 1);
Pbind(*[
instrument: \synth,
cutoff: cutoffBus.asMap,
]).play;
MIDIdef.cc(\cutoff, { |val, num, chan, src|
cutoffBus.set(val.linlin(0, 255, 1.0, 3.0));
}, 14);
For many controls, use a loop to create a new control bus for each control name. We build up a baseEvent dictionary as before, and a controlBuses dictionary to hold the buses:
var controlNames, controlBuses, baseEvent;
controlNames = [\cutoff, \pitchBend, \resonance];
baseEvent = ();
controlNames.do { |name|
var controlBus;
controlBus = Bus.control(nil, 1);
controlBuses[name] = controlBus;
baseEvent[name] = controlBus.asMap;
};
Pbindf(baseEvent, Pbind(*[
instrument: \synth,
dur: 0.1,
]).play;
MIDIdef.cc(\cutoff, { |val, num, chan, src|
controlBuses[\cutoff].set(val.linlin(0, 255, 1.0, 3.0));
}, 14);
If you are using Patterns or Events, be careful not to use any control name that conflicts with the built-in Event type! For example, if you were to add a parameter called \detune, you’ll run into a bug since \detune has a built-in meaning in the default Event type. The patterns system is not smart enough to catch such issues for you.
(When I ran into this bug in my own work, it was the last straw that got me to quit using patterns/events entirely and go back to raw Synths/Routines. I have found that the patterns system has too much built-in magic and edge cases for my taste. Luckily control buses work just as well for Synths/Routines as for patterns.)
Beyond that, a few notes, can elaborate on request:
- Use lags on the synthdef inputs to avoid stairstepping.
- By default, every bus is initialized to a 0 value. You will want to set the buses to reasonable defaults immediately after they are created.
- There is no need for Pfuncn here. Before we were sending numerical values from sclang to scsynth, now we are sending a bus number that points to a changing signal on scsynth. As such, the baseEvent object is no longer changing. only the buses that it points to are changing. But, if you were to mix per-note modulation with continuous modulation, you would need to use the Pfuncn trick above.
- Since control buses exist on the server, you can play LFO synths into them. You can do this using a control-rate synthdef with an
Out.kr to the appropriate bus. Watch out for node ordering, these nodes have to be ordered before the synth nodes that are playing.
- If you have a lot of MIDI controls then you may want to consider using a loop to abstract over the
MIDIdef creations, using a dictionary of ControlSpecs to define the linlin and linexp mappings.
P.S. would like to plug for my blog, where I have a post collecting some SuperCollider usage tips: https://nathan.ho.name/posts/supercollider-tips/ (sorry the https is screwed up, oops) Honestly this topic could become a new post in its own right…