From 6a9e91cb5ae99eee8313087dcb94780daefcb262 Mon Sep 17 00:00:00 2001 From: Owen Green Date: Tue, 14 Jul 2020 01:26:03 +0100 Subject: [PATCH] Add RT query proto-examples to help files --- .../HelpSource/Classes/FluidKDTree.schelp | 52 ++++++++++++++---- .../HelpSource/Classes/FluidKMeans.schelp | 44 +++++++-------- .../Classes/FluidKNNClassifier.schelp | 53 +++++++++++++++++++ .../Classes/FluidKNNRegressor.schelp | 43 +++++++++++++-- .../HelpSource/Classes/FluidNormalize.schelp | 37 +++++++++++++ .../HelpSource/Classes/FluidPCA.schelp | 34 +++++++++++- .../Classes/FluidStandardize.schelp | 40 +++++++++++++- 7 files changed, 264 insertions(+), 39 deletions(-) diff --git a/release-packaging/HelpSource/Classes/FluidKDTree.schelp b/release-packaging/HelpSource/Classes/FluidKDTree.schelp index 679934e..e527a1e 100644 --- a/release-packaging/HelpSource/Classes/FluidKDTree.schelp +++ b/release-packaging/HelpSource/Classes/FluidKDTree.schelp @@ -72,7 +72,7 @@ fork{ ) // Make a new tree, and fit it to the dataset -~tree = FluidKDTree(s); +~tree = FluidKDTree(s,numNeighbours:5,lookupDataSet:~ds); //Fit it to the dataset ~tree.fit(~ds); @@ -83,23 +83,57 @@ fork{ //Return labels of k nearest points to a new point +( ~p = [ 1.0.linrand,1.0.linrand ]; +~tree.numNeighbours = 5; ~tmpbuf = Buffer.loadCollection(s, ~p, 1, { - ~tree.kNearest(~tmpbuf,5, { |a|~nearest = a;}) + ~tree.kNearest(~tmpbuf,{ |a|a.postln;~nearest = a;}) }); - +) // Labels of nearest points ~nearest.postln; // Values +( fork{ -~nearest.do{|n| - ~ds.getPoint(n, ~tmpbuf, {~tmpbuf.getn(0, 2, {|x|x.postln})}); - s.sync; -} + ~nearest.do{|n| + ~ds.getPoint(n, ~tmpbuf, {~tmpbuf.getn(0, 2, {|x|x.postln})}); + s.sync; + } } - +) //Distances of the nearest points -~tree.kNearestDist(~tmpbuf, 5, { |a| a.postln }); +~tree.kNearestDist(~tmpbuf, { |a| a.postln }); +:: + +subsection:: Server Side Queries + +code:: + +( +~inputPoint = Buffer.alloc(s,2); +~predictPoint = Buffer.alloc(s,10); +~pitchingBus = Bus.control; +~catchingBus = Bus.control; +) + +( + +~tree.inBus_(~pitchingBus).outBus_(~catchingBus).inBuffer_(~inputPoint).outBuffer_(~predictPoint); +{ + var trig = Impulse.kr(ControlRate.ir/2); + var point = 2.collect{TRand.kr(0,1,trig)}; + point.collect{|p,i| BufWr.kr([p],~inputPoint,i)}; + Poll.kr(trig,point); + Out.kr(~pitchingBus.index,[trig]); + Poll.kr(In.kr(~catchingBus.index),BufRd.kr(1,~predictPoint,Array.iota(5))); + Silent.ar; +}.play(~tree.synth,addAction:\addBefore); + +) + + + :: + diff --git a/release-packaging/HelpSource/Classes/FluidKMeans.schelp b/release-packaging/HelpSource/Classes/FluidKMeans.schelp index d43d54c..d04336d 100644 --- a/release-packaging/HelpSource/Classes/FluidKMeans.schelp +++ b/release-packaging/HelpSource/Classes/FluidKMeans.schelp @@ -95,7 +95,7 @@ fork{ // Fit into 4 clusters ( -~kmeans.fitPredict(~dataset,~clusters, 4, action: {|c| +~kmeans.fitPredict(~dataset,~clusters,action: {|c| "Fitted.\n # Points in each cluster:".postln; c.do{|x,i| ("Cluster" + i + "->" + x.asInteger + "points").postln; @@ -145,23 +145,23 @@ w.refresh; w.front; ) -//Querying on the server using busses and buffers: -//This is the equivalent of predictPoint, but wholly on the server -//FluidKMeans is accessed via its own synth, so we need to use -//a bus to communicate with it. The inBus receives a trigger to query, using data -//from inBuffer; a trigger is then send to outBus with the prediction in outBuffer +:: + +subsection:: Server Side Queries + +This is the equivalent of predictPoint, but wholly on the server +FluidKMeans is accessed via its own synth, so we need to use +a bus to communicate with it. The inBus receives a trigger to query, using data +from inBuffer; a trigger is then send to outBus with the prediction in outBuffer +code:: ( -~ib = Bus.audio(s); // input bus must be audio (for now) +~ib = Bus.control(s); // input bus must be audio (for now) ~ob = Bus.control(s); //output bus can be kr ~tempPoint = Buffer.alloc(s,1,2); ~inpPoint = Buffer.alloc(s,2); ~outPoint = Buffer.alloc(s,1); ) -~tempPoint.getn(0,2,{|x|x.post}) - -~inpPoint.getn(0,2,{|x|x.post}) - //We make two Synths. One, before FluidKMeans, generates a random point and sends //a trigger to query. The second, after FluidKMeans, gives us the predicted cluster //triggering upadtes from the outBus ( @@ -169,13 +169,12 @@ w.front; ~kmeans.inBus_(~ib).outBus_(~ob).inBuffer_(~inpPoint).outBuffer_(~outPoint); //pitching { - var trig = Impulse.kr(1); + var trig = Impulse.kr(100); var point = WhiteNoise.kr(1.dup); var copied; - BufWr.kr(point,~tempPoint,0); Poll.kr(trig, point, [\pointX,\pointY]); - copied = Done.kr(FluidBufFlatten.kr(~tempPoint, ~inpPoint, trig: trig)); - Out.ar(~ib.index,K2A.ar(copied > Delay1.kr(copied))); + point.collect{ |p,i| BufWr.kr([p],~inpPoint,i)}; + Out.kr(~ib.index,[trig]); }.play(~kmeans.synth,addAction:\addBefore); //catching { @@ -183,20 +182,17 @@ w.front; }.play(~kmeans.synth,addAction:\addAfter); ) -// to sonify the output, here are random values alternating quadrant. +// to sonify the output, here are random values alternating quadrant, generated more quickly as the cursor moves rightwards ( //Set properties on FluidKMeans: ~kmeans.inBus_(~ib).outBus_(~ob).inBuffer_(~inpPoint).outBuffer_(~outPoint); //pitching { - var count, trig, point, copied; - trig = Impulse.kr(MouseX.kr(0,1).exprange(0.5,1000).poll); - count = Stepper.kr(trig,0,0,3); - point = Latch.kr(WhiteNoise.ar([0.1,0.1],[count.div(2)-0.5,count.mod(2)-0.5]),trig); - BufWr.kr(point,~tempPoint,0); - copied = Done.kr(FluidBufFlatten.kr(~tempPoint, ~inpPoint, trig: trig)); - Out.ar(~ib.index,K2A.ar(copied > Delay1.kr(copied))); - trig*0.1; + var trig = Impulse.kr(MouseX.kr(0,1).exprange(0.5,ControlRate.ir / 2)); + var point = 2.collect{ TIRand.kr(0,3,trig).linlin(0,3,-1,1) }; + point.collect{|p,i| BufWr.kr([p],~inpPoint,i)}; + Out.kr(~ib.index,[trig]); + T2A.ar(trig)*0.1; }.play(~kmeans.synth,addAction:\addBefore); //catching { diff --git a/release-packaging/HelpSource/Classes/FluidKNNClassifier.schelp b/release-packaging/HelpSource/Classes/FluidKNNClassifier.schelp index edc5a02..6ea3228 100644 --- a/release-packaging/HelpSource/Classes/FluidKNNClassifier.schelp +++ b/release-packaging/HelpSource/Classes/FluidKNNClassifier.schelp @@ -145,5 +145,58 @@ w.drawFunc = { w.refresh; w.front; ) +:: +subsection::Server Side Queries +This is the equivalent of predictPoint, but wholly on the server +FluidKMeans is accessed via its own synth, so we need to use +a bus to communicate with it. The inBus receives a trigger to query, using data +from inBuffer; a trigger is then send to outBus with the prediction in outBuffer +code:: +( +~ib = Bus.control(s); // input bus must be audio (for now) +~ob = Bus.control(s); //output bus can be kr +~inpPoint = Buffer.alloc(s,2); +~outPoint = Buffer.alloc(s,1); +) + +//We make two Synths. One, before FluidKMeans, generates a random point and sends +//a trigger to query. The second, after FluidKMeans, gives us the predicted cluster //triggering upadtes from the outBus +( +//Set properties on FluidKMeans: +~classifier.inBus_(~ib).outBus_(~ob).inBuffer_(~inpPoint).outBuffer_(~outPoint); +//pitching +{ + var trig = Impulse.kr(100); + var point = WhiteNoise.kr(1.dup); + Poll.kr(trig, point, [\pointX,\pointY]); + point.collect{ |p,i| BufWr.kr([p],~inpPoint,i)}; + Out.kr(~ib.index,[trig]); +}.play(~classifier.synth,addAction:\addBefore); +//catching +{ + Poll.kr(In.kr(~ob),Latch.kr(BufRd.kr(1,~outPoint,0,interpolation:0),In.kr(~ob)),\cluster); +}.play(~classifier.synth,addAction:\addAfter); +) + +// to sonify the output, here are random values alternating quadrant. +( +//Set properties on FluidKMeans: +~classifier.inBus_(~ib).outBus_(~ob).inBuffer_(~inpPoint).outBuffer_(~outPoint); +//pitching +{ + var trig = Impulse.kr(MouseX.kr(0,1).exprange(0.5,ControlRate.ir /2).poll); + var point = 2.collect{TIRand.kr(0,3,trig).linlin(0,3,-1,1)}; + point.collect{|p,i| BufWr.kr([p],~inpPoint,i)}; + Out.kr(~ib.index,[trig]); + T2A.ar(trig)*0.1; +}.play(~classifier.synth,addAction:\addBefore); +//catching +{ + SinOsc.ar((Latch.kr(BufRd.kr(1,~outPoint,0,interpolation:0),In.kr(~ob)) + 69).midicps,mul: 0.1); +}.play(~classifier.synth,addAction:\addAfter); +) :: + + + diff --git a/release-packaging/HelpSource/Classes/FluidKNNRegressor.schelp b/release-packaging/HelpSource/Classes/FluidKNNRegressor.schelp index b51f0b4..41558d9 100644 --- a/release-packaging/HelpSource/Classes/FluidKNNRegressor.schelp +++ b/release-packaging/HelpSource/Classes/FluidKNNRegressor.schelp @@ -89,15 +89,13 @@ d = Dictionary.with( ~testdata.collect{|x, i| [i.asString, [x]]}.flatten)]) ); - +~targetdata.plot ~source.print; ~target.print; ~test.print; ) - - // Now make a regressor and fit it to the source and target, and predict against test //grab the output data whilst we're at it, so we can inspect ( @@ -110,8 +108,45 @@ d = Dictionary.with( }); ) - //We should see a single cycle of a chirp ~outputdata.plot; +:: + +subsection:: Server Side Queries + +code:: +//Setup +( +~inputPoint = Buffer.alloc(s,1); +~predictPoint = Buffer.alloc(s,2); +~avgBuf = Buffer.alloc(s,10,2); +~pitchingBus = Bus.control; +~catchingBus = Bus.control; +) + +( +~regressor.inBus_(~pitchingBus).outBus_(~catchingBus).inBuffer_(~inputPoint).outBuffer_(~predictPoint); + +~inputSynth = { + var input = Saw.kr(2).linlin(-1,1,0,1); + var trig = Impulse.kr(ControlRate.ir/10); + BufWr.kr(input,~inputPoint,0); + Out.kr(~pitchingBus.index,[trig]); +}; + +~inputSynth.play(~regressor.synth,addAction:\addBefore); + +~outputSynth = { + Poll.kr(In.kr(~catchingBus.index),BufRd.kr(1,~predictPoint,0),"mapped value") +}; +~outputSynth.play(~regressor.synth,addAction:\addAfter); +~outputSynth.scope +) + + :: + + + + diff --git a/release-packaging/HelpSource/Classes/FluidNormalize.schelp b/release-packaging/HelpSource/Classes/FluidNormalize.schelp index a28d6c2..6bc9f09 100644 --- a/release-packaging/HelpSource/Classes/FluidNormalize.schelp +++ b/release-packaging/HelpSource/Classes/FluidNormalize.schelp @@ -64,6 +64,7 @@ EXAMPLES:: code:: s.boot; //Preliminaries: we want some audio, a couple of FluidDataSets, some Buffers and a FluidNormalize +// FluidNormalize.dumpAllMethods ( ~audiofile = File.realpath(FluidBufPitch.class.filenameSymbol).dirname +/+ "../AudioFiles/Tremblay-ASWINE-ScratchySynth-M.wav"; ~raw = FluidDataSet(s,\norm_help_raw); @@ -123,4 +124,40 @@ FluidBufPitch.process(s,~audio, features: ~pitch_feature); ~plot2 = ~normedarray.flatten(1).unlace.plot("Normalized",Rect(410,0,400,400)).plotMode=\bars; ) +//Server side queries +//Setup +( +~tempPoint = Buffer.alloc(s,2); +~predictPoint = Buffer.alloc(s,2); +~avgBuf = Buffer.alloc(s,10,2); +~pitchingBus = Bus.control; +~catchingBus = Bus.control; +) +( +~normalizer.inBus_(~pitchingBus).outBus_(~catchingBus).inBuffer_(~tempPoint).outBuffer_(~predictPoint); +//Pitching (no pun intended): read frames out of buffer and pass to standardize +{ + var audio = BufRd.ar(1,~audio,LFSaw.ar(BufDur.ir(~audio).reciprocal).range(0, BufFrames.ir(~audio))); + var counter = Stepper.ar(Impulse.ar(ControlRate.ir),max:9); + var trig = HPZ1.ar(counter) < 0; + //average 10 frames: one could use the MovingAverage extension here + var avg; + BufWr.kr(FluidPitch.kr(audio),~avgBuf,phase:counter); + avg = Mix.new(BufRd.kr(2, ~avgBuf, phase:10.collect{|x|x})) * 0.1; + //assemble data point + BufWr.kr(avg[0],~tempPoint,0); + BufWr.kr(avg[1],~tempPoint,1); + Poll.kr(T2K.kr(trig),BufRd.kr(1,~tempPoint,[0,1]),["pitch (raw)", "confidence (raw)"]); + Out.kr(~pitchingBus.index,[T2K.kr(trig)]); +}.play(~normalizer.synth,addAction:\addBefore); + +{ + Poll.kr(In.kr(~catchingBus.index),BufRd.kr(1,~predictPoint,[0,1]),["pitch (normalized)", "confidence (normalized)"]) +}.play(~normalizer.synth,addAction:\addAfter); + +) + + + + :: diff --git a/release-packaging/HelpSource/Classes/FluidPCA.schelp b/release-packaging/HelpSource/Classes/FluidPCA.schelp index b6f0a6f..bbe2246 100644 --- a/release-packaging/HelpSource/Classes/FluidPCA.schelp +++ b/release-packaging/HelpSource/Classes/FluidPCA.schelp @@ -121,7 +121,6 @@ FluidBufMFCC.process(s,~audio, features: ~mfcc_feature); ) - //Visualise the 2D projection of our original 12D data ( d = ~reducedarray.flatten(1).unlace.deepCollect(1, { |x| x.normalize}); @@ -141,3 +140,36 @@ w.refresh; w.front; ) :: + +subsection:: Server Side Queries + +Let's map our learned PCA dimensions to the controls of a processor + +code:: + +( +~inputPoint = Buffer.alloc(s,12); +~predictPoint = Buffer.alloc(s,2); +~pitchingBus = Bus.control; +~catchingBus = Bus.control; +) + +( +~pca.inBus_(~pitchingBus).outBus_(~catchingBus).inBuffer_(~inputPoint).outBuffer_(~predictPoint); + +{ + var mapped; + var audio = BufRd.ar(1,~audio,LFSaw.ar(BufDur.ir(~audio).reciprocal).range(0, BufFrames.ir(~audio))); + var mfcc = FluidMFCC.kr(audio)[1..12]; + var smoothed = LagUD.kr(mfcc,1*ControlDur.ir,500*ControlDur.ir); + var trig = Impulse.kr(ControlRate.ir / 2); + smoothed.collect{|coeff,i| BufWr.kr([coeff],~inputPoint,i)}; + Out.kr(~pitchingBus,[trig]); + mapped = Latch.kr(BufRd.kr(2,~predictPoint).linlin(-3,3,0,3),In.kr(~catchingBus)); + CombC.ar(audio,3,mapped[0],mapped[1]*3) +}.play(~pca.synth,addAction:\addBefore); + +) + + +:: diff --git a/release-packaging/HelpSource/Classes/FluidStandardize.schelp b/release-packaging/HelpSource/Classes/FluidStandardize.schelp index 7e23d0c..23a246a 100644 --- a/release-packaging/HelpSource/Classes/FluidStandardize.schelp +++ b/release-packaging/HelpSource/Classes/FluidStandardize.schelp @@ -50,7 +50,7 @@ ARGUMENT:: destBuffer A link::Classes/Buffer:: to contain the standardize value ARGUMENT:: action A function to run when processing is complete - +[].unlace EXAMPLES:: code:: @@ -113,6 +113,44 @@ FluidBufPitch.process(s,~audio, features: ~pitch_feature); ~rawarray.flatten(1).unlace.plot("Unstandardized",Rect(0,0,400,400),minval:0,maxval:[5000,1]).plotMode=\bars; ~plot2 = ~stdarray.flatten(1).unlace.plot("Standardized",Rect(410,0,400,400)).plotMode=\bars; ) +:: + +subsection::Server Side Querying + +Because FluidStandardize runs in its own link::Classes/Synth:: on the server, communication is done via control-rate link::Classes/Bus:: objects for triggering and link::Classes/Buffer:: objects for passing and retreiving data. + +code:: +//Setup +( +~tempPoint = Buffer.alloc(s,2); +~predictPoint = Buffer.alloc(s,2); +~avgBuf = Buffer.alloc(s,10,2); +~pitchingBus = Bus.control; +~catchingBus = Bus.control; +) +( +~standardizer.inBus_(~pitchingBus).outBus_(~catchingBus).inBuffer_(~tempPoint).outBuffer_(~predictPoint); +//Pitching (no pun intended): read frames out of buffer and pass to standardize +{ + var audio = BufRd.ar(1,~audio,LFSaw.ar(BufDur.ir(~audio).reciprocal).range(0, BufFrames.ir(~audio))); + var counter = Stepper.ar(Impulse.ar(ControlRate.ir),max:9); + var trig = HPZ1.ar(counter) < 0; + //average 10 frames: one could use the MovingAverage extension here + var avg; + BufWr.kr(FluidPitch.kr(audio),~avgBuf,phase:counter); + avg = Mix.new(BufRd.kr(2, ~avgBuf, phase:10.collect{|x|x})) * 0.1; + //assemble data point + BufWr.kr(avg[0],~tempPoint,0); + BufWr.kr(avg[1],~tempPoint,1); + Poll.kr(T2K.kr(trig),BufRd.kr(1,~tempPoint,[0,1]),["pitch (raw)", "confidence (raw)"]); + Out.kr(~pitchingBus.index,[T2K.kr(trig)]); +}.play(~standardizer.synth,addAction:\addBefore); + +//catching +{ + Poll.kr(In.kr(~catchingBus.index),BufRd.kr(1,~predictPoint,[0,1]),["pitch (standardized)", "confidence (standardized)"]) +}.play(~standardizer.synth,addAction:\addAfter); +) ::