This class is meant as an emulation of SCSoundFileView. last mod: 13-jan-08 sciss
Also refer to JSCView for different behaviour affecting all widgets
no-op / not working | |
block size | block sizes for decimation are ignored |
path names | (in the SoundFile object) cannot be relative. use .absolutePath instead. files must be locally accessible |
lissajou | lissajou style (style == 2) is not supported |
different behaviour | |
memory | waveform cache is read / written from harddisk (tmp folder) and not kept completely in RAM |
resolution | waveform can be displayed at full sample resolution. when zoomed out, peak + RMS are shown |
extended functionality | |
action | additional arguments for type of action and parameters |
cache | management of waveform cache |
known issues / todo | |
elasticResizeMode | not yet implemented (this is always _1_ now) |
read | can only be asynchronous. put in a Routine to wait for completion |
setData | can only be asynchronous. put in a Routine. |
data | (getter) not yet implemented |
performance | graphics update could be more efficiently buffering during scrolling |
metaAction | does not get evaluated on ctrl+mouseclick/drag |
background | offscreen image paints transparent pixels black on Linux / Sun Java SE 1.6 (therefore background_ has no effect, and selections are not visible ;-C ) |
Note: please use the abstraction layer GUI.soundFileView if possible! (see GUI)
Note: this implementation is slightly incomplete!
This gadget is a waveform display for sound files. It has facilities for zooming in time and amplitude, handles a timeline cursor and multiple selections.
In the following example, click on the "..."
button to select a soundfile from a dialog. use the slider on the right to zoom vertically and the range slider on the bottom to move and zoom horizontally:
( f = nil; w = JSCWindow( "Soundfile View", Rect( 300, 300, 770, 270 )); JSCStaticText( w, Rect( 20, 10, 40, 20 )) .align_( \right ) .string_( "Path:" ); x = JSCDragSink( w, Rect( 70, 10, 650, 20 )) .resize_( 2 ) .action_({ arg b; if( f.notNil, { f.close; f = nil; }); f = SoundFile.new; f.openRead( b.object ); GUI.useID( \swing, { f.inspect }); a.soundfile = f; a.gridOn = false; y.lo = 0; y.hi = 1; a.readWithTask( 0, f.numFrames, doneAction: { arg b; a.gridResolution = (b.soundfile.numFrames / (b.soundfile.sampleRate * 16)).max( 0.1 ); a.gridOn = true; }); // a.read( 0, f.numFrames ); // warning... }); JSCButton( w, Rect( 730, 10, 20, 20 )) .resize_( 3 ) .states_([[ "..." ]]) .action_({ arg b; SwingDialog.getPaths({ arg paths; x.object = paths.first; x.doAction; }, maxSize: 1 ); }); a = JSCSoundFileView( w, Rect( 20, 40, 700, 180 )) .resize_( 5 ); a.elasticMode = 1; a.timeCursorOn = true; a.timeCursorColor = Color.red; // a.timeCursorPosition = 2500; y = JSCRangeSlider( w, Rect( 20, 230, 700, 20 )) .resize_( 8 ) .action_({ arg b; a.zoomToFrac( b.range.max( a.bounds.width / a.numFrames.max( 1 ))); if( b.range < 1, { a.scrollTo( b.lo / (1 - b.range) )}); // stupid scrollTo definition }); JSCSlider( w, Rect( 730, 40, 20, 180 )) .resize_( 6 ) .value_( 0.5 ) .action_({ arg b; a.yZoom = b.value.linexp( 0, 1, 0.02, 50.0 ); }); w.front; )
// x.object = "sounds/a11wlk01.wav".absolutePath; // ! absolutePath // x.doAction; // spills out a warning as SwingOSC necessarily reads asynchronous z = SoundFile.openRead( "sounds/a11wlk01.wav".absolutePath ); a.readFile( z, 0, z.numFrames ); // <soundFile>, <startFrame>, <numFrames>, <block>, <close> z.isOpen; // --> readFile closes the sound file by default // reading again from the previously used sound file a.read( 11025, 22050 ); // <startFrame>, <numFrames>, <block>, <close> // asynchronous read z = SoundFile.openRead( "sounds/SinedPink.aiff".absolutePath ); // <soundFile>, <startFrame>, <numFrames>, <block>, <doneAction> a.readFileWithTask( z, 0, -1, doneAction: { "Yippie!".postln }); // -1 is short for "all frames" // reading again asynchronously from the previously used sound file a.readWithTask( 500, -1, doneAction: { "Once more!".postln }); // <startFrame>, <numFrames>, <block>, <doneAction> // passing in custom data (this doesn't work with SCSoundFileView - why?) a.setData( Signal.chebyFill( 44100, [ 0.3, -0.8, 1.1, -0.95, -0.4 ]));
// waveform style: 0 = normal, 1 = all channels overlayed // (you have to load a stereo or multichannel file to see the difference!) a.style = 1; a.style = 0; a.drawsWaveForm = false; a.drawsWaveForm = true; a.waveColors = [ Color.white ]; // for channel 1 (mono) a.waveColors = [ Color.red, Color.green ]; // for channels 1 and 2 (stereo) etc. a.background = Color.white; a.background = Color.black; // turn on/off time grid and set its resolution a.gridOn = false; a.gridOn = true; a.gridResolution = 0.2; // every 200 milliseconds a.gridColor = Color.green( 0.5 );
// place timeline cursor a.timeCursorPosition = 66666; // in sample frames a.timeCursorOn = false; a.timeCursorOn = true; a.timeCursorColor = Color( 0.5, 0.0, 1.0 );
// make selections a.setSelectionStart( 0, 0 ); a.setSelectionSize( 0, 44100 ); a.setSelectionStart( 1, 88200 ); a.setSelectionSize( 1, 44100 ); a.setSelectionStart( 63, 66150 ); // max. selection index is 64 a.setSelectionSize( 63, 11025 ); a.setSelectionColor( 0, Color.red( alpha: 0.5 )); // selections from the user a.currentSelection = 1; // index of the selection which the user edits with the mouse; index is 0 ... 63 a.setEditableSelectionStart( 1, false ); // now only the selection #1 stop point may be edited a.setEditableSelectionStart( 1, true ); a.setEditableSelectionSize( 1, false ); // now the selection #1 size may not be altered a.setEditableSelectionSize( 1, true );
Note: mouse modifiers for making selections:
Ctrl+Drag
: move cursor (but don't touch selection)Shift+Drag
: extend selection (but don't touch cursor)Meta+Click
: select allCtrl+Shift+Drag
: move selection (but don't touch cursor) // XXX these modifiers should be changed?
To track user activity, you can assign a Function to 'action':
// what == \cursor or \selection // for what == \cursor -> params = [ <newPosition> ] // for what == \selection -> params = [ <index>, <newStart>, <newSize> ] a.action = { arg butt, what ... params; ([ what ] ++ params).postln };
(note that the cocoa variant SCSoundFileView
does not provide the additional arguments!!)
a.zoomAllOut; fork { 200.do({ arg i; a.zoomToFrac( ((i+1)/200).pow( 4 )); 0.05.wait; }); }; a.zoom( 0.5 ); // relative (zoom in factor 2) a.zoom( 2.0 ); // relative (zoom out factor 2) a.setSelection( 4, [ 20000, 40000 ]); a.zoomSelection( 4 ); // zoom to one selection fork { 200.do({ arg i; a.yZoom = ((i+1)/100); 0.05.wait; }); }; // y-zoom
In order to speed up waveform calculation, cache management can be globally enabled and configured:
JSCSoundFileView.cacheFolder = "/tmp/swingOSC"; // where waveform cache is to be stored JSCSoundFileView.cacheCapacity = 30; // maximum total size of cache files in megabytes JSCSoundFileView.cacheActive = true; // to activate cache
Note: unless you set your folder to a system temporary folder (like "/tmp"
), the cache will not be purged after rebooting the computer. If you wish to erase your cache, set the capacity to 0
after setting the folder.