FluidPlotter & FluidWaveform (#36)
* FluidPlotter and test file highlight method sets color * FluidPlotter is beta * added some error checks and warnings * FluidWaveform in alpha * updated fluid plotter to require fewer external defers * fluid plotter synced up with james' max one, helpfile _started_ * fix/FluidPlotter only worked with KMeans, not it also works with a user created LabelSet * FluidPlotter helpfile done * FluidWaveform delete temp file delete it right away instead of on close * initialize with internal dict a plotter initialized with no 'dict' now makes an empty internal dict so that one can use 'setPoint' and 'addPoint' right off the bat * FluidWaveform helpfile made Co-authored-by: tremblap <info@pierrealexandretremblay.com>nix
parent
5933b96cff
commit
fb053f3c71
@ -0,0 +1,253 @@
|
|||||||
|
FluidPlotterPoint {
|
||||||
|
var id, <x, <y, <>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 <parent, <userView, <xmin, <xmax, <ymin, <ymax, <pointSize = 6, pointSizeScale = 1, dict_internal, <dict, shape = \circle, catColors, highlightIdentifier;
|
||||||
|
|
||||||
|
*new {
|
||||||
|
arg parent, bounds, dict, mouseMoveAction,xmin = 0,xmax = 1,ymin = 0,ymax = 1;
|
||||||
|
^super.new.init(parent, bounds, dict, mouseMoveAction,xmin,xmax,ymin,ymax);
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
arg parent_, bounds, dict_, mouseMoveAction, xmin_ = 0,xmax_ = 1,ymin_ = 0,ymax_ = 1;
|
||||||
|
|
||||||
|
parent = parent_;
|
||||||
|
xmin = xmin_;
|
||||||
|
xmax = xmax_;
|
||||||
|
ymin = ymin_;
|
||||||
|
ymax = ymax_;
|
||||||
|
|
||||||
|
this.createCatColors;
|
||||||
|
dict_internal = Dictionary.new;
|
||||||
|
if(dict_.notNil,{this.dict_(dict_)});
|
||||||
|
this.createPlotWindow(bounds,parent_,mouseMoveAction,dict_);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCatColors {
|
||||||
|
catColors = "1f77b4ff7f0e2ca02cd627289467bd8c564be377c27f7f7fbcbd2217becf".clump(6).collect({
|
||||||
|
arg hex;
|
||||||
|
Color.newHex(hex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
categories_ {
|
||||||
|
arg labelSetDict;
|
||||||
|
if(dict_internal.size == 0,{
|
||||||
|
var label_to_int = Dictionary.new;
|
||||||
|
var counter = 0;
|
||||||
|
dict_internal.keysValuesDo({
|
||||||
|
arg id, fp_pt;
|
||||||
|
var category_string = labelSetDict.at("data").at(id)[0];
|
||||||
|
var category_int;
|
||||||
|
var color;
|
||||||
|
|
||||||
|
if(label_to_int.at(category_string).isNil,{
|
||||||
|
label_to_int.put(category_string,counter);
|
||||||
|
counter = counter + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
category_int = label_to_int.at(category_string);
|
||||||
|
|
||||||
|
if(category_int > (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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -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);
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue