diff --git a/release-packaging/Classes/FluidPlotter.sc b/release-packaging/Classes/FluidPlotter.sc new file mode 100644 index 0000000..601b001 --- /dev/null +++ b/release-packaging/Classes/FluidPlotter.sc @@ -0,0 +1,253 @@ +FluidPlotterPoint { + var id, color, <>size = 1; + + *new { + arg id, x, y, color, size = 1; + ^super.new.init(id,x,y,color,size); + } + + init { + arg id_, x_, y_, color_, size_ = 1; + id = id_; + x = x_; + y = y_; + color = color_ ? Color.black; + size = size_; + } +} + +FluidPlotter { + var (catColors.size-1),{ + "FluidPlotter:setCategories_ FluidPlotter doesn't have that many category colors. You can use the method 'setColor_' to set colors for individual points.".warn + }); + + color = catColors[category_int]; + fp_pt.color_(color); + }); + this.refresh; + },{ + "FluidPlotter::setCategories_ FluidPlotter cannot receive setCategories. It has no data. First set a dictionary.".warn; + }); + } + + pointSize_ { + arg identifier, size; + if(dict_internal.at(identifier).notNil,{ + dict_internal.at(identifier).size_(size); + this.refresh; + },{ + "FluidPlotter::pointSize_ identifier not found".warn; + }); + } + + // TODO: addPoint_ that checks if the key already exists and throws an error if it does + addPoint_ { + arg identifier, x, y, color, size = 1; + if(dict_internal.at(identifier).notNil,{ + "FluidPlotter::addPoint_ There already exists a point with identifier %. Point not added. Use setPoint_ to overwrite existing points.".format(identifier).warn; + },{ + this.setPoint_(identifier,x,y,size,color); + }); + } + + setPoint_ { + arg identifier, x, y, color, size = 1; + + dict_internal.put(identifier,FluidPlotterPoint(identifier,x,y,color ? Color.black,size)); + + this.refresh; + } + + pointColor_ { + arg identifier, color; + if(dict_internal.at(identifier).notNil,{ + dict_internal.at(identifier).color_(color); + this.refresh; + },{ + "FluidPlotter::setColor_ identifier not found".warn; + }); + } + + shape_ { + arg sh; + shape = sh; + this.refresh; + } + + background_ { + arg bg; + userView.background_(bg); + } + + refresh { + {userView.refresh}.defer; + } + + pointSizeScale_ { + arg ps; + pointSizeScale = ps; + this.refresh; + } + + dict_ { + arg d; + + if(d.isNil,{^this.dictNotProperlyFormatted}); + if(d.size != 2,{^this.dictNotProperlyFormatted}); + if(d.at("data").isNil,{^this.dictNotProperlyFormatted}); + if(d.at("cols").isNil,{^this.dictNotProperlyFormatted}); + if(d.at("cols") != 2,{^this.dictNotProperlyFormatted}); + + dict = d; + dict_internal = Dictionary.new; + dict.at("data").keysValuesDo({ + arg k, v; + dict_internal.put(k,FluidPlotterPoint(k,v[0],v[1],Color.black,1)); + }); + if(userView.notNil,{ + this.refresh; + }); + } + + xmin_ { + arg val; + xmin = val; + this.refresh; + } + + xmax_ { + arg val; + xmax = val; + this.refresh; + } + + ymin_ { + arg val; + ymin = val; + this.refresh; + } + + ymax_ { + arg val; + ymax = val; + this.refresh; + } + + highlight_ { + arg identifier; + highlightIdentifier = identifier; + this.refresh; + } + + dictNotProperlyFormatted { + "FluidPlotter: The dictionary passed in is not properly formatted.".error; + } + + createPlotWindow { + arg bounds,parent_, mouseMoveAction,dict_; + var xpos, ypos; + var mouseAction = { + arg view, x, y, modifiers, buttonNumber, clickCount; + x = x.linlin(pointSize/2,userView.bounds.width-(pointSize/2),xmin,xmax); + y = y.linlin(pointSize/2,userView.bounds.height-(pointSize/2),ymax,ymin); + mouseMoveAction.(this,x,y,modifiers,buttonNumber, clickCount); + }; + + if(parent_.isNil,{xpos = 0; ypos = 0},{xpos = bounds.left; ypos = bounds.top}); + { + parent = parent_ ? Window("FluidPlotter",bounds); + userView = UserView(parent,Rect(xpos,ypos,bounds.width,bounds.height)); + + userView.drawFunc_({ + if(dict_internal.notNil,{ + dict_internal.keysValuesDo({ + arg key, pt; + var pointSize_, scaledx, scaledy, color; + + /* key.postln; + pt.postln; + pt.x.postln; + pt.y.postln;*/ + + if(key == highlightIdentifier,{ + pointSize_ = pointSize * 2.3 * pt.size + },{ + pointSize_ = pointSize * pt.size + }); + + pointSize_ = pointSize_ * pointSizeScale; + + scaledx = pt.x.linlin(xmin,xmax,0,userView.bounds.width,nil) - (pointSize_/2); + scaledy = pt.y.linlin(ymax,ymin,0,userView.bounds.height,nil) - (pointSize_/2); + + shape.switch( + \square,{Pen.addRect(Rect(scaledx,scaledy,pointSize_,pointSize_))}, + \circle,{Pen.addOval(Rect(scaledx,scaledy,pointSize_,pointSize_))} + ); + + Pen.color_(pt.color); + Pen.draw; + }); + }); + }); + + userView.mouseMoveAction_(mouseAction); + userView.mouseDownAction_(mouseAction); + + this.background_(Color.white); + + if(parent_.isNil,{parent.front;}); + }.defer; + } + + close { + parent.close; + } +} \ No newline at end of file diff --git a/release-packaging/Classes/FluidWaveform.sc b/release-packaging/Classes/FluidWaveform.sc new file mode 100644 index 0000000..88934e3 --- /dev/null +++ b/release-packaging/Classes/FluidWaveform.sc @@ -0,0 +1,47 @@ +FluidWaveform { + + *new { + arg audio_buf, slices_buf, bounds; + ^super.new.init(audio_buf,slices_buf, bounds); + } + + init { + arg audio_buf, slices_buf, bounds; + Task{ + var path = "%%_%_FluidWaveform.wav".format(PathName.tmp,Date.localtime.stamp,UniqueID.next); + var sfv, win, userView; + + bounds = bounds ? Rect(0,0,800,200); + win = Window("FluidWaveform",bounds); + audio_buf.write(path,"wav"); + + audio_buf.server.sync; + + sfv = SoundFileView(win,Rect(0,0,bounds.width,bounds.height)); + sfv.readFile(SoundFile(path)); + sfv.gridOn_(false); + + File.delete(path); + + if(slices_buf.notNil,{ + slices_buf.loadToFloatArray(action:{ + arg slices_fa; + + userView = UserView(win,Rect(0,0,bounds.width,bounds.height)) + .drawFunc_({ + slices_fa.do{ + arg start_samp; + var x = start_samp.linlin(0,audio_buf.numFrames,0,bounds.width); + Pen.line(Point(x,0),Point(x,bounds.height)); + Pen.color_(Color.red); + Pen.stroke; + }; + }); + }); + }); + + win.front; + }.play(AppClock); + } +} + diff --git a/test/FluidPlotter_test.scd b/test/FluidPlotter_test.scd new file mode 100644 index 0000000..55b606d --- /dev/null +++ b/test/FluidPlotter_test.scd @@ -0,0 +1,146 @@ +( +// make some dummy data and plot it +~dummy_data = { + arg xmin = 20, xmax = 20000, ymin = -130, ymax = 0; + Dictionary.newFrom([ + "cols",2, + "data",Dictionary.newFrom(Array.fill(200,{ + arg i; + var return; + if((i % 2) == 0,{ + return = "example-%".format((i/2).asInteger); + },{ + return = [rrand(xmin,xmax),rrand(ymin,ymax)]; + }); + // return.postln; + return; + })) + ]); +}; + +Window.closeAll; +// self window +d = ~dummy_data.value; +// d.postln; +~fp = FluidPlotter(bounds:Rect(200,200,600,600),dict:d,mouseMoveAction:{ + arg view, x, y, modifiers; + [view, x, y, modifiers].dopostln; + "".postln; +},xmin:20,xmax:20000,ymin:-130,ymax:0); +) + +// click and drag on the plotter to report stuff in the mouseMoveAction callback function + +// change point size of just one point +~fp.pointSize_("example-5",10); + +// change it back +~fp.pointSize_("example-5",1); + +// change all points size bigger... +~fp.pointSizeScale_(2); + +// ...smaller... +~fp.pointSizeScale_(0.5); + +// ...back to normal +~fp.pointSizeScale_(1); + +( +// change 10 random points red +10.do({ + ~fp.pointColor_("example-%".format(rrand(0,99)),Color.red); +}); +) +// "highlight" a point (makes it a little bigger) +~fp.highlight_("example-95"); + +// a different one +~fp.highlight_("example-94"); + +// none +~fp.highlight_(nil); + +// put some different data in +~fp.dict_(~dummy_data.value); + +// change the ranges +( +~fp.ymin_(-140); +~fp.ymax_(10); +~fp.xmin_(-200); +~fp.xmax_(21000); +) + +// change the point shapes +~fp.shape_(\square); + +// change back to circles +~fp.shape_(\circle); + +// change the color of just one point +~fp.pointColor_("example-7",Color.red); + +// change the background color +~fp.background_(Color.red) +~fp.background_(Color.white) + +// ==== perform KMeans on the data and colorize the categories ====== +( +s.waitForBoot{ + Routine{ + var labelset = FluidLabelSet(s); + var kmeans = FluidKMeans(s); + var ds = FluidDataSet(s); + + s.sync; + + ds.load(~fp.dict,{ + kmeans.fitPredict(ds,labelset,{ + labelset.dump({ + arg lsdict; + defer{~fp.categories_(lsdict)}; + "done".postln; + }); + }); + }); + }.play; +} +) + +// close it or it's parent +~fp.close; + + +// a FluidPlotter inside a parent with parent +( +Window.closeAll; +d = Dictionary.newFrom([ + "cols",2, + "data",Dictionary.newFrom(Array.fill(200,{ + arg i; + var return; + if((i%2) == 0,{ + return = "example-%".format((i/2).asInteger); + },{ + return = [exprand(20,20000),rrand(-130,0)]; + }); + return; + })) +]); +w = Window("test",Rect(50,50,800,600)).front; +~fp = FluidPlotter(w,Rect(50,50,400,400),dict:d,mouseMoveAction:{ + arg view, x, y, modifiers; + [view, x, y, modifiers].dopostln; + "".postln; +},xmin:20,xmax:20000,ymin:-130,ymax:0); +) + +// you can make an empty one and then set the dict later +( +Window.closeAll; +~fp = FluidPlotter(bounds:Rect(100,100,500,500)) +) + +// now set data +~fp.dict_(~dummy_data.(0.01,1,0.0,1.0).postln); \ No newline at end of file diff --git a/test/FluidWaveform_test.scd b/test/FluidWaveform_test.scd new file mode 100644 index 0000000..93a5d55 --- /dev/null +++ b/test/FluidWaveform_test.scd @@ -0,0 +1,14 @@ +( +s.waitForBoot{ + + Task{ + var buf = Buffer.read(s,"/Users/macprocomputer/Desktop/_flucoma/code/flucoma-core-src/AudioFiles/Nicol-LoopE-M.wav"); + var slicepoints = Buffer(s); + + FluidBufAmpSlice.process(s,buf,indices:slicepoints,fastRampUp:10,fastRampDown:2205,slowRampUp:4410,slowRampDown:4410,onThreshold:10,offThreshold:5,floor:-40,minSliceLength:4410,highPassFreq:20,action:{ + FluidWaveform(buf,slicepoints,Rect(0,0,1600,400)); + }); + + }.play(AppClock); +} +) \ No newline at end of file