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_ {
@ -203,42 +195,54 @@ FluidPlotter : FluidViewer {
} }
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,{
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; 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;
fork({
var path = "%%_%_FluidWaveform.wav".format(PathName.tmp,Date.localtime.stamp,UniqueID.next); var path = "%%_%_FluidWaveform.wav".format(PathName.tmp,Date.localtime.stamp,UniqueID.next);
var sfv; var sfv = SoundFileView();
audioBuffer.write(path,"wav");
audioBuffer.server.sync;
sfv = SoundFileView(win,bounds);
sfv.peakColor_(waveformColor); sfv.peakColor_(waveformColor);
sfv.drawsBoundingLines_(false); sfv.drawsBoundingLines_(false);
sfv.rmsColor_(Color.clear); sfv.rmsColor_(Color.clear);
sfv.background_(Color.clear); sfv.background_(Color.clear);
sfv.readFile(SoundFile(path));
sfv.gridOn_(false); sfv.gridOn_(false);
File.delete(path); forkIfNeeded({
},AppClock); audioBuffer.write(path,"wav");
^audioBuffer.server; audioBuffer.server.sync;
sfv.readFile(SoundFile(path));
File.delete(path)
}, AppClock);
^sfv
} }
} }
@ -63,29 +52,43 @@ 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();
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;
};
if(audioBuffer.notNil,{ userView = UserView();
fork({
indicesBuffer.numChannels.switch( forkIfNeeded({
1,{ indicesBuffer.loadToFloatArray(action: {
indicesBuffer.loadToFloatArray(action:{ arg v;
arg slices_fa; slices_fa = v;
UserView(win,bounds) condition.signalOne;
.drawFunc_({ });
condition.wait { slices_fa.notNil };
userView.drawFunc = numChannels.switch(
1, {{
arg viewport;
var bounds = viewport.bounds;
Pen.width_(lineWidth); Pen.width_(lineWidth);
slices_fa.do{ slices_fa.do{
arg start_samp; arg start_samp;
@ -93,37 +96,24 @@ FluidWaveformIndicesLayer : FluidViewer {
Pen.line(Point(x,0),Point(x,bounds.height)); Pen.line(Point(x,0),Point(x,bounds.height));
Pen.color_(color); Pen.color_(color);
Pen.stroke; Pen.stroke;
}; }};
});
});
}, },
2,{ 2, {{
indicesBuffer.loadToFloatArray(action:{ arg viewport;
arg slices_fa; var bounds = viewport.bounds;
slices_fa = slices_fa.clump(2);
UserView(win,bounds)
.drawFunc_({
Pen.width_(lineWidth); Pen.width_(lineWidth);
slices_fa.do{ slices_fa.clump(2).do{
arg arr; arg arr;
var start = arr[0].linlin(0,audioBuffer.numFrames,0,bounds.width); var start = arr[0].linlin(0,audioBuffer.numFrames,0,bounds.width);
var end = arr[1].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.addRect(Rect(start,0,end-start,bounds.height));
Pen.color_(color.alpha_(0.25)); Pen.color_(color.alpha_(0.25));
Pen.fill; Pen.fill;
}; }};
});
});
},{
Error("% indicesBuffer must have either 1 nor 2 channels.".format(this.class)).throw;
} }
); );
},AppClock); }, AppClock);
^indicesBuffer.server; ^userView;
},{
Error("% In order to display an indicesBuffer an audioBuffer must be included.".format(this.class)).throw;
});
} }
} }
@ -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,11 +178,10 @@ 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) Pen.width = lineWidth;
.drawFunc_({
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;
@ -200,9 +191,9 @@ FluidWaveformFeaturesLayer : FluidViewer {
Pen.stroke; Pen.stroke;
}); });
}); });
},AppClock); }, AppClock);
});
^featuresBuffer.server; ^userView;
} }
} }
@ -211,55 +202,25 @@ 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 {
arg imageBuffer_, imageColorScheme_ = 0, imageColorScaling_ = 0, imageAlpha_ = 1;
imageBuffer = imageBuffer_;
imageColorScheme = imageColorScheme_;
imageColorScaling = imageColorScaling_;
imageAlpha = imageAlpha_;
} }
draw { draw {
arg win, bounds; var colors = this.prGetColorsFromScheme(imageColorScheme);
var colors; var condition = CondVar();
var vals = nil;
if(imageColorScheme.isKindOf(Color),{ var userView = UserView();
// "imageColorScheme is a kind of Color".postln;
colors = 256.collect{ forkIfNeeded({
arg i; var img = Image(imageBuffer.numFrames, imageBuffer.numChannels);
Color(imageColorScheme.red,imageColorScheme.green,imageColorScheme.blue,i.linlin(0,255,0.0,1.0)); imageBuffer.loadToFloatArray(action: {
}; arg v;
},{ vals = v;
imageColorScheme.switch( condition.signalOne;
0,{
colors = this.loadColorFile("CET-L02");
},
1,{
colors = this.loadColorFile("CET-L16");
},
2,{
colors = this.loadColorFile("CET-L08");
},
3,{
colors = this.loadColorFile("CET-L03");
},
4,{
colors = this.loadColorFile("CET-L04");
},
{
"% imageColorScheme: % is not valid.".format(thisMethod,imageColorScheme).warn;
}
);
}); });
condition.wait { vals.notNil };
imageBuffer.loadToFloatArray(action:{
arg vals;
fork({
var img = Image(imageBuffer.numFrames,imageBuffer.numChannels);
imageColorScaling.switch( imageColorScaling.switch(
FluidWaveform.lin,{ FluidWaveform.lin,{
@ -270,27 +231,27 @@ FluidWaveformImageLayer {
FluidWaveform.log,{ FluidWaveform.log,{
vals = (vals + 1e-6).log; vals = (vals + 1e-6).log;
vals = vals.linlin(vals.minItem,vals.maxItem,0.0,255.0).asInteger; vals = vals.linlin(vals.minItem,vals.maxItem,0.0,255.0).asInteger;
// vals.postln;
}, },
{ {
"% colorScaling argument % is invalid.".format(thisMethod,imageColorScaling).warn; "% colorScaling argument % is invalid.".format(thisMethod,imageColorScaling).warn;
} }
); );
// colors.postln;
vals.do{ vals.do{
arg val, index; arg val, index;
img.setColor(colors[val], index.div(imageBuffer.numChannels), imageBuffer.numChannels - 1 - index.mod(imageBuffer.numChannels)); img.setColor(colors[val], index.div(imageBuffer.numChannels), imageBuffer.numChannels - 1 - index.mod(imageBuffer.numChannels));
}; };
UserView(win,bounds) userView.drawFunc = {
.drawFunc_{ arg viewport;
img.drawInRect(Rect(0,0,bounds.width,bounds.height),fraction:imageAlpha); var bounds = viewport.bounds;
img.drawInRect(
Rect(0, 0, bounds.width, bounds.height),
fraction: imageAlpha
);
}; };
},AppClock); }, AppClock);
}); ^userView;
^imageBuffer.server;
} }
loadColorFile { loadColorFile {
@ -300,112 +261,122 @@ FluidWaveformImageLayer {
Color.fromArray(row); Color.fromArray(row);
} }
} }
prGetColorsFromScheme {
arg imageColorScheme;
var colors;
if(imageColorScheme.isKindOf(Color),{
colors = 256.collect{
arg i;
imageColorScheme.copy.alpha_(i.linlin(0,255,0.0,1.0));
};
},{
imageColorScheme.switch(
0,{
colors = this.loadColorFile("CET-L02");
},
1,{
colors = this.loadColorFile("CET-L16");
},
2,{
colors = this.loadColorFile("CET-L08");
},
3,{
colors = this.loadColorFile("CET-L03");
},
4,{
colors = this.loadColorFile("CET-L04");
},
{
"% imageColorScheme: % is not valid.".format(thisMethod,imageColorScheme).warn;
}
);
});
^colors;
}
} }
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_; layers = List.new;
waveformColor = waveformColor ? Color(*0.6.dup(3)); 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);
}); };
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,{ 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

Loading…
Cancel
Save