@ -1,7 +1,30 @@
s.reboot
// using nmf in 'real-time' as a classifier
// how it works: a circular buffer is recording and attacks trigger the process
// if in learning mode, it does a one component nmf which makes an approximation of the base. 3 of those will be copied in 3 different positions of our final 3-component base
// in in guessing mode, it does a thres component nmf from the trained bases and yields the 3 activation peaks, on which it thresholds resynth
//how to use:
// 1. start the server
// 2. select between parenthesis below and execute. You should get a window with 3 pads (bd sn hh) and various menus
// 3. train the 3 classes:
// 3.1 select the learn option
// 3.2 select which class you want to train
// 3.3 play the sound you want to associate with that class a few times (the left audio channel is the source)
// 3.4 click the transfer button
// 3.5 repeat (3.2-3.4) for the other 2 classes.
// 3.x you can observe the 3 bases here:
f.plot(numChannels:3)
// 4. classify
// 4.1 select the classify option
// 4.2 press a pad and look at the activation
// 4.3 tweak the thresholds and enjoy the resynthesis. (the right audio channel is the detected class where classA is a bd sound)
// 4.x you can observe the 3 activations here:
h.plot(numChannels:3)
//a circular buffer is doing a fake real time and attacks trigger an analysis
/// code to execute first
(
b = Buffer.alloc(s,s.sampleRate * 2);
g = Bus.audio(s,1);
@ -11,9 +34,11 @@ e = Buffer.alloc(s, 65);
f = Buffer.alloc(s, 65, 3);
h = Buffer.alloc(s, 65, 3);
j = [0.0,0.0,0.0];
k = [0.0,0.0,0.0 ];
k = [0.5,0.5,0.5 ];
SynthDef(\JITcircular,{arg bufnum = 0, input = 0, env = 0;
// the circular buffer with triggered actions sending the location of the head at the attack
Routine {
SynthDef(\JITcircular,{arg bufnum = 0, input = 0, env = 0;
var head, head2, duration, audioin, halfdur, trig;
duration = BufFrames.kr(bufnum) / 2;
halfdur = duration / 2;
@ -30,13 +55,12 @@ SynthDef(\JITcircular,{arg bufnum = 0, input = 0, env = 0;
SendReply.ar(trig, '/attack',head);
Out.ar(0,audioin);
}).add;
// drum sounds taken from original code by snappizz
// https://sccode.org/1-523
// produced further and randomised by PA
}).add;
SynthDef(\fluidbd, {
// drum sounds taken from original code by snappizz
// https://sccode.org/1-523
// produced further and humanised by PA
SynthDef(\fluidbd, {
|out = 0|
var body, bodyFreq, bodyAmp;
var pop, popFreq, popAmp;
@ -60,9 +84,9 @@ SynthDef(\fluidbd, {
snd = snd.tanh;
Out.ar(out, snd);
}).add;
}).add;
SynthDef(\fluidsn, {
SynthDef(\fluidsn, {
|out = 0|
var pop, popAmp, popFreq;
var noise, noiseAmp;
@ -82,9 +106,9 @@ SynthDef(\fluidsn, {
snd = (pop + click + noise) * 1.4;
Out.ar(out, snd);
}).add;
}).add;
SynthDef(\fluidhh, {
SynthDef(\fluidhh, {
|out = 0|
var click, clickAmp;
var noise, noiseAmp, noiseFreq;
@ -95,20 +119,22 @@ SynthDef(\fluidhh, {
noise = Mix(BPF.ar(ClipNoise.ar, [noiseFreq, noiseFreq+141], [0.12, 0.31], [2.0, 1.2])) * noiseAmp;
Out.ar(out, noise);
}).add;
)
}).add;
(
// instantiate the JIT-circular-buffer
x = Synth(\JITcircular,[\bufnum, b.bufnum, \input, g.index]);
e.fill(0,65,0.1);
// makes sure all the synthdefs are on the server
s.sync;
// instantiate the listener to cue the processing from the language side
// instantiate the JIT-circular-buffer
x = Synth(\JITcircular,[\bufnum, b.bufnum, \input, g.index]);
e.fill(0,65,0.1);
r = OSCFunc({ arg msg;
// instantiate the listener to cue the processing from the language side
r = OSCFunc({ arg msg;
if (c == 0, {
// if in training mode, makes a single component nmf
FluidBufNMF.process(s, b, msg[3], 128, bases:e, basesMode: 1, windowSize: 128);
}, {
// if in classifying mode, makes a 3 component nmf from the pretrained bases and compares the activations with the set thresholds
FluidBufNMF.process(s, b, msg[3], 128, components:3, bases:f, basesMode: 2, activations:h, windowSize: 128, action:{
h.getn(3,3,{|x|
j = x;
@ -119,34 +145,38 @@ r = OSCFunc({ arg msg;
};
);
});
}, '/attack', s.addr);
)
}, '/attack', s.addr);
// stop it all
(
w = Window("Control", Rect(100,100,590,100)).front;
Button(w, Rect(10,10,80, 80)).states_([["bd",Color.black,Color.white]]).mouseDownAction_({Synth(\fluidbd, [\out, g.index], x, \addBefore)});
Button(w, Rect(100,10,80, 80)).states_([["sn",Color.black,Color.white]]).mouseDownAction_({Synth(\fluidsn, [\out, g.index], x, \addBefore)});
Button(w, Rect(190,10,80, 80)).states_([["hh",Color.black,Color.white]]).mouseDownAction_({Synth(\fluidhh, [\out, g.index], x,\addBefore)});
StaticText(w, Rect(280,7,75,25)).string_("Select").align_(\center);
PopUpMenu(w, Rect(280,32,75,25)).items_(["learn","classify"]).action_({|value| c = value.value; if (c == 0, {e.fill(0,65,0.1)});});
PopUpMenu(w, Rect(280,65,75,25)).items_(["classA","classB","classC"]).action_({|value| d = value.value; e.fill(0,65,0.1);});
Button(w, Rect(365,65,65,25)).states_([["transfer",Color.black,Color.white]]).mouseDownAction_({if (c == 0, {FluidBufCompose.process(s, e, numChans:1, destination:f, destStartChan:d);});});
StaticText(w, Rect(440,7,75,25)).string_("Activations");
l = Array.fill(3, {arg i;
// make sure all the synths are instantiated
s.sync;
// GUI for control
{
w = Window("Control", Rect(100,100,590,100)).front;
Button(w, Rect(10,10,80, 80)).states_([["bd",Color.black,Color.white]]).mouseDownAction_({Synth(\fluidbd, [\out, g.index], x, \addBefore)});
Button(w, Rect(100,10,80, 80)).states_([["sn",Color.black,Color.white]]).mouseDownAction_({Synth(\fluidsn, [\out, g.index], x, \addBefore)});
Button(w, Rect(190,10,80, 80)).states_([["hh",Color.black,Color.white]]).mouseDownAction_({Synth(\fluidhh, [\out, g.index], x,\addBefore)});
StaticText(w, Rect(280,7,75,25)).string_("Select").align_(\center);
PopUpMenu(w, Rect(280,32,75,25)).items_(["learn","classify"]).action_({|value| c = value.value; if (c == 0, {e.fill(0,65,0.1)});});
PopUpMenu(w, Rect(280,65,75,25)).items_(["classA","classB","classC"]).action_({|value| d = value.value; e.fill(0,65,0.1);});
Button(w, Rect(365,65,65,25)).states_([["transfer",Color.black,Color.white]]).mouseDownAction_({if (c == 0, {FluidBufCompose.process(s, e, numChans:1, destination:f, destStartChan:d);});});
StaticText(w, Rect(440,7,75,25)).string_("Activations");
l = Array.fill(3, {arg i;
StaticText(w, Rect(440,((i+1) * 20 )+ 7,75,25));
});
StaticText(w, Rect(520,7,55,25)).string_("Thresh").align_(\center);
3.do {arg i;
TextField(w, Rect(520,((i+1) * 20 )+ 7,55,25)).action_({|x| k[i] = x.value.asFloat;});
}) ;
});
StaticText(w, Rect(520,7,55,25)).string_("Thresh").align_(\center);
3.do {arg i;
TextField(w, Rect(520,((i+1) * 20 )+ 7,55,25)).string_("0.5" ).action_({|x| k[i] = x.value.asFloat;});
};
w.onClose_({b.free;g.free;r.clear;x.free; y.free;q.stop;});
)
w.onClose_({b.free;g.free;r.clear;x.free; y.free;q.stop;});
}.defer;
(
q = Routine {
s.sync;
// updates the activations
q = Routine {
{
{
l[0].string_("A: " ++ j[0].round(0.001));
@ -155,10 +185,6 @@ q = Routine {
}.defer;
0.1.wait;
}.loop;
}.play;
}.play;
)
e.getn(0,65,{|x|x.postln;})
f.getn(0,65 * 3,{|x|x.postln;})
f.plot(numChannels:3)
h.plot(numChannels:3)