What are the methods of getting the arg values of a Synth?

I have this:

SynthDef(\sine,{
 |a=100|

}).add;

Eventually this is mapped to a bus control

b=Bus.control();
~synth=Synth(\sine);
~synth.map(\a,b);

When I do this:

t=Task({
inf.do({
 synth.get(\a,{
    |res|
    res.postln;
});;
t.start;

I get the initial value, but not if it is mapped to a bus control. Is there a way of getting the mapped input values short of reading it from the bus?

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

3 Likes

Quick note, I removed a couple of debug posts… this was one where I thought it would be very quick to write but a few things were trickier than expected… and I got impatient :flushed:

Do update before using in a public context…

hjh

The new class methodology works. The question comes up often probably because of the way the language is structured, and the tutorials, hint that it should be an affordance that has been implemented. Sometimes I think that language/framework/library writers deliberately have awkward design choices, not in supercollider but elsewhere, to generate some kind of traffic.

FWIW I don’t think this is a deliberately awkward design choice. I’ve been around here for 20 years and there really hasn’t been, at any time, any serious consideration of a “Synth-plus” framework that would meet common user expectations (state maintenance, perhaps even smart patching between nodes). I don’t fully understand why there seems to be a vacuum around this topic, tbh (except JITLib).

hjh