In a word, no.
What you’re describing is the way the server /s_get
command works.
This question comes up often enough (3-4 times in the last month or two) that I think we should have a more intelligent class to supersede Synth. I just made a sketch of one:
NOTE: This is not a finished class and I’m afraid I wouldn’t have time to finish it in the immediate future. Someone else is more than welcome to. It’s just enough to do a demo of how get
might look if it automatically dispatches to a bus when the control is mapped. I’ve not replicated the entire Synth interface, but it would need to be done to consider it production-ready. It’s also not carefully debugged.
SynthAware {
// "has-a" synth, not "is-a" synth
var <synth, <nodeMap;
*new { |defName, args, target, addAction = \addToHead|
^super.new.init(defName, args, target, addAction)
}
// needs *basicNew, newMsg, etc.
init { |defName, args, target, addAction|
synth = Synth(defName, args, target, addAction);
nodeMap = IdentityDictionary.new;
SynthDescLib.at(defName).controls.do { |cname|
if(cname.name != '?') {
nodeMap[cname.name] = cname.defaultValue;
}
};
this.updateMap(args);
}
get { |index, action|
var value;
if(index.isNumber) {
index = SynthDescLib.at(synth.defName)
.controlNames[index]; // not sure about array args
};
value = nodeMap[index];
if(value.isNumber or: { value.isSequenceableCollection }) {
action.value(value)
} {
// assume a bus
value.get(action)
}
}
set { |... pairs|
synth.set(*pairs);
this.updateMap(pairs)
}
map { |... pairs|
synth.map(*pairs);
(1, 3 .. pairs.size-1).do { |i|
// "should" work?
pairs[i] = pairs[i].asBus(\control, 1, synth.server);
};
this.updateMap(pairs)
}
updateMap { |argPairs|
argPairs.pairsDo { |key, value|
this.updateOneMap(key, value)
}
}
updateOneMap { |key, value|
var isMap = false, rate;
case
{ value.isString } {
if("ac".includes(value[0])) {
isMap = true;
(1 .. value.size-1).do { |i|
if(value[i].isDecDigit.not) {
isMap = false
}
}
};
rate = if(value[0] == $a) { \audio } { \control };
}
{ value.isSymbol } {
isMap = value.isMap;
value = value.asString;
rate = if(value[0] == $a) { \audio } { \control };
};
if(isMap) {
value = Bus(rate, value.asString[1..].asInteger, 1, synth.server);
};
nodeMap.put(key, value)
}
free { synth.free }
}
Paste that into a new code window, File → Save As Extension and recompile the class library. Then you can do:
s.boot;
c = Bus.control(s, 1);
~lfo = { LFDNoise3.kr(3).exprange(200, 800) }
.play(outbus: c);
a = SynthAware(\default);
a.get(\freq, _.postln); // prints 440.0
a.set(\freq, c.asMap); // either one should behave the same
a.map(\freq, c.index); // this is to test busnum --> Bus object
a.nodeMap[\freq] // now it's a Bus
a.get(\freq, _.postln); // prints the value from the bus
a.free; ~lfo.free;
“Why doesn’t Synth do this already?” One answer is that tracking node arg values in the client adds weight, and you might not always need it (in which case it would just be a waste of memory and CPU).
“Why didn’t somebody make a class like this before?” I get the feeling at times that SC programming culture has a very strong bias toward the low-level server abstractions (SynthDef, Group, Synth) – starting with the help system, where everything is about Synths, leading users to the conclusion that Synth is the way. I think when the requirements get a bit more complex, we need smarter abstractions on top of the basic ones, and then shift the culture toward the smarter abstractions. But I don’t think this is going to happen anytime soon; the bias is too strong.
(JITLib is one exception, though it’s an entire alternate system that behaves quite differently from Synth etc.)
hjh