SwingOSC – Java-based GUI classes

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 sizeblock sizes for decimation are ignored
path names(in the SoundFile object) cannot be relative. use .absolutePath instead. files must be locally accessible
lissajoulissajou style (style == 2) is not supported
different behaviour
memorywaveform cache is read / written from harddisk (tmp folder) and not kept completely in RAM
resolutionwaveform can be displayed at full sample resolution. when zoomed out, peak + RMS are shown
extended functionality
actionadditional arguments for type of action and parameters
cachemanagement of waveform cache
known issues / todo
elasticResizeModenot yet implemented (this is always _1_ now)
readcan only be asynchronous. put in a Routine to wait for completion
setDatacan only be asynchronous. put in a Routine.
data(getter) not yet implemented
performancegraphics update could be more efficiently buffering during scrolling
metaActiondoes not get evaluated on ctrl+mouseclick/drag
backgroundoffscreen image paints transparent pixels black on Linux / Sun Java SE 1.6 (therefore background_ has no effect, and selections are not visible ;-C )

 

JSCSoundFileView

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;
)

 

Setting Waveform Data

// 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 ]));

 

Style Customization

// 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 );

 

Cursor

// place timeline cursor
    a.timeCursorPosition = 66666;   // in sample frames
    a.timeCursorOn = false;
    a.timeCursorOn = true;
    a.timeCursorColor = Color( 0.5, 0.0, 1.0 );

 

Selections

// 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:

 

Tracking User Interaction

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!!)

Zooming

    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

 

Managing Waveform Caching (SwingOSC only)

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.