hey,
i have set up a ProtoDef for a modulation matrix, which dynamically creates a number of in and output busses and corresponding knobs, so you could set the amplitudes of each modulator and they are mixed and sent to a specific bus, which could be mapped to a specific param of the source. The routing is based on InFeedback
. The source and modulation are using NodeProxyGui2. I have been thinking alot about how to get that right for some months now, i hope you have some ideas
There are at least two things here:
1.) Im wondering if InFeeback
is capable of audio rate modulation, or how could i make sure the nodes are in the right order? I would actually dont have a problem to integrate the modulator in the ProtoDef
itself without the input busses.
2.) I first evaluate the modMatrix Prototype
, then i evaluate .play
on the modulator and after that .play
on the source. The modulator is mapped to a specific param of the source via \fmod, NodeProxy.for(topEnvironment[\modMatrix].outputBusses[0])
or \fmod, topEnvironment[\modMatrix].matrixProxy.bus.subBus(0, 0)
. When i then hit .stop
on the modulator the source is still playing with the same modulation, i would like to disable the modulation when i hit .stop
for the modulator. How can i do that?
ProtoDef for the modMatrix
(
ProtoDef(\modMatrix) {
~init = { |self|
self.numOfMappings = self.numOfMappings;
self.inputBusses = Order.new();
self.numOfModulators = self.numOfModulators;
self.outputBusses = Order.new();
self.getMatrix;
self.params = IdentityDictionary.new();
self.paramViews = IdentityDictionary.new();
self.window = Window.new(self.defName, Rect(10, 780, 320, 260)).front;
self.window.layout = VLayout.new(
self.makeTransportSection;
);
self.initFont;
self.window.view.children.do{ |c| c.font = self.font };
self.setUpDependencies;
self.makeGui;
self.midiController = MKtl(\faderfox, "faderfox_ec4");
};
~initFont = { |self|
var fontSize = 14;
self.font = Font.monospace(fontSize, bold: false, italic: false);
};
~setInputBusses = { |self|
self.numOfMappings.do{ |numInput|
var inputBus = Bus.audio(s, 1);
self.inputBusses.put(numInput, inputBus);
};
};
~setOutputBusses = { |self|
self.numOfModulators.do{ |numOutput|
var outputBus = Bus.audio(s, 1);
self.outputBusses.put(numOutput, outputBus);
};
};
~getMatrix = { |self|
self.setOutputBusses;
self.setInputBusses;
self.matrixProxy ?? {
self.matrixProxy = NodeProxy.audio(s, self.numOfModulators);
};
self.matrixProxy.source = {
self.outputBusses.collect{ |outputBus, numOutput|
var mods, amps, norm;
mods = self.inputBusses.collect{ |inputBus, numInput|
InFeedback.ar(inputBus);
};
amps = self.inputBusses.collect{ |inputBus, numInput|
var key, spec;
key = "mod%_%".format(numOutput, numInput).asSymbol;
spec = [0.0, 1.0, \lin].asSpec;
NamedControl.kr(key, spec: spec);
};
norm = (1 / amps).sum;
mods = (mods * amps).sum * min(norm, 1);
Out.ar(outputBus, mods);
};
};
self.matrixProxy;
};
~nodeProxyChanged = { |self|
self.matrixProxy.controlKeysValues.pairsDo{ |key, val|
if(self.params[key].notNil, {
var spec = self.params[key];
self.paramViews[key].value_(spec.unmap(val));
});
};
};
~setUpDependencies = { |self|
var nodeProxyChangedFunc = { self.nodeProxyChanged }.defer;
self.matrixProxy.addDependant(nodeProxyChangedFunc);
self.window.onClose_{
self.matrixProxy.removeDependant(nodeProxyChangedFunc);
self.matrixProxy.clear;
self.outputBusses.do{ |outputBus| outputBus.free };
self.inputBusses.do{ |inputBus| inputBus.free };
self.outputBusses.clear;
self.inputBusses.clear;
self.params.clear;
self.paramViews.clear;
};
};
~makeParamSection = { |self|
self.params.clear;
self.matrixProxy.controlKeysValues.pairsDo{ |key, val|
var spec;
spec = (self.matrixProxy.specs.at(key) ?? { Spec.specs.at(key) }).asSpec;
self.params.put(key, spec);
};
};
~makeParamViews = { |self|
self.paramViews.clear;
self.makeParamSection;
self.params.sortedKeysValuesDo{ |key, spec|
var paramVal, knob;
paramVal = self.matrixProxy.get(key);
knob = Knob.new()
.value_(spec.unmap(paramVal))
.action_{ |el|
self.matrixProxy.set(key, spec.map(el.value));
};
self.paramViews.put(key, knob);
};
self.paramViews;
};
~makeParamLayout = { |self|
var view, layout, rowsOfKnobs, modTexts, paramTexts;
view = View.new().layout_(VLayout.new());
self.makeParamViews;
paramTexts = self.numOfMappings.collect{ |numOutput|
StaticText.new().string_("param_%".format(numOutput));
};
modTexts = self.numOfModulators.collect{ |numInput|
StaticText.new().string_("mod_%".format(numInput));
};
rowsOfKnobs = self.paramViews.atAll(self.paramViews.order).clump(self.numOfModulators);
layout = GridLayout.rows(*
[[nil] ++ modTexts] ++
self.numOfMappings.collect{ |i| [paramTexts[i]] ++ rowsOfKnobs[i]};
);
view.layout.add(layout);
view.children.do{ |c| c.font = self.font };
view;
};
~paramsDo = { |self, func|
self.params.keysValuesDo{ |key, spec|
var paramVal;
paramVal = self.matrixProxy.get(key);
self.matrixProxy.set(key, func.(paramVal, spec));
};
};
~randomize = { |self, randmin = 0.0, randmax = 1.0|
self.paramsDo{ |val, spec|
spec.map(rrand(randmin, randmax));
};
};
~vary = { |self, deviation = 0.1|
self.paramsDo{ |val, spec|
spec.map((spec.unmap(val) + 0.0.gauss(deviation)).clip(0, 1));
};
};
~defaults = { |self|
self.paramsDo{ |val, spec|
spec.default;
};
};
~makeTransportSection = { |self|
var popup;
popup = PopUpMenu.new()
.allowsReselection_(true)
.items_(#[
"defaults",
"randomize parameters",
"vary parameters",
"document",
"post",
])
.action_({ |obj|
switch(obj.value,
0, { self.defaults },
1, { self.randomize },
2, { self.vary },
3, { self.matrixProxy.document },
4, { self.matrixProxy.asCode.postln },
)
})
.keyDownAction_({ |obj, char|
if(char == Char.ret, {
obj.doAction
})
})
.canFocus_(true)
.fixedWidth_(25);
HLayout.new(
[popup, align: \right]
);
};
~makeGui = { |self|
if(self.paramLayout.notNil, { self.paramLayout.remove });
self.paramLayout = self.makeParamLayout;
self.window.layout.add(self.paramLayout);
self.window;
};
~mapMidi = { |self|
self.unmapMidi;
self.params.sortedKeysValuesDo{ |key, spec, i|
var paramVal;
paramVal = self.matrixProxy.get(key);
self.midiController.elAt(\GR01, \kn, i)
.value_(spec.unmap(paramVal))
.action_{ |el|
{ self.matrixProxy.set(key, spec.map(el.value)) }.defer
};
};
};
~unmapMidi = { |self|
self.midiController.resetActions;
};
}
)
ProtoDef for getting NodeProxies from SynthDefs
(
ProtoDef(\makeNodeProxy) { |synthDefName, initArgs=#[], bounds, excludeParams=#[], ignoreParams=#[], numChannels = 2|
~init = { |self|
self.setNodeProxy;
self.makeGui;
};
~setNodeProxy = { |self|
self.synthDef = try{ SynthDescLib.global[self.synthDefName].def } ?? {
Error("SynthDef '%' not found".format(self.synthDefName)).throw
};
self.nodeProxy = NodeProxy.audio(s, self.numChannels);
self.nodeProxy.prime(self.synthDef).set(*self.initArgs);
};
~nodeProxyGui = { |self|
NodeProxyGui2(self.nodeProxy, show: false)
.excludeParams_(self.excludeParams)
.ignoreParams_(self.ignoreParams)
};
~makeGui = { |self|
if(self.window.notNil) { self.window.close };
self.window = Window.new(self.synthDef.name, self.bounds).front;
self.window.layout = VLayout(self.nodeProxyGui);
self.window;
};
};
)
test SynthDefs:
(
SynthDef(\source, {
var tFreq, trig, env, freq, fmod, sig;
tFreq = \tFreq.kr(4, spec: ControlSpec(1, 12));
trig = Impulse.ar(tFreq);
env = Env.perc(0.1, 0.2).ar(0, trig);
freq = \freq.kr(440, spec: ControlSpec(20, 1000));
sig = SinOsc.ar(freq + (freq * \fmod.ar(0).poll * \fmIndex.kr(2, spec: ControlSpec(0, 5))));
sig = sig * env;
sig = Pan2.ar(sig, 0);
sig = sig * \amp.kr(-35, spec: ControlSpec(-35, -15, \lin, 1)).dbamp;
Out.ar(\out.kr(0), sig);
}).add;
SynthDef(\modulation, {
var flux, rate, phase;
var phaseDivA, phaseDivB, phaseDivC;
var modA, modB, modC;
flux = LFDNoise3.ar(\fluxMF.kr(1, spec: ControlSpec(0.125, 50)));
flux = 2 ** (flux * \fluxMD.kr(0, spec: ControlSpec(0, 3)));
rate = \rate.kr(0.5, spec: ControlSpec(0.1, 5));
rate = rate * flux;
phase = Phasor.ar(DC.ar(0), rate * SampleDur.ir);
phaseDivA = (phase * \modDivA.kr(1, spec: ControlSpec(1, 16))).wrap(0, 1);
phaseDivB = (phase * \modDivB.kr(3, spec: ControlSpec(1, 16))).wrap(0, 1);
phaseDivC = (phase * \modDivC.kr(5, spec: ControlSpec(1, 16))).wrap(0, 1);
modA = sin(phaseDivA * 2pi);
modB = sin(phaseDivB * 2pi);
modC = sin(phaseDivC * 2pi);
Out.ar(\modOutA.kr(0), modA);
Out.ar(\modOutB.kr(0), modB);
Out.ar(\modOutC.kr(0), modC);
}).add;
)
evaluate the ProtoDefs and test the setup:
// setup Busses
~modMatrix = Prototype(\modMatrix) { ~numOfMappings = 4; ~numOfModulators = 3 };
// set param value
~modMatrix.matrixProxy.set(\mod0_0, 1);
~modMatrix.matrixProxy.set(\mod0_1, 1);
~modMatrix.matrixProxy.set(\mod0_2, 1);
// map MIDI
~modMatrix.mapMidi;
// unmap MIDI
~modMatrix.unmapMidi;
/////////////////////////////////////////////////////
// make nodeProxy and map source to outputbusses of modMatrix
(
~source = Prototype(\makeNodeProxy) {
~synthDefName = \source;
~initArgs = [
\fmod, NodeProxy.for(topEnvironment[\modMatrix].outputBusses[0])
//\fmod, topEnvironment[\modMatrix].matrixProxy.bus.subBus(0, 0)
];
~bounds = Rect(10, 430, 440, 300);
~excludeParams = [];
~ignoreParams = [\amp];
};
)
/////////////////////////////////////////////////////
// make nodeProxy and map modulators to inputBusses of modMatrix
(
~modulation = Prototype(\makeNodeProxy) {
~synthDefName = \modulation;
~initArgs = [
\modOutA, topEnvironment[\modMatrix].inputBusses[0],
\modOutB, topEnvironment[\modMatrix].inputBusses[1],
\modOutC, topEnvironment[\modMatrix].inputBusses[2]
];
~bounds = Rect(10, 70, 440, 320);
~excludeParams = [\modOutA, \modOutB, \modOutC];
~ignoreParams = [];
};
)