Here is a quick and dirty way which scales nicely. Not a full MVC model, but the most important bits of it. This method needs to be expanded if you want to be able to close and open your ui without loosing values. The code uses two basic tricks
Events can have setters:
(
p = (); // empty event
p.add(\amp_ -> {|self, val|
self = val;
('amp set to ' ++ val).postln
});
p.amp = 5; // -> 5. watch post window
)
// you are not actually setting the amp directly with p.amp = 5
(
// we can do anything inside the setter function
p = (); // empty event
p.add(\amp_ -> {|self, val|
val = val * 300;
self = val;
('amp set to ' ++ val).postln
});
p.amp = 5; // -> 1500
)
// notice the important difference betwen p.amp = 10 and p[\amp] = 10
p[\amp] = 10
p.amp -> 10 // sets value but does not call function so now assigning is normal
p.amp = 10 // now function is called and val is multiplied by 300 -> 1500
Knobs, sliders, buttons etc all store values in the range [0, 1]. We can map and unmap values back and forth between ui elements and real values:
~spec = [20, 100, \lin].asSpec; // simple default linear mapping, range[20, 100]
// map some value to ui range [0, 1]
~spec.unmap(30); //-> 0.125
// if value is 0.125 in ui:
~spec.map(0.125) // -> ui val maps back to real range: 30
~spec2 = [20, 20000, \exp].asSpec; // for freq
~spec2.unmap(730) // -> 0.52076428815216
~spec2.map(0.52076428815216) // -> 730
~spec3 = [0, 1, \lin, 1].asSpec // one step = either 0 or 1
~spec3.unmap(0.2) // we feed it a float, rounds to 0
~spec3.unmap(0.7) // rounds to 1
Here is a full example, change to your chosen synth arg names and implement the synth call where stated. This structure scales well, easy to add parameters and controls.
(
// match names to your actual controls
var seq = [\lpCut, \amp, \state];
var defaults = (
lpCut: 2000,
amp: 0.2,
state: 0
);
var spec = (
lpCut: [60, 10000, \exp].asSpec, // exp mapping for freq
amp: [0, 1].asSpec, // amp linear mapping [0, 1]
state: [0, 1, \lin, 1].asSpec // 1 step = val is either 0 or 1
);
var ui = (
lpCut: Knob(),
amp: Knob(),
state: Button().states_([[\Off], [\On]]);
);
var vals = ();
var view = View().layout_(HLayout(*seq.collect{|key|
VLayout(StaticText().string_(key.toUpper).align_(\center), ui[key])
})).front.alwaysOnTop_(true);
seq.do{|key|
vals.add((key ++ \_).asSymbol -> { |self, val|
self = val;
{ ui[key].value = spec[key].unmap(val) }.defer; // unmaps the real val to the range [0, 1]
(key ++ ' set to: ' ++ val).asSymbol.postln;
// ADD SYNTH CALL HERE, eg. mySynth.set(key, val)
});
vals.perform(key.asSetter, defaults[key])
};
seq.do{|key|
ui[key].action = {|n|
var val = spec[key].map(n.());
vals.perform(key.asSetter, val);
};
};
a = vals // assign to oneletter variable for ease
)
/// now setting from ui updates vals and set synth
/// Also the other way around, when set by code ui updates:
a.amp = 0.67
a.lpCut = 600
a.state = 1
a.state = 0