hey, i have been trying to prototype an object which takes a SynthDef, prepares a NodeProxy using .prime and you specify which parameters should be controlled by Sliders and which ones by RangeSliders. The basic functionality is working already.
(
SynthDef(\test, {
var freqRange = \freqRange.kr([2500, 3500], spec: ControlSpec(100, 8000));
var ringRange = \ringRange.kr([0.1, 0.15], spec: ControlSpec(0.1, 2.0));
var ampRange = \ampRange.kr([0.1, 0.2], spec: ControlSpec(0.1, 1.0));
var sig = Splay.ar(
Array.fill(3, {
Ringz.ar(
Dust.ar(\dens.kr(5, spec: ControlSpec(1, 10))),
exprand(freqRange[0], freqRange[1]),
exprand(ringRange[0], ringRange[1]),
exprand(ampRange[0], ampRange[1])
)
})
).distort;
sig = sig * \amp.kr(0.5, spec: ControlSpec(0, 1));
Out.ar(\out.kr(0), sig);
}).add;
)
(
~makeNodeProxy = { |synthDefName, singleParams, rangeParams, initArgs =#[], numChannels = 2|
Environment.make{ |self|
// NodeProxy Definitions
~synthDef = SynthDescLib.global[synthDefName].def ?? {
Error("SynthDef '%' not found".format(synthDefName)).throw
};
~nodeProxy = NodeProxy.audio(s, numChannels);
~nodeProxy.prime(~synthDef);//.set(*initArgs);
// GUI Definitions
~rangeParams = IdentityDictionary.new();
~singleParams = IdentityDictionary.new();
~singleParamViews = IdentityDictionary.new();
~rangeParamViews = IdentityDictionary.new();
// GUI methods
// TO DO: set up dependencies
~setUpDependencies = { |self|
};
// TO DO: add nodeProxyChanged
~nodeProxyChanged = { |self, what, args|
};
// TO DO: add parameterChanged
~parameterChanged = { |self, key, val|
};
~makeRangeParamSection = { |self|
//self.rangeParams.do{ |spec| spec.removeDependant(specChangedFunc) };
self.rangeParams.clear;
self.nodeProxy
.controlKeysValues(except: self.nodeProxy.internalKeys ++ singleParams)
.pairsDo{ |key, val|
var spec;
spec = (self.nodeProxy.specs.at(key) ?? { Spec.specs.at(key) }).asSpec;
//"Spec for paramname %: %".format(key, spec).postln;
//spec.addDependant(specChangedFunc); ????
self.rangeParams.put(key, spec);
};
};
~makeSingleParamSection = { |self|
//self.singleParams.do{ |spec| spec.removeDependant(specChangedFunc) };
self.singleParams.clear;
self.nodeProxy
.controlKeysValues(except: self.nodeProxy.internalKeys ++ rangeParams)
.pairsDo{ |key, val|
var spec;
spec = (self.nodeProxy.specs.at(key) ?? { Spec.specs.at(key) }).asSpec;
//"Spec for paramname %: %".format(key, spec).postln;
//spec.addDependant(specChangedFunc); ????
self.singleParams.put(key, spec);
};
};
~makeSingleParamViews = { |self|
var sliderView;
sliderView = View.new().layout_(VLayout.new());
self.makeSingleParamSection.();
self.singleParams.sortedKeysValuesDo{ |key, spec|
var layout, paramVal;
var slider, valueBox;
layout = HLayout.new(
[StaticText.new().string_(key), s: 1],
);
paramVal = self.nodeProxy.get(key);
slider = Slider.new()
.orientation_(\horizontal)
.value_(spec.unmap(paramVal))
.action_({ |obj|
var val = spec.map(obj.value);
valueBox.value = val;
self.nodeProxy.set(key, val);
});
valueBox = NumberBox.new()
.action_({ |obj|
var val = spec.constrain(obj.value);
slider.value_(spec.unmap(val));
self.nodeProxy.set(key, val);
})
.decimals_(3)
.value_(spec.constrain(paramVal));
//used to be able to fetch the sliders later when they need to be updated
//singleParamViews.put(key, (type: \number, slider: slider, numBox: valueBox));
layout.add(valueBox, 1);
layout.add(slider, 4);
sliderView.layout.add(layout);
};
sliderView;
};
~makeRangeParamViews = { |self|
var rangeSliderView;
rangeSliderView = View.new().layout_(VLayout.new());
self.makeRangeParamSection.();
self.rangeParams.sortedKeysValuesDo{ |key, spec|
var layout, paramLoVal, paramHiVal;
var rangeSlider, lowValueBox, highValueBox;
layout = HLayout.new(
[StaticText.new().string_(key), s: 1],
);
paramLoVal = self.nodeProxy.get(key)[0];
paramHiVal = self.nodeProxy.get(key)[1];
rangeSlider = RangeSlider.new()
.orientation_(\horizontal)
.lo_(spec.unmap(paramLoVal))
.hi_(spec.unmap(paramHiVal))
.action_{ |obj|
var loVal = spec.map(obj.lo);
var hiVal = spec.map(obj.hi);
lowValueBox.value = loVal;
highValueBox.value = hiVal;
self.nodeProxy.set(key, [loVal, hiVal]);
};
lowValueBox = NumberBox.new()
.action_({ |obj|
var val = spec.constrain(obj.value);
rangeSlider.lo_(spec.unmap(val));
self.nodeProxy.set(key, val);
})
.decimals_(3)
.value_(spec.constrain(paramLoVal));
highValueBox = NumberBox.new()
.action_({ |obj|
var val = spec.constrain(obj.value);
rangeSlider.hi_(spec.unmap(val));
self.nodeProxy.set(key, val);
})
.decimals_(3)
.value_(spec.constrain(paramHiVal));
//used to be able to fetch the sliders later when they need to be updated
//rangeParamViews.put(key, (type: \number, slider: slider, numBox: valueBox));
layout.add(lowValueBox, 1);
layout.add(rangeSlider, 4);
layout.add(highValueBox, 1);
rangeSliderView.layout.add(layout);
};
rangeSliderView;
};
~makeGui = { |self|
var window, sliderView, rangeSliderView;
window = Window(self.synthDef.name, Rect(10, 500, 440, 320)).front;
window.layout = VLayout.new();
sliderView = self.makeSingleParamViews.();
rangeSliderView = self.makeRangeParamViews.();
window.layout.add(sliderView, 1);
window.layout.add(rangeSliderView, 1);
window;
};
}.know_(true);
};
x = ~makeNodeProxy.(\test, [\dens, \amp], [\freqRange, \ringRange, \ampRange]);
x.makeGui;
)
x.nodeProxy.play;
i have been building it looking into the source code of NodeProxyGui2, which is awesome but Im missing two functionalities i need:
- not every parameter should end up in the GUI, there are some i would like to control by using .set
- i would like to have RangeSliders for some of the parameters
this results in a slimmer GUI then one filled with parameters i dont want there or parameters which need two Sliders for low and high value instead of one RangeSlider.
The first question is: other then specifying an array NamedControl in the SynthDef and using [0] and [1] to map them to low and high values, would it also be possible to specify two NamedControls one for \low.kr
and one for \high.kr
and their specs and map them to the RangeSlider? I have some code build on top of that which has a problem with using arrays which is not easily fixed i think.
The second question is: i have doubled alot of code ~makeRangeParamSection
and ~makeSingleParamViews
are nearly identical with ~makeRangeParamSection
and ~makeRangeParamViews
is there a better way to go about that without retyping most of the stuff?
The third question is: in ~makeRangeParamSection
and ~makeSingleParamSection
im using .controlKeysValues(except: self.nodeProxy.internalKeys ++ singleParams or rangeParams)
to filter out the params which should either end up as Sliders or RangeSliders. Is this a good way of doing things? Or should i better search for specific key names?
There is some functionality in NodeProxyGUi2 for example using .set on the Nodeproxy and the Sliders will update automatically which i wasnt able to figure out yet.
But i already learned alot looking into the code and writing some on my own