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

@ -24,38 +24,27 @@ FluidWaveformAudioLayer {
var audioBuffer, waveformColor; var audioBuffer, waveformColor;
*new { *new {
arg audioBuffer, waveformColor; arg audioBuffer, waveformColor(Color.gray);
^super.new.init(audioBuffer,waveformColor); ^super.newCopyArgs(audioBuffer, waveformColor);
}
init {
arg audioBuffer_, waveformColor_;
audioBuffer = audioBuffer_;
waveformColor = waveformColor_ ? Color.gray;
} }
draw { draw {
arg win, bounds; var path = "%%_%_FluidWaveform.wav".format(PathName.tmp,Date.localtime.stamp,UniqueID.next);
fork({ var sfv = SoundFileView();
var path = "%%_%_FluidWaveform.wav".format(PathName.tmp,Date.localtime.stamp,UniqueID.next); sfv.peakColor_(waveformColor);
var sfv; sfv.drawsBoundingLines_(false);
sfv.rmsColor_(Color.clear);
sfv.background_(Color.clear);
sfv.gridOn_(false);
forkIfNeeded({
audioBuffer.write(path,"wav"); audioBuffer.write(path,"wav");
audioBuffer.server.sync; 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.readFile(SoundFile(path));
sfv.gridOn_(false); File.delete(path)
}, AppClock);
File.delete(path); ^sfv
},AppClock);
^audioBuffer.server;
} }
} }
@ -63,67 +52,68 @@ FluidWaveformIndicesLayer : FluidViewer {
var indicesBuffer, audioBuffer, color, lineWidth; var indicesBuffer, audioBuffer, color, lineWidth;
*new { *new {
arg indicesBuffer, audioBuffer, color, lineWidth = 1; arg indicesBuffer, audioBuffer, color(Color.red), lineWidth = 1;
^super.new.init(indicesBuffer, audioBuffer, color, lineWidth); ^super.newCopyArgs(indicesBuffer, audioBuffer, color, lineWidth);
}
init {
arg indicesBuffer_, audioBuffer_, color_, lineWidth_;
indicesBuffer = indicesBuffer_;
audioBuffer = audioBuffer_;
color = color_ ? Color.red;
lineWidth = lineWidth_;
} }
draw { draw {
arg win, bounds; var userView;
var condition = CondVar();
if(audioBuffer.notNil,{ var slices_fa = nil;
fork({
indicesBuffer.numChannels.switch( var numChannels = indicesBuffer.numChannels;
1,{ if ([1, 2].includes(numChannels).not) {
indicesBuffer.loadToFloatArray(action:{ Error(
arg slices_fa; "% indicesBuffer must have either 1 or 2 channels."
UserView(win,bounds) .format(this.class)
.drawFunc_({ ).throw;
Pen.width_(lineWidth); };
slices_fa.do{ if (audioBuffer.isNil) {
arg start_samp; Error(
var x = start_samp.linlin(0,audioBuffer.numFrames,0,bounds.width); "% In order to display an indicesBuffer an audioBuffer must be included."
Pen.line(Point(x,0),Point(x,bounds.height)); .format(this.class)
Pen.color_(color); ).throw;
Pen.stroke; };
};
}); userView = UserView();
});
}, forkIfNeeded({
2,{ indicesBuffer.loadToFloatArray(action: {
indicesBuffer.loadToFloatArray(action:{ arg v;
arg slices_fa; slices_fa = v;
slices_fa = slices_fa.clump(2); condition.signalOne;
UserView(win,bounds) });
.drawFunc_({ condition.wait { slices_fa.notNil };
Pen.width_(lineWidth);
slices_fa.do{ userView.drawFunc = numChannels.switch(
arg arr; 1, {{
var start = arr[0].linlin(0,audioBuffer.numFrames,0,bounds.width); arg viewport;
var end = arr[1].linlin(0,audioBuffer.numFrames,0,bounds.width); var bounds = viewport.bounds;
Pen.addRect(Rect(start,0,end-start,bounds.height)); Pen.width_(lineWidth);
Pen.color_(color.alpha_(0.25)); slices_fa.do{
Pen.fill; 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;
},{ }};
Error("% indicesBuffer must have either 1 nor 2 channels.".format(this.class)).throw; },
} 2, {{
); arg viewport;
},AppClock); var bounds = viewport.bounds;
^indicesBuffer.server; Pen.width_(lineWidth);
},{ slices_fa.clump(2).do{
Error("% In order to display an indicesBuffer an audioBuffer must be included.".format(this.class)).throw; 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 { *new {
arg featuresBuffer, colors, stackFeatures = false, normalizeFeaturesIndependently = true, lineWidth = 1; arg featuresBuffer, colors, stackFeatures = false, normalizeFeaturesIndependently = true, lineWidth = 1;
^super.new.init(featuresBuffer,colors,stackFeatures,normalizeFeaturesIndependently,lineWidth); colors = colors ?? { this.createCatColors };
}
init {
arg featuresBuffer_, colors_, stackFeatures_ = false, normalizeFeaturesIndependently_ = true, lineWidth_ = 1;
featuresBuffer = featuresBuffer_;
normalizeFeaturesIndependently = normalizeFeaturesIndependently_;
stackFeatures = stackFeatures_;
lineWidth = lineWidth_;
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 // 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 { draw {
arg win, bounds; var userView = UserView();
var condition = CondVar();
var fa = nil;
featuresBuffer.loadToFloatArray(action:{ forkIfNeeded({
arg fa;
var minVal = 0, maxVal = 0; var minVal = 0, maxVal = 0;
var stacked_height;
if(stackFeatures,{ featuresBuffer.loadToFloatArray(action:{
stacked_height = bounds.height / featuresBuffer.numChannels; arg v;
fa = v;
condition.signalOne
}); });
condition.wait { fa.notNil };
if(normalizeFeaturesIndependently.not,{ if(normalizeFeaturesIndependently.not,{
minVal = fa.minItem; minVal = fa.minItem;
@ -167,7 +152,14 @@ FluidWaveformFeaturesLayer : FluidViewer {
fa = fa.clump(featuresBuffer.numChannels).flop; 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({ fa.do({
arg channel, channel_i; arg channel, channel_i;
var maxy;// a lower value; var maxy;// a lower value;
@ -186,23 +178,22 @@ FluidWaveformFeaturesLayer : FluidViewer {
maxVal = channel.maxItem; maxVal = channel.maxItem;
}); });
channel = channel.resamp1(bounds.width).linlin(minVal,maxVal,miny,maxy); channel = channel.resamp1(bounds.width)
.linlin(minVal,maxVal,miny,maxy);
UserView(win,bounds)
.drawFunc_({ Pen.width = lineWidth;
Pen.width_(lineWidth); Pen.moveTo(Point(0,channel[0]));
Pen.moveTo(Point(0,channel[0])); channel[1..].do{
channel[1..].do{ arg val, i;
arg val, i; Pen.lineTo(Point(i+1,val));
Pen.lineTo(Point(i+1,val)); };
}; Pen.color_(colors[channel_i % colors.size]);
Pen.color_(colors[channel_i % colors.size]); Pen.stroke;
Pen.stroke;
});
}); });
},AppClock); });
}); }, AppClock);
^featuresBuffer.server;
^userView;
} }
} }
@ -211,27 +202,73 @@ FluidWaveformImageLayer {
*new { *new {
arg imageBuffer, imageColorScheme = 0, imageColorScaling = 0, imageAlpha = 1; arg imageBuffer, imageColorScheme = 0, imageColorScaling = 0, imageAlpha = 1;
^super.new.init(imageBuffer,imageColorScheme,imageColorScaling,imageAlpha); ^super.newCopyArgs(
imageBuffer, imageColorScheme, imageColorScaling, imageAlpha
);
} }
init { draw {
arg imageBuffer_, imageColorScheme_ = 0, imageColorScaling_ = 0, imageAlpha_ = 1; 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_; imageColorScaling.switch(
imageColorScheme = imageColorScheme_; FluidWaveform.lin,{
imageColorScaling = imageColorScaling_; var minItem = vals.minItem;
imageAlpha = imageAlpha_; 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 { loadColorFile {
arg win, bounds; arg filename;
var colors; ^CSVFileReader.readInterpret(FluidFilesPath("../color-schemes/%.csv".format(filename))).collect{
arg row;
Color.fromArray(row);
}
}
prGetColorsFromScheme {
arg imageColorScheme;
var colors;
if(imageColorScheme.isKindOf(Color),{ if(imageColorScheme.isKindOf(Color),{
// "imageColorScheme is a kind of Color".postln;
colors = 256.collect{ colors = 256.collect{
arg i; 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( imageColorScheme.switch(
@ -256,156 +293,90 @@ FluidWaveformImageLayer {
); );
}); });
imageBuffer.loadToFloatArray(action:{ ^colors;
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);
}
} }
} }
FluidWaveform : FluidViewer { FluidWaveform : FluidViewer {
classvar <lin = 0, <log = 1; classvar <lin = 0, <log = 1;
var <win, bounds, display_bounds, <layers; var <parent, bounds, standalone, view, <layers;
*new { *new {
arg audioBuffer, indicesBuffer, featuresBuffer, parent, bounds, lineWidth = 1, waveformColor, stackFeatures = false, imageBuffer, imageColorScheme = 0, imageAlpha = 1, normalizeFeaturesIndependently = true, imageColorScaling = 0; arg audioBuffer, indicesBuffer, featuresBuffer,
^super.new.init(audioBuffer,indicesBuffer, featuresBuffer, parent, bounds, lineWidth, waveformColor,stackFeatures,imageBuffer,imageColorScheme,imageAlpha,normalizeFeaturesIndependently,imageColorScaling); 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 { init {
arg audio_buf, slices_buf, feature_buf, parent_, bounds_, lineWidth = 1, waveformColor,stackFeatures = false, imageBuffer, imageColorScheme = 0, imageAlpha = 1, normalizeFeaturesIndependently = true, imageColorScaling = 0; arg audio_buf, slices_buf, feature_buf,
layers = List.new; lineWidth = 1, waveformColor, stackFeatures = false,
imageBuffer, imageColorScheme = 0, imageAlpha = 1,
fork({ normalizeFeaturesIndependently = true, imageColorScaling = 0;
var plotImmediately = false; var plotImmediately = false;
bounds = bounds_;
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 = Rect(0,0,imageBuffer.numFrames,imageBuffer.numChannels);
}); };
plotImmediately = true;
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;
});
if(audio_buf.notNil,{ if (audio_buf.notNil) {
this.addAudioLayer(audio_buf,waveformColor); this.addAudioLayer(audio_buf, waveformColor);
audio_buf.server.sync; plotImmediately = true;
plotImmediately = true; };
});
if(feature_buf.notNil,{ if (feature_buf.notNil) {
this.addFeaturesLayer(feature_buf,this.createCatColors,stackFeatures,normalizeFeaturesIndependently,lineWidth); this.addFeaturesLayer(feature_buf, this.createCatColors, stackFeatures, normalizeFeaturesIndependently, lineWidth);
feature_buf.server.sync; plotImmediately = true;
plotImmediately = true; };
});
if(slices_buf.notNil,{ if (slices_buf.notNil) {
this.addIndicesLayer(slices_buf,audio_buf,Color.red,lineWidth); this.addIndicesLayer(slices_buf, audio_buf, Color.red, lineWidth);
slices_buf.server.sync; plotImmediately = true;
plotImmediately = true; };
});
if(plotImmediately,{this.front;}); if (plotImmediately) { this.front };
},AppClock);
} }
addImageLayer { addImageLayer {
arg imageBuffer, imageColorScheme = 0, imageColorScaling = 0, imageAlpha = 1; arg imageBuffer, imageColorScheme = 0, imageColorScaling = 0, imageAlpha = 1;
var l = FluidWaveformImageLayer(imageBuffer,imageColorScheme,imageColorScaling,imageAlpha); var l = FluidWaveformImageLayer(imageBuffer, imageColorScheme, imageColorScaling, imageAlpha);
// l.postln;
layers.add(l); layers.add(l);
// layers.postln;
// l.draw(win,display_bounds);
} }
addAudioLayer { addAudioLayer {
arg audioBuffer, waveformColor; arg audioBuffer, waveformColor;
var l = FluidWaveformAudioLayer(audioBuffer,waveformColor); var l = FluidWaveformAudioLayer(audioBuffer, waveformColor);
// l.postln;
layers.add(l); layers.add(l);
// layers.postln;
// l.draw(win,display_bounds);
} }
addIndicesLayer { addIndicesLayer {
arg indicesBuffer, audioBuffer, color, lineWidth = 1; arg indicesBuffer, audioBuffer, color, lineWidth = 1;
var l = FluidWaveformIndicesLayer(indicesBuffer,audioBuffer,color,lineWidth); var l = FluidWaveformIndicesLayer(indicesBuffer,audioBuffer,color,lineWidth);
// l.postln;
layers.add(l); layers.add(l);
// layers.postln;
// l.draw(win,display_bounds);
} }
addFeaturesLayer { addFeaturesLayer {
arg featuresBuffer, colors, stackFeatures = false, normalizeFeaturesIndependently = true, lineWidth = 1; arg featuresBuffer, colors, stackFeatures = false, normalizeFeaturesIndependently = true, lineWidth = 1;
var l = FluidWaveformFeaturesLayer(featuresBuffer,colors,stackFeatures,normalizeFeaturesIndependently,lineWidth); var l = FluidWaveformFeaturesLayer(featuresBuffer,colors,stackFeatures,normalizeFeaturesIndependently, lineWidth);
// l.postln;
layers.add(l); layers.add(l);
// layers.postln;
// l.draw(win,display_bounds);
} }
addLayer { addLayer {
@ -414,26 +385,50 @@ FluidWaveform : FluidViewer {
} }
front { front {
this.prMakeView;
fork({ fork({
this.refresh;
if (standalone) { parent.front };
}, AppClock);
}
UserView(win,display_bounds) // defer({}, nil) forks if not already running on the AppClock
.drawFunc_{ refresh {
Pen.fillColor_(Color.white); forkIfNeeded({
Pen.addRect(Rect(0,0,bounds.width,bounds.height)); var noView = if (view.isNil) { true } { view.isClosed };
Pen.fill; if (noView) { this.prMakeView };
}; view.removeAll;
view.layout = StackLayout().mode_(\stackAll);
layers.do{ layers.do {
arg layer; arg layer, n;
// layer.postln; var layerView;
layer.draw(win,display_bounds).sync; layerView = layer.draw;
view.layout.add(layerView);
view.layout.index = view.layout.index + 1;
}; };
win.front; view.refresh;
},AppClock); }, AppClock);
} }
close { 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 Creates a new instance of FluidPlotter
ARGUMENT:: parent 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 ARGUMENT:: bounds
Where to show the FluidPlotter, either within the parent or on the screen (if no parent is passed). 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 ARGUMENT:: ymax
Maximum of the Y range to display. Default is 1. 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:: returns::
An instance of FluidPlotter 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); },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 // you can make an empty one and then set the dict later
( (
Window.closeAll; 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. 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 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 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. 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 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. 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. returns:: A new instance of FluidWaveform.
METHOD:: lin METHOD:: lin
@ -109,6 +112,9 @@ See this argument in the class method 'new' above.
ARGUMENT:: normalizeFeaturesIndependently ARGUMENT:: normalizeFeaturesIndependently
See this argument in the class method 'new' above. See this argument in the class method 'new' above.
ARGUMENT:: lineWidth
See this argument in the class method 'new' above.
METHOD:: addImageLayer METHOD:: addImageLayer
Add a grapic layer that shows an image derived from a buffer. 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 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. 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 METHOD:: close
Close the FluidWaveform window. If parent is not STRONG::nil::, this method will close the parent window. 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. 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; 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 // show spectrogram
~mags = Buffer(s); ~mags = Buffer(s);
FluidBufSTFT.processBlocking(s,~drums,magnitude:~mags,action:{"stft done".postln;}); 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.addAudioLayer(~audio,Color(1,1,1,0.5));
~fw.addIndicesLayer(~indices,~audio,Color.black); ~fw.addIndicesLayer(~indices,~audio,Color.black);
~fw.addFeaturesLayer(~pitch_analysis,[Color.cyan,Color.magenta]); ~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 // 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 // check how many layers
~fw.layers.size ~fw.layers.size
@ -301,4 +318,4 @@ s.waitForBoot{
~fw.front; ~fw.front;
} }
) )
:: ::

Loading…
Cancel
Save