resizable and layoutable guis (#83)

* resizable and layoutable guis

* FluidWaveform: rename 'win' to 'parent'

* FluidWaveform/FluidPlotter: update help

* FluidWaveform/Plotter: make views before forking

This way views are immediately available upon creation,
for example to be added to layouts.
Views are still correctly updated with data from within the fork,
whenever they are ready.

* Thanks @elgiano! + a few small edits

Co-authored-by: Ted Moore <ted@tedmooremusic.com>
nix
gianlucaelia 4 years ago committed by GitHub
parent 106e4d5ea1
commit d776b25ed5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,36 +2,28 @@ 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_;
arg id, x, y, color(Color.black), size = 1;
^super.newCopyArgs(id,x,y,color,size);
}
}
FluidPlotter : FluidViewer {
var <parent, <userView, <xmin, <xmax, <ymin, <ymax, <zoomxmin, <zoomxmax, <zoomymin, <zoomymax, <pointSize = 6, pointSizeScale = 1, dict_internal, <dict, shape = \circle, highlightIdentifiersArray, categoryColors;
var <parent, <xmin, <xmax, <ymin, <ymax, standalone,
<zoomxmin, <zoomxmax, <zoomymin, <zoomymax,
<userView, <pointSize = 6, pointSizeScale = 1, dict_internal, <dict,
shape = \circle, highlightIdentifiersArray, categoryColors;
*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);
arg parent, bounds, dict, mouseMoveAction,
xmin = 0, xmax = 1, ymin = 0, ymax = 1, standalone = true;
if (parent.notNil) { standalone = false };
^super.newCopyArgs(parent, xmin, xmax, ymin, ymax, standalone)
.init(bounds, dict, mouseMoveAction);
}
init {
arg parent_, bounds, dict_, mouseMoveAction, xmin_ = 0,xmax_ = 1,ymin_ = 0,ymax_ = 1;
parent = parent_;
xmin = xmin_;
xmax = xmax_;
ymin = ymin_;
ymax = ymax_;
arg bounds, dict, mouseMoveAction;
zoomxmin = xmin;
zoomxmax = xmax;
@ -40,8 +32,8 @@ FluidPlotter : FluidViewer {
categoryColors = this.createCatColors;
dict_internal = Dictionary.new;
if(dict_.notNil,{this.dict_(dict_)});
this.createPlotWindow(bounds,parent_,mouseMoveAction,dict_);
if (dict.notNil) { this.dict = dict };
this.createPlotWindow(bounds, mouseMoveAction);
}
categories_ {
@ -196,49 +188,61 @@ FluidPlotter : FluidViewer {
highlightIdentifiersArray = identifier.collect({arg item; item.asSymbol});
this.refresh;
}
}
dictNotProperlyFormatted {
"FluidPlotter: The dictionary passed in is not properly formatted.".error;
}
createPlotWindow {
arg bounds,parent_, mouseMoveAction,dict_;
var xpos, ypos;
arg bounds, mouseMoveAction;
var zoomRect = nil;
var zoomDragStart = Point(0,0);
if(parent_.isNil,{xpos = 0; ypos = 0},{xpos = bounds.left; ypos = bounds.top});
bounds = bounds ? Rect(0,0,800,800);
if (parent.isNil) {
if (standalone) {
parent = Window("FluidPlotter", bounds);
userView = UserView();
defer {
parent.view.layout = HLayout(userView).margins_(0).spacing_(0);
}
} {
parent = userView = UserView();
}
} {
userView = UserView(parent, bounds)
};
{
var reportMouseActivity;
parent = parent_ ? Window("FluidPlotter",bounds);
userView = UserView(parent,Rect(xpos,ypos,bounds.width,bounds.height));
userView.drawFunc_({
userView.drawFunc = {
arg viewport;
var w = viewport.bounds.width, h = viewport.bounds.height;
if(dict_internal.notNil,{
dict_internal.keysValuesDo({
arg key, pt;
var pointSize_, scaledx, scaledy, color;
if(highlightIdentifiersArray.notNil,{
if(highlightIdentifiersArray.includes(key),{
pointSize_ = pointSize * 2.3 * pt.size
},{
pointSize_ = pointSize * pt.size
});
},{
pointSize_ = pointSize * pt.size;
});
pointSize_ = pointSize * pt.size;
if (highlightIdentifiersArray.notNil) {
if (highlightIdentifiersArray.includes(key)) {
pointSize_ = pointSize_ * 2.3;
};
};
pointSize_ = pointSize_ * pointSizeScale;
scaledx = pt.x.linlin(zoomxmin,zoomxmax,0,userView.bounds.width,nil);
scaledy = pt.y.linlin(zoomymax,zoomymin,0,userView.bounds.height,nil);
scaledx = pt.x.linlin(zoomxmin,zoomxmax,0,w,nil) - (pointSize_/2);
scaledy = pt.y.linlin(zoomymax,zoomymin,0,h,nil) - (pointSize_/2);
shape.switch(
\square,{Pen.addRect(Rect(scaledx - (pointSize_ /2),scaledy - (pointSize_ /2),pointSize_,pointSize_))},
\circle,{Pen.addOval(Rect(scaledx - (pointSize_ /2),scaledy - (pointSize_ /2),pointSize_,pointSize_))}
\square, {
Pen.addRect(Rect(scaledx,scaledy,pointSize_,pointSize_))
},
\circle, {
Pen.addOval(Rect(scaledx,scaledy,pointSize_,pointSize_))
}
);
Pen.color_(pt.color);
@ -251,7 +255,7 @@ FluidPlotter : FluidViewer {
Pen.draw(2);
});
});
});
};
reportMouseActivity = {
arg view, x, y, modifiers, buttonNumber, clickCount;
@ -260,32 +264,32 @@ FluidPlotter : FluidViewer {
mouseMoveAction.(this,realx,realy,modifiers,buttonNumber, clickCount);
};
userView.mouseDownAction_({
userView.mouseDownAction = {
arg view, x, y, modifiers, buttonNumber, clickCount;
case{modifiers == 524288}{
case { modifiers.isAlt } {
zoomDragStart.x = x;
zoomDragStart.y = y;
zoomRect = Rect(zoomDragStart.x,zoomDragStart.y,0,0);
}
{modifiers == 262144}{
{ modifiers.isCtrl } {
this.resetZoom;
}
{
reportMouseActivity.(this,x,y,modifiers,buttonNumber,clickCount);
};
});
};
userView.mouseMoveAction_({
userView.mouseMoveAction = {
arg view, x, y, modifiers, buttonNumber, clickCount;
if(modifiers == 524288,{
if (modifiers.isAlt) {
zoomRect = Rect(zoomDragStart.x,zoomDragStart.y,x - zoomDragStart.x,y - zoomDragStart.y);
this.refresh;
},{
} {
reportMouseActivity.(this,x,y,modifiers,buttonNumber,clickCount);
});
});
};
};
userView.mouseUpAction_({
userView.mouseUpAction = {
arg view, x, y, modifiers, buttonNumber, clickCount;
if(zoomRect.notNil,{
@ -312,14 +316,16 @@ FluidPlotter : FluidViewer {
});
reportMouseActivity.(this,x,y,modifiers,buttonNumber,clickCount);
});
};
this.background_(Color.white);
if(parent_.isNil,{parent.front;});
if (standalone) { parent.front };
}.defer;
}
asView { ^userView }
resetZoom {
zoomxmin = xmin;
zoomxmax = xmax;

@ -24,38 +24,27 @@ FluidWaveformAudioLayer {
var audioBuffer, waveformColor;
*new {
arg audioBuffer, waveformColor;
^super.new.init(audioBuffer,waveformColor);
}
init {
arg audioBuffer_, waveformColor_;
audioBuffer = audioBuffer_;
waveformColor = waveformColor_ ? Color.gray;
arg audioBuffer, waveformColor(Color.gray);
^super.newCopyArgs(audioBuffer, waveformColor);
}
draw {
arg win, bounds;
fork({
var path = "%%_%_FluidWaveform.wav".format(PathName.tmp,Date.localtime.stamp,UniqueID.next);
var sfv;
var path = "%%_%_FluidWaveform.wav".format(PathName.tmp,Date.localtime.stamp,UniqueID.next);
var sfv = SoundFileView();
sfv.peakColor_(waveformColor);
sfv.drawsBoundingLines_(false);
sfv.rmsColor_(Color.clear);
sfv.background_(Color.clear);
sfv.gridOn_(false);
forkIfNeeded({
audioBuffer.write(path,"wav");
audioBuffer.server.sync;
sfv = SoundFileView(win,bounds);
sfv.peakColor_(waveformColor);
sfv.drawsBoundingLines_(false);
sfv.rmsColor_(Color.clear);
sfv.background_(Color.clear);
sfv.readFile(SoundFile(path));
sfv.gridOn_(false);
File.delete(path)
}, AppClock);
File.delete(path);
},AppClock);
^audioBuffer.server;
^sfv
}
}
@ -63,67 +52,68 @@ FluidWaveformIndicesLayer : FluidViewer {
var indicesBuffer, audioBuffer, color, lineWidth;
*new {
arg indicesBuffer, audioBuffer, color, lineWidth = 1;
^super.new.init(indicesBuffer, audioBuffer, color, lineWidth);
}
init {
arg indicesBuffer_, audioBuffer_, color_, lineWidth_;
indicesBuffer = indicesBuffer_;
audioBuffer = audioBuffer_;
color = color_ ? Color.red;
lineWidth = lineWidth_;
arg indicesBuffer, audioBuffer, color(Color.red), lineWidth = 1;
^super.newCopyArgs(indicesBuffer, audioBuffer, color, lineWidth);
}
draw {
arg win, bounds;
if(audioBuffer.notNil,{
fork({
indicesBuffer.numChannels.switch(
1,{
indicesBuffer.loadToFloatArray(action:{
arg slices_fa;
UserView(win,bounds)
.drawFunc_({
Pen.width_(lineWidth);
slices_fa.do{
arg start_samp;
var x = start_samp.linlin(0,audioBuffer.numFrames,0,bounds.width);
Pen.line(Point(x,0),Point(x,bounds.height));
Pen.color_(color);
Pen.stroke;
};
});
});
},
2,{
indicesBuffer.loadToFloatArray(action:{
arg slices_fa;
slices_fa = slices_fa.clump(2);
UserView(win,bounds)
.drawFunc_({
Pen.width_(lineWidth);
slices_fa.do{
arg arr;
var start = arr[0].linlin(0,audioBuffer.numFrames,0,bounds.width);
var end = arr[1].linlin(0,audioBuffer.numFrames,0,bounds.width);
Pen.addRect(Rect(start,0,end-start,bounds.height));
Pen.color_(color.alpha_(0.25));
Pen.fill;
};
});
});
},{
Error("% indicesBuffer must have either 1 nor 2 channels.".format(this.class)).throw;
}
);
},AppClock);
^indicesBuffer.server;
},{
Error("% In order to display an indicesBuffer an audioBuffer must be included.".format(this.class)).throw;
});
var userView;
var condition = CondVar();
var slices_fa = nil;
var numChannels = indicesBuffer.numChannels;
if ([1, 2].includes(numChannels).not) {
Error(
"% indicesBuffer must have either 1 or 2 channels."
.format(this.class)
).throw;
};
if (audioBuffer.isNil) {
Error(
"% In order to display an indicesBuffer an audioBuffer must be included."
.format(this.class)
).throw;
};
userView = UserView();
forkIfNeeded({
indicesBuffer.loadToFloatArray(action: {
arg v;
slices_fa = v;
condition.signalOne;
});
condition.wait { slices_fa.notNil };
userView.drawFunc = numChannels.switch(
1, {{
arg viewport;
var bounds = viewport.bounds;
Pen.width_(lineWidth);
slices_fa.do{
arg start_samp;
var x = start_samp.linlin(0,audioBuffer.numFrames,0,bounds.width);
Pen.line(Point(x,0),Point(x,bounds.height));
Pen.color_(color);
Pen.stroke;
}};
},
2, {{
arg viewport;
var bounds = viewport.bounds;
Pen.width_(lineWidth);
slices_fa.clump(2).do{
arg arr;
var start = arr[0].linlin(0,audioBuffer.numFrames,0,bounds.width);
var end = arr[1].linlin(0,audioBuffer.numFrames,0,bounds.width);
Pen.addRect(Rect(start,0,end-start,bounds.height));
Pen.color_(color.alpha_(0.25));
Pen.fill;
}};
}
);
}, AppClock);
^userView;
}
}
@ -132,33 +122,28 @@ FluidWaveformFeaturesLayer : FluidViewer {
*new {
arg featuresBuffer, colors, stackFeatures = false, normalizeFeaturesIndependently = true, lineWidth = 1;
^super.new.init(featuresBuffer,colors,stackFeatures,normalizeFeaturesIndependently,lineWidth);
}
init {
arg featuresBuffer_, colors_, stackFeatures_ = false, normalizeFeaturesIndependently_ = true, lineWidth_ = 1;
featuresBuffer = featuresBuffer_;
normalizeFeaturesIndependently = normalizeFeaturesIndependently_;
stackFeatures = stackFeatures_;
lineWidth = lineWidth_;
colors = colors_ ?? {this.createCatColors};
colors = colors ?? { this.createCatColors };
// we'll index into it to draw, so just in case the user passed just one color, this will ensure it can be "indexed" into
if(colors.isKindOf(SequenceableCollection).not,{colors = [colors]});
if (colors.isKindOf(SequenceableCollection).not) { colors = [colors] };
^super.newCopyArgs(
featuresBuffer,colors,stackFeatures,normalizeFeaturesIndependently, lineWidth
);
}
draw {
arg win, bounds;
var userView = UserView();
var condition = CondVar();
var fa = nil;
featuresBuffer.loadToFloatArray(action:{
arg fa;
forkIfNeeded({
var minVal = 0, maxVal = 0;
var stacked_height;
if(stackFeatures,{
stacked_height = bounds.height / featuresBuffer.numChannels;
featuresBuffer.loadToFloatArray(action:{
arg v;
fa = v;
condition.signalOne
});
condition.wait { fa.notNil };
if(normalizeFeaturesIndependently.not,{
minVal = fa.minItem;
@ -167,7 +152,14 @@ FluidWaveformFeaturesLayer : FluidViewer {
fa = fa.clump(featuresBuffer.numChannels).flop;
fork({
userView.drawFunc_({
arg viewport;
var bounds = viewport.bounds;
var stacked_height;
if (stackFeatures) {
stacked_height = bounds.height / featuresBuffer.numChannels;
};
fa.do({
arg channel, channel_i;
var maxy;// a lower value;
@ -186,23 +178,22 @@ FluidWaveformFeaturesLayer : FluidViewer {
maxVal = channel.maxItem;
});
channel = channel.resamp1(bounds.width).linlin(minVal,maxVal,miny,maxy);
UserView(win,bounds)
.drawFunc_({
Pen.width_(lineWidth);
Pen.moveTo(Point(0,channel[0]));
channel[1..].do{
arg val, i;
Pen.lineTo(Point(i+1,val));
};
Pen.color_(colors[channel_i % colors.size]);
Pen.stroke;
});
channel = channel.resamp1(bounds.width)
.linlin(minVal,maxVal,miny,maxy);
Pen.width = lineWidth;
Pen.moveTo(Point(0,channel[0]));
channel[1..].do{
arg val, i;
Pen.lineTo(Point(i+1,val));
};
Pen.color_(colors[channel_i % colors.size]);
Pen.stroke;
});
},AppClock);
});
^featuresBuffer.server;
});
}, AppClock);
^userView;
}
}
@ -211,27 +202,73 @@ FluidWaveformImageLayer {
*new {
arg imageBuffer, imageColorScheme = 0, imageColorScaling = 0, imageAlpha = 1;
^super.new.init(imageBuffer,imageColorScheme,imageColorScaling,imageAlpha);
^super.newCopyArgs(
imageBuffer, imageColorScheme, imageColorScaling, imageAlpha
);
}
init {
arg imageBuffer_, imageColorScheme_ = 0, imageColorScaling_ = 0, imageAlpha_ = 1;
draw {
var colors = this.prGetColorsFromScheme(imageColorScheme);
var condition = CondVar();
var vals = nil;
var userView = UserView();
forkIfNeeded({
var img = Image(imageBuffer.numFrames, imageBuffer.numChannels);
imageBuffer.loadToFloatArray(action: {
arg v;
vals = v;
condition.signalOne;
});
condition.wait { vals.notNil };
imageBuffer = imageBuffer_;
imageColorScheme = imageColorScheme_;
imageColorScaling = imageColorScaling_;
imageAlpha = imageAlpha_;
imageColorScaling.switch(
FluidWaveform.lin,{
var minItem = vals.minItem;
vals = (vals - minItem) / (vals.maxItem - minItem);
vals = (vals * 255).asInteger;
},
FluidWaveform.log,{
vals = (vals + 1e-6).log;
vals = vals.linlin(vals.minItem,vals.maxItem,0.0,255.0).asInteger;
},
{
"% colorScaling argument % is invalid.".format(thisMethod,imageColorScaling).warn;
}
);
vals.do{
arg val, index;
img.setColor(colors[val], index.div(imageBuffer.numChannels), imageBuffer.numChannels - 1 - index.mod(imageBuffer.numChannels));
};
userView.drawFunc = {
arg viewport;
var bounds = viewport.bounds;
img.drawInRect(
Rect(0, 0, bounds.width, bounds.height),
fraction: imageAlpha
);
};
}, AppClock);
^userView;
}
draw {
arg win, bounds;
var colors;
loadColorFile {
arg filename;
^CSVFileReader.readInterpret(FluidFilesPath("../color-schemes/%.csv".format(filename))).collect{
arg row;
Color.fromArray(row);
}
}
prGetColorsFromScheme {
arg imageColorScheme;
var colors;
if(imageColorScheme.isKindOf(Color),{
// "imageColorScheme is a kind of Color".postln;
colors = 256.collect{
arg i;
Color(imageColorScheme.red,imageColorScheme.green,imageColorScheme.blue,i.linlin(0,255,0.0,1.0));
imageColorScheme.copy.alpha_(i.linlin(0,255,0.0,1.0));
};
},{
imageColorScheme.switch(
@ -256,156 +293,90 @@ FluidWaveformImageLayer {
);
});
imageBuffer.loadToFloatArray(action:{
arg vals;
fork({
var img = Image(imageBuffer.numFrames,imageBuffer.numChannels);
imageColorScaling.switch(
FluidWaveform.lin,{
var minItem = vals.minItem;
vals = (vals - minItem) / (vals.maxItem - minItem);
vals = (vals * 255).asInteger;
},
FluidWaveform.log,{
vals = (vals + 1e-6).log;
vals = vals.linlin(vals.minItem,vals.maxItem,0.0,255.0).asInteger;
// vals.postln;
},
{
"% colorScaling argument % is invalid.".format(thisMethod,imageColorScaling).warn;
}
);
// colors.postln;
vals.do{
arg val, index;
img.setColor(colors[val], index.div(imageBuffer.numChannels), imageBuffer.numChannels - 1 - index.mod(imageBuffer.numChannels));
};
UserView(win,bounds)
.drawFunc_{
img.drawInRect(Rect(0,0,bounds.width,bounds.height),fraction:imageAlpha);
};
},AppClock);
});
^imageBuffer.server;
}
loadColorFile {
arg filename;
^CSVFileReader.readInterpret(FluidFilesPath("../color-schemes/%.csv".format(filename))).collect{
arg row;
Color.fromArray(row);
}
^colors;
}
}
FluidWaveform : FluidViewer {
classvar <lin = 0, <log = 1;
var <win, bounds, display_bounds, <layers;
var <parent, bounds, standalone, view, <layers;
*new {
arg audioBuffer, indicesBuffer, featuresBuffer, parent, bounds, lineWidth = 1, waveformColor, stackFeatures = false, imageBuffer, imageColorScheme = 0, imageAlpha = 1, normalizeFeaturesIndependently = true, imageColorScaling = 0;
^super.new.init(audioBuffer,indicesBuffer, featuresBuffer, parent, bounds, lineWidth, waveformColor,stackFeatures,imageBuffer,imageColorScheme,imageAlpha,normalizeFeaturesIndependently,imageColorScaling);
arg audioBuffer, indicesBuffer, featuresBuffer,
parent, bounds,
lineWidth = 1, waveformColor, stackFeatures = false,
imageBuffer, imageColorScheme = 0, imageAlpha = 1,
normalizeFeaturesIndependently = true, imageColorScaling = 0,
standalone = true;
if (parent.notNil) { standalone = false };
^super.newCopyArgs(parent, bounds, standalone)
.init(audioBuffer, indicesBuffer, featuresBuffer,
lineWidth, waveformColor, stackFeatures,
imageBuffer, imageColorScheme, imageAlpha,
normalizeFeaturesIndependently, imageColorScaling
);
}
init {
arg audio_buf, slices_buf, feature_buf, parent_, bounds_, lineWidth = 1, waveformColor,stackFeatures = false, imageBuffer, imageColorScheme = 0, imageAlpha = 1, normalizeFeaturesIndependently = true, imageColorScaling = 0;
layers = List.new;
fork({
var plotImmediately = false;
bounds = bounds_;
arg audio_buf, slices_buf, feature_buf,
lineWidth = 1, waveformColor, stackFeatures = false,
imageBuffer, imageColorScheme = 0, imageAlpha = 1,
normalizeFeaturesIndependently = true, imageColorScaling = 0;
var plotImmediately = false;
waveformColor = waveformColor ? Color(*0.6.dup(3));
layers = List.new;
waveformColor = waveformColor ? Color(*0.6.dup(3));
if(bounds.isNil && imageBuffer.notNil,{
if (imageBuffer.notNil) {
this.addImageLayer(imageBuffer, imageColorScheme, imageColorScaling, imageAlpha);
if(standalone && bounds.isNil) {
bounds = Rect(0,0,imageBuffer.numFrames,imageBuffer.numChannels);
});
bounds = bounds ? Rect(0,0,800,200);
if(parent_.isNil,{
win = Window("FluidWaveform",bounds);
win.background_(Color.white);
display_bounds = Rect(0,0,bounds.width,bounds.height);
},{
win = parent_;
display_bounds = bounds;
});
if(imageBuffer.notNil,{
this.addImageLayer(imageBuffer,imageColorScheme,imageColorScaling,imageAlpha);
imageBuffer.server.sync;
plotImmediately = true;
});
};
plotImmediately = true;
};
if(audio_buf.notNil,{
this.addAudioLayer(audio_buf,waveformColor);
audio_buf.server.sync;
plotImmediately = true;
});
if (audio_buf.notNil) {
this.addAudioLayer(audio_buf, waveformColor);
plotImmediately = true;
};
if(feature_buf.notNil,{
this.addFeaturesLayer(feature_buf,this.createCatColors,stackFeatures,normalizeFeaturesIndependently,lineWidth);
feature_buf.server.sync;
plotImmediately = true;
});
if (feature_buf.notNil) {
this.addFeaturesLayer(feature_buf, this.createCatColors, stackFeatures, normalizeFeaturesIndependently, lineWidth);
plotImmediately = true;
};
if(slices_buf.notNil,{
this.addIndicesLayer(slices_buf,audio_buf,Color.red,lineWidth);
slices_buf.server.sync;
plotImmediately = true;
});
if (slices_buf.notNil) {
this.addIndicesLayer(slices_buf, audio_buf, Color.red, lineWidth);
plotImmediately = true;
};
if(plotImmediately,{this.front;});
},AppClock);
if (plotImmediately) { this.front };
}
addImageLayer {
arg imageBuffer, imageColorScheme = 0, imageColorScaling = 0, imageAlpha = 1;
var l = FluidWaveformImageLayer(imageBuffer,imageColorScheme,imageColorScaling,imageAlpha);
// l.postln;
var l = FluidWaveformImageLayer(imageBuffer, imageColorScheme, imageColorScaling, imageAlpha);
layers.add(l);
// layers.postln;
// l.draw(win,display_bounds);
}
addAudioLayer {
arg audioBuffer, waveformColor;
var l = FluidWaveformAudioLayer(audioBuffer,waveformColor);
// l.postln;
var l = FluidWaveformAudioLayer(audioBuffer, waveformColor);
layers.add(l);
// layers.postln;
// l.draw(win,display_bounds);
}
addIndicesLayer {
arg indicesBuffer, audioBuffer, color, lineWidth = 1;
var l = FluidWaveformIndicesLayer(indicesBuffer,audioBuffer,color,lineWidth);
// l.postln;
layers.add(l);
// layers.postln;
// l.draw(win,display_bounds);
}
addFeaturesLayer {
arg featuresBuffer, colors, stackFeatures = false, normalizeFeaturesIndependently = true, lineWidth = 1;
var l = FluidWaveformFeaturesLayer(featuresBuffer,colors,stackFeatures,normalizeFeaturesIndependently,lineWidth);
// l.postln;
var l = FluidWaveformFeaturesLayer(featuresBuffer,colors,stackFeatures,normalizeFeaturesIndependently, lineWidth);
layers.add(l);
// layers.postln;
// l.draw(win,display_bounds);
}
addLayer {
@ -414,26 +385,50 @@ FluidWaveform : FluidViewer {
}
front {
this.prMakeView;
fork({
this.refresh;
if (standalone) { parent.front };
}, AppClock);
}
UserView(win,display_bounds)
.drawFunc_{
Pen.fillColor_(Color.white);
Pen.addRect(Rect(0,0,bounds.width,bounds.height));
Pen.fill;
};
layers.do{
arg layer;
// layer.postln;
layer.draw(win,display_bounds).sync;
// defer({}, nil) forks if not already running on the AppClock
refresh {
forkIfNeeded({
var noView = if (view.isNil) { true } { view.isClosed };
if (noView) { this.prMakeView };
view.removeAll;
view.layout = StackLayout().mode_(\stackAll);
layers.do {
arg layer, n;
var layerView;
layerView = layer.draw;
view.layout.add(layerView);
view.layout.index = view.layout.index + 1;
};
win.front;
},AppClock);
view.refresh;
}, AppClock);
}
close {
win.close;
parent.close;
}
}
prMakeView {
if (parent.isNil) {
if (standalone) {
parent = Window("FluidWaveform", bounds: bounds ? Rect(0,0,800,200));
parent.background_(Color.white);
view = parent.view;
} {
parent = view = View();
view.background_(Color.white);
}
} {
view = View(parent, bounds)
view.background_(Color.white);
};
}
asView { ^view }
}

@ -14,7 +14,7 @@ METHOD:: new
Creates a new instance of FluidPlotter
ARGUMENT:: parent
A parent view to embed the FluidPlotter in. If no parent is passed, FluidPlotter will create a window for itself at the given bounds.
A parent view to embed the FluidPlotter in. If no parent is passed, FluidPlotter will create a window for itself at the given bounds. To create a view without STRONG::parent:: and STRONG::bounds:: (e.g. for GUIs with link::Guides/GUI-Layout-Management::), see the STRONG::standalone:: argument below.
ARGUMENT:: bounds
Where to show the FluidPlotter, either within the parent or on the screen (if no parent is passed).
@ -37,6 +37,9 @@ Minimum of the Y range to display. Default is 0.
ARGUMENT:: ymax
Maximum of the Y range to display. Default is 1.
ARGUMENT:: standalone
If strong::false::, creates a link::Classes/View:: without parent or bounds, so that it can be used as part of a larger GUI, e.g. with link::Guides/GUI-Layout-Management::.
returns::
An instance of FluidPlotter
@ -308,6 +311,36 @@ w = Window("test",Rect(50,50,800,600)).front;
},xmin:20,xmax:20000,ymin:-130,ymax:0);
)
// two FluidPlotter side by side with standalone=false
(
Window.closeAll;
// make two different data dictionaries
d = 2.collect { 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,1200,600)).front;
// make two plotters, one for each data dictionary
~fluidPlotters = d.collect { |data|
FluidPlotter(dict: data, standalone: false, mouseMoveAction:{
arg view, x, y, modifiers;
[view, x, y, modifiers].postln;
"".postln;
},xmin:20,xmax:20000,ymin:-130,ymax:0)
};
// assign plotters to window's layout
w.layout = HLayout(~fluidPlotters[0], ~fluidPlotters[1]);
)
// you can make an empty one and then set the dict later
(
Window.closeAll;

@ -21,7 +21,7 @@ ARGUMENT:: featuresBuffer
A link::Classes/Buffer:: containing features to plot over the waveform. If this link::Classes/Buffer:: is multiple channels, it will plot each channel as a separate feature.
ARGUMENT:: parent
A link::Classes/Window:: to place this FluidWaveform in. If STRONG::nil::, FluidWaveform will make its own window using the STRONG::bounds:: argument.
A link::Classes/Window:: to place this FluidWaveform in. If STRONG::nil::, FluidWaveform will make its own window using the STRONG::bounds:: argument. To create a view without parent and bounds (e.g. for GUIs with link::Guides/GUI-Layout-Management::), see the STRONG::standalone:: argument below.
ARGUMENT:: bounds
A link::Classes/Rect:: of where to place the FluidWaveform. If parent is STRONG::nil::, these bounds will be used to create a new link::Classes/Window::. If parent is not STRONG::nil::, these bounds will be used to place this FluidWaveform in the parent.
@ -57,6 +57,9 @@ Boolean. All the features in STRONG::featureBuf:: need to be normalized for plot
ARGUMENT:: imageColorScaling
An integer indicating how to scale the values in strong::imageBuffer:: before applying the strong::imageColorScheme::. 0 indicates linear scaling, 1 indicates logarithmic scaling. The default is 1. These integers can also be accessed via FluidWaveform.lin and FluidWaveform.log.
ARGUMENT:: standalone
If strong::false::, creates a link::Classes/View:: without parent or bounds, so that it can be used as part of a larger GUI, e.g. with link::Guides/GUI-Layout-Management::.
returns:: A new instance of FluidWaveform.
METHOD:: lin
@ -109,6 +112,9 @@ See this argument in the class method 'new' above.
ARGUMENT:: normalizeFeaturesIndependently
See this argument in the class method 'new' above.
ARGUMENT:: lineWidth
See this argument in the class method 'new' above.
METHOD:: addImageLayer
Add a grapic layer that shows an image derived from a buffer.
@ -127,11 +133,14 @@ See this argument in the class method 'new' above.
METHOD:: front
Similar to link::Classes/Window::'s strong::front:: method. Shows the FluidWaveform. This must be called after layers have been added in order to see the layers.
METHOD:: refresh
Similar to link::Classes/Window::'s strong::refresh:: method. Redraws all FluidWaveform layers. Has to be called after link::Classes/FluidWaveform#-addLayer:: in order to see the new layer.
METHOD:: close
Close the FluidWaveform window. If parent is not STRONG::nil::, this method will close the parent window.
METHOD:: win
METHOD:: parent
returns:: The FluidWaveform window. If parent is not STRONG::nil::, this method will return the parent window.
@ -154,6 +163,14 @@ FluidWaveform(~drums,parent:w,bounds:Rect(100,100,800,300));
w.front;
)
// put two of them in another window's layout
(
w = Window("FluidWaveform Test",Rect(0,0,1000,500));
f = 2.collect{FluidWaveform(~drums, standalone: false)};
w.view.layout = VLayout(f[0], f[1]);
w.front;
)
// show spectrogram
~mags = Buffer(s);
FluidBufSTFT.processBlocking(s,~drums,magnitude:~mags,action:{"stft done".postln;});
@ -249,7 +266,7 @@ FluidBufPitch.processBlocking(s,~audio,features:~pitch_analysis,action:{"done".p
~fw.addAudioLayer(~audio,Color(1,1,1,0.5));
~fw.addIndicesLayer(~indices,~audio,Color.black);
~fw.addFeaturesLayer(~pitch_analysis,[Color.cyan,Color.magenta]);
~fw.front; // // if no buffers of any kind are passed, then you'll need to call `.front` after adding layers
~fw.front;
)
(
@ -262,7 +279,7 @@ FluidBufPitch.processBlocking(s,~audio,features:~pitch_analysis,action:{"done".p
)
// add one more
~fw.addFeaturesLayer(~pitch_analysis,[Color.cyan,Color.yellow]).front; // <<<----- notice the `.front` here to update display after adding this layer
~fw.addFeaturesLayer(~pitch_analysis,[Color.cyan,Color.yellow]).refresh; // <<<----- notice the `.refresh` here to update display after adding this layer
// check how many layers
~fw.layers.size
@ -301,4 +318,4 @@ s.waitForBoot{
~fw.front;
}
)
::
::

Loading…
Cancel
Save