Here’s what I did to create a re-usable interface for an MLP regressor to use with any synth.
Basically you can take this base class and extend it to use with your synth.
A couple of notes:
- The MLPGUI Class expects a
makeSynth
method to exist in your subclass. Maybe some OOP nerds can help me make this look nicer and error-proof. - You will need to override the n parameter of the base class in order to declare the number of parameters to be controlled by the MLP Regressor (see next post for an example) otherwise you will have an error.
- Your synth should accept an output buffer from the MLP regressor (here called y_buf) as an input argument. Note that there is currently an open PR to allow
FluidKrToBuf.kr
to be initialized before the buffer is created (for example, if you are compiling a SynthDef on the server). In my example, I am setting the buffer size ofFluidKrToBuf.kr
to the n parameter of the class which is equal to the total number of parameters.
Buf to kr enhancements by tedmoore · Pull Request #42 · flucoma/flucoma-sc · GitHub
So far it works pretty nicely for me. Here is the main class:
MLPGui {
var s,n,name,
<>w,<>synth,dsin,dsout,nn,prediction_buf,x_buf,y_buf;
*new { |s,n|
^super.newCopyArgs(s ? Server.default,n).init
}
init {
fork {
dsin = FluidDataSet(s); s.sync;
dsout = FluidDataSet(s); s.sync;
nn = FluidMLPRegressor(
server: s,
hidden: [7],
activation: FluidMLPRegressor.sigmoid,
outputActivation: FluidMLPRegressor.sigmoid,
learnRate: 0.1,
batchSize: 1,
validation: 0); s.sync;
prediction_buf = Buffer.alloc(s,n); s.sync;
x_buf = Buffer.alloc(s, 2); s.sync;
y_buf = Buffer.alloc(s, n); s.sync;
};
Task { this.makeGUI(n) }.play(AppClock);
}
makeGUI {
var counter,predicting,multisliderview;
counter = 0;
predicting = false;
w = Window(name, Rect(0,0,1000,400));
w.userCanClose = false;
multisliderview = MultiSliderView(w,Rect(0,0,400,400))
.size_(n)
.elasticMode_(true)
.action_({ |msv|
msv.value.postln;
y_buf.setn(0, msv.value);
});
Slider2D(w,Rect(400,0,400,400))
.action_({ |xy|
[xy.x, xy.y].postln;
x_buf.setn(0, [xy.x, xy.y]);
if(predicting, {
nn.predictPoint(x_buf, y_buf, action: {
y_buf.getn(0,n, action: { |prediction_values|
{multisliderview.value_(prediction_values)}.defer;
});
});
});
});
Button(w,Rect(800,0,200,20))
.states_([["Make Synth"],["Stop Synth"]])
.action_({ |b|
b.value.postln;
if (b.value == 0, { if (synth.isPlaying == false, { synth.run(false); "stop my synth".postln }) });
if (b.value == 1, { if (synth.isPlaying == false, { this.makeSynth; "make my synth".postln }) });
});
Button(w,Rect(800,20,200,20))
.states_([["Add Point"]])
.action_({ |b|
var id = "example-%".format(counter);
counter = counter+1;
dsin.addPoint(id,x_buf);
dsout.addPoint(id,y_buf);
});
Button(w,Rect(800,40,200,20))
.states_([["Train"]])
.action_({ |b|
nn.fit(dsin,dsout, action:{|error|
"loss: %".format(error).postln;
});
});
Button(w,Rect(800,60,200,20))
.states_([["Not Predicting",Color.yellow,Color.black], ["Predicting",Color.green,Color.black]])
.action_({ |b|
predicting = b.value.asBoolean;
});
Button(w,Rect(800,80,200,20))
.states_([["Print Input Dataset"]])
.action_({ dsin.print });
Button(w,Rect(800,100,200,20))
.states_([["Print Output Dataset"]])
.action_({ dsout.print });
Button(w,Rect(800,120,200,20))
.states_([["Randomize Parameters"]])
.action_({ multisliderview.valueAction_(Array.fill(n, { 1.0.rand }));
});
} // end makeGUI
}