## SwingOSC – Java-based GUI classes

This class is meant as an emulation of SCSoundFileView. last mod: 17-jul-09 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
timeCursorEditable_ | additional method
viewFrames_ | additional method
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 )

# JSCSoundFileView

Note: please use the abstraction layer SoundFileView if possible!

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:

- `Ctrl+Drag` : move cursor (but don't touch selection)
- `Shift+Drag` : extend selection (but don't touch cursor)
- `Meta+Click` : select all
- `Ctrl+Shift+Drag` : move selection (but don't touch cursor) `// XXX these modifiers should be changed?`

## 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.