Package de.sciss.swingosc

Source Code of de.sciss.swingosc.SoundFileView$Decimator

/*
*  SoundFileView.java
*  SwingOSC
*
*  Copyright (c) 2005-2011 Hanns Holger Rutz. All rights reserved.
*
*  This software is free software; you can redistribute it and/or
*  modify it under the terms of the GNU General Public License
*  as published by the Free Software Foundation; either
*  version 2, june 1991 of the License, or (at your option) any later version.
*
*  This software is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
*  General Public License for more details.
*
*  You should have received a copy of the GNU General Public
*  License (gpl.txt) along with this software; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*
*  For further information, please contact Hanns Holger Rutz at
*  contact@sciss.de
*
*
*  Changelog:
*    18-Nov-06  created
*    28-Jul-07  added cache support
*    26-Aug-08  uses java default tmp dir instead of user prefs tmp dir
*/
package de.sciss.swingosc;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
//import java.awt.Image;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.TexturePaint;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
//import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
//import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
//import java.awt.image.VolatileImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.event.MouseInputAdapter;

//import de.sciss.app.AbstractApplication;
import de.sciss.app.BasicEvent;
import de.sciss.app.EventManager;
//import de.sciss.eisenkraut.io.AudioStake;
//import de.sciss.eisenkraut.io.InterleavedAudioStake;
//import de.sciss.eisenkraut.io.MultiMappedAudioStake;
//import de.sciss.eisenkraut.io.PrefCacheManager;
//import de.sciss.eisenkraut.io.DecimatedTrail.AudioFileCacheInfo;
import de.sciss.gui.AquaFocusBorder;
import de.sciss.io.AudioFile;
import de.sciss.io.AudioFileCacheInfo;
import de.sciss.io.AudioFileDescr;
import de.sciss.io.CacheManager;
//import de.sciss.io.IOUtil;
import de.sciss.io.InterleavedStreamFile;
import de.sciss.io.Span;
import de.sciss.util.MutableInt;
import de.sciss.util.MutableLong;

/**
@version  0.61, 17-Oct-08
@author    Hanns Holger Rutz
*/
public class SoundFileView
extends JComponent
implements FocusListener
{
  public static final int      MODEL_PCM        = 0;
  public static final int      MODEL_HALFWAVE_PEAKRMS  = 1;
  public static final int      MODEL_MEDIAN      = 2;
  public static final int      MODEL_FULLWAVE_PEAKRMS  = 3;

  private int            decimChannels    = 0;

  private static final Color    colrFg        = new Color( 0xFF, 0xFF, 0x00, 0xFF );
//  private Color          colrFg2        = colrFg;
  private static Color      colrFg2        = new Color( 0xAA, 0xAA, 0x00, 0xAA );
  private Color[]          colrWave      = new Color[ 0 ];
  private Color[]          colrWave2      = new Color[ 0 ];
  private int           recentWidth      = -1;
  private int           recentHeight    = -1;
//  private float          xZoom        = 1.0f;
  protected Span          viewSpan      = new Span();
  private float          yZoom        = 1.0f;
  private boolean          overlay        = false;
  private boolean          lissajou      = false;
 
  private int            style        = 0;

  private boolean          gridOn        = true;
  private Color          colrGrid      = Color.blue;
  private long          gridOffset      = 0;
  private float          gridResolution    = 1.0f;
  protected boolean        timeCursorEditable  = true;
  protected boolean        timeCursorOn    = false;
  private Color          colrTimeCursor    = Color.blue;
  protected long          timeCursorPos    = 0;
  private boolean          waveOn        = true;
  private double          sampleRate      = 44100.0;
//  private int            decimation      = 64;
 
  private final Line2D      shpCursor      = new Line2D.Float();
  private final GeneralPath    shpGrid        = new GeneralPath();

  // ------- borrowing from Eisenkraut -------
 
  private final int[]        decimations      = { 8, 12, 16 };
  protected final DecimationHelp[] decimHelps;
  private final int        SUBNUM;
  private final int        MAXSHIFT;
  protected final int        MAXCOARSE;
  private final long        MAXMASK;
  private final int        MAXCEILADD;
  private final Decimator      decimator;
  private final Object      fileSync      = new Object();
  protected EventManager      asyncManager    = null;
  protected final Object      bufSync        = new Object();
  protected Thread        threadAsync      = null;
  protected boolean        keepAsyncRunning  = false;
    private AudioFile[]        tempFAsync      = null// lazy

    protected float[][]        tmpBuf        = null// lazy
  private final int        tmpBufSize;
  protected float[][]        tmpBuf2        = null// lazy
  private final int        tmpBufSize2;
  protected int          fullChannels;

  private final List        busyList      = new ArrayList();
  private final int        model;
  private final int        modelChannels;

  private static final Paint    pntBusy;
  private static final int[]    busyPixels      = { 0xFFCBCBCB, 0xFFC0C0C0, 0xFFA8A8A8, 0xFFE6E6E6, 0xFFB2B2B2, 0xFFCACACA,
                              0xFFB1B1B1, 0xFFD5D5D5, 0xFFC0C0C0 };
//  private static final Paint    pntRMS;
//  private static final int[]    rmsPixels      = { 0xFFFFFF00, 0xFFFFFF00, 0xFFFFFF00, 0xFFFFFF00, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000 };
//  private static final Stroke    strkLine      = new BasicStroke( 2.0f );
  private static final Stroke    strkLine      = new BasicStroke( 4f );

  protected AudioFile        fullScale      = null;
  private boolean          deleteFullScale    = false;
  private DecimatedStake      decimatedStake    = null;
 
//  private Insets          insets        = new Insets( 0, 0, 0, 0 );
  private int            vGap        = 1;
  private Rectangle        r          = new Rectangle();

  protected volatile float    readProgress    = 0f;
 
  // selecting
  protected final Selection[]    selections      = new Selection[ 64 ];
  protected int          selectionIndex    = 0;
  protected final Color      colrSelection    = new Color( 0x00, 0x00, 0xFF, 0x7F );
 
  // cached waveform snapshot
  protected boolean        needsImageUpdate  = true;
//  private Image          img          = null;
//  private VolatileImage      img          = null;
  private BufferedImage      img          = null;
 
  protected Span          totalSpan      = new Span();
 
  private final List        listeners      = new ArrayList();
 
  protected CacheManager      cm;
 
  static {
    BufferedImage img;
    img = new BufferedImage( 3, 3, BufferedImage.TYPE_INT_ARGB );
    img.setRGB( 0, 0, 3, 3, busyPixels, 0, 3 );
    pntBusy = new TexturePaint( img, new Rectangle( 0, 0, 3, 3 ));
//    img = new BufferedImage( 8, 1, BufferedImage.TYPE_INT_ARGB );
//    img.setRGB( 0, 0, 8, 1, rmsPixels, 0, 8 );
//    pntRMS = new TexturePaint( img, new Rectangle( 0, 0, 8, 1 ));
  }

  public SoundFileView()
  {
    super();
 
//    setOpaque( true );
    setBackground( Color.black );
    setFocusable( true );
    setBorder( new AquaFocusBorder() );
    putClientProperty( "insets", getInsets() );
   
    final MouseAdapter ma = new MouseAdapter();
    addMouseListener( ma );
    addMouseMotionListener( ma );
    addFocusListener( this );
    setCursor( new Cursor( Cursor.TEXT_CURSOR ));
   
    model    = MODEL_FULLWAVE_PEAKRMS;
    switch( model ) {
    case MODEL_HALFWAVE_PEAKRMS:
      modelChannels  = 4;
      decimator    = new HalfPeakRMSDecimator();
      break;
//    case MODEL_MEDIAN: 
//      modelChannels  = 1;
//      decimator    = new MedianDecimator();
//      break;
    case MODEL_FULLWAVE_PEAKRMS:
      modelChannels  = 3;
      decimator    = new FullPeakRMSDecimator();
      break;
    default:
      throw new IllegalArgumentException( "Model " + model );
    }
    SUBNUM    = decimations.length;  // the first 'subsample' is actually fullrate
    decimHelps  = new DecimationHelp[ SUBNUM ];
//    for( int i = 0; i < SUBNUM; i++ ) {
//      this.decimations[ i ] = new DecimationHelp( fullScale.getRate(), decimations[ i ]);
//    }
    MAXSHIFT      = decimations[ SUBNUM - 1 ];
    MAXCOARSE      = 1 << MAXSHIFT;
    MAXMASK        = -MAXCOARSE;
    MAXCEILADD      = MAXCOARSE - 1;

    tmpBufSize      = Math.max( 4096, MAXCOARSE << 1 );
    tmpBufSize2      = SUBNUM > 0 ? Math.max( 4096, tmpBufSize >> decimations[ 0 ]) : tmpBufSize;
  }
 
  public void setSelectionIndex( int idx )
  {
    ensureSelection( idx );
    selectionIndex  = idx;
  }
 
  public void setSelectionColor( int idx, Color c )
  {
    ensureSelection( idx );
    selections[ idx ].colr = c;
    efficientRepaint( selections[ idx ].span );
  }
 
  public void setSelectionSpan( int idx, long start, long stop )
  {
    setSelectionSpan( idx, new Span( start, stop ));
  }
 
  public void setSelectionSpan( int idx, Span span )
  {
    final Span dirty;
    ensureSelection( idx );
    dirty = selections[ idx ].span.isEmpty() ? span : span.union( selections[ idx ].span );
    selections[ idx ].span = span;
    efficientRepaint( dirty );
  }
 
  public void setSelectionStartEditable( int idx, boolean editable )
  {
    ensureSelection( idx );
    selections[ idx ].editableStart  = editable;
  }

  public void setSelectionSizeEditable( int idx, boolean editable )
  {
    ensureSelection( idx );
    selections[ idx ].editableSize  = editable;
  }

  public void setSelectionEditable( int idx, boolean editableStart, boolean editableSize )
  {
    ensureSelection( idx );
    selections[ idx ].editableStart  = editableStart;
    selections[ idx ].editableSize  = editableSize;
  }
   
  protected void ensureSelection( int idx )
  {
    if( (idx < 0) || (idx >= selections.length) )
      throw new IllegalArgumentException( String.valueOf( idx ));
   
    if( selections[ idx ] == null ) {
      selections[ idx ] = new Selection();
    }
  }
 
  public void setStyle( int style )
  {
    this.style      = style;
    overlay        = style > 0;
    lissajou      = style == 2;
    needsImageUpdate  = true;
    repaint();
  }
 
  public int getStyle()
  {
    return style;
  }
 
//  public void setXZoom( float f )
//  {
//    xZoom    = f;
//    recentWidth  = -1;  // triggers recalc
//    repaint();
//  }

  public void setYZoom( float f )
  {
    yZoom        = f;
    needsImageUpdate  = true;
    repaint();
  }
 
//  public float getXZoom()
//  {
//    return xZoom;
//  }

  public float getYZoom()
  {
    return yZoom;
  }
 
  public void setViewSpan( Span span )
  {
    viewSpan      = span;
    needsImageUpdate  = true;
    repaint();
  }

  public void setViewSpan( long start, long stop )
  {
    setViewSpan( new Span( start, stop ));
  }
 
  public void setWaveColor( Color c )
  {
    final Color[] cs = new Color[ fullChannels ];
    for( int i = 0; i < cs.length; i++ ) {
      cs[ i ] = c;
    }
    setWaveColors( cs );
  }

  // sync: call in event thread only
  public void setWaveColors( Color[] c )
  {
    colrWave = c;
    recalcDarkColors();
    if( waveOn ) {
      needsImageUpdate = true;
      repaint();
    }
  }
 
  public void setBackground( Color c )
  {
    super.setBackground( c );
    recalcDarkColors();
//    needsImageUpdate  = true;
  }
 
  private void recalcDarkColors()
  {
    colrWave2 = new Color[ colrWave.length ];
    final Color colrBg = getBackground();
    Color colrNorm;
    for( int i = 0; i < colrWave.length; i++ ) {
      colrNorm = colrWave[ i ];
      colrWave2[ i ] = new Color( ((colrNorm.getRed() << 1) + colrBg.getRed()) / 3,
                      ((colrNorm.getGreen() << 1) + colrBg.getGreen()) / 3,
                      ((colrNorm.getBlue() << 1) + colrBg.getBlue()) / 3,
                      ((colrNorm.getAlpha() << 1) + colrBg.getAlpha()) / 3 );
    }
  }
 
  public void setObjWaveColors( Object[] o )
  {
    final Color[] c = new Color[ o.length ];
   
    for( int i = 0; i < o.length; i++ ) {
      c[ i ] = (Color) o[ i ];
    }
   
    setWaveColors( c );
  }
 
  public void setGridColor( Color c )
  {
    colrGrid  = c;
    if( gridOn ) {
      needsImageUpdate = true;
      repaint();
    }
  }
 
  public void setGridPainted( boolean onOff )
  {
    if( onOff != gridOn ) {
      gridOn  = onOff;
      needsImageUpdate = true;
      repaint();
    }
  }
 
  public void setGridResolution( float res )
  {
    if( res != gridResolution ) {
      gridResolution  = res;
      if( gridOn ) {
        needsImageUpdate = true;
        repaint();
      }
    }
  }
 
  public void setTimeCursorColor( Color c )
  {
    colrTimeCursor  = c;
    if( timeCursorOn ) {
      efficientRepaint( new Span( timeCursorPos, timeCursorPos ));
    }
  }

  public void setTimeCursorEditable( boolean onOff )
  {
    timeCursorEditable = onOff;
  }
 
  public void setTimeCursorPainted( boolean onOff )
  {
    if( onOff != timeCursorOn ) {
      timeCursorOn  = onOff;
      efficientRepaint( new Span( timeCursorPos, timeCursorPos ));
    }
  }

  public void setTimeCursorPosition( long frame )
  {
    if( frame != timeCursorPos ) {
      final Span dirty = new Span( Math.min( frame, timeCursorPos ), Math.max( frame, timeCursorPos ));
      timeCursorPos = frame;
      if( timeCursorOn ) {
        efficientRepaint( dirty );
      }
    }
  }

  public void setWavePainted( boolean onOff )
  {
    if( onOff != waveOn ) {
      waveOn        = onOff;
      needsImageUpdate   = true;
      repaint();
    }
  }
 
  public void setCacheManager( CacheManager cm )
  {
    this.cm  = cm;
  }

  protected void subsampleWrite( float[][] inBuf, float[][] outBuf, DecimatedStake das, int len, AudioFile cacheAF )
  throws IOException
  {
    int  decim;
 
    if( SUBNUM < 1 ) return;
   
    decim      = decimHelps[ 0 ].shift;
    // calculate first decimation from fullrate PCM
    len        >>= decim;
    if( inBuf != null ) {
      decimator.decimatePCM( inBuf, outBuf, 0, len, 1 << decim );
      das.continueWrite( 0, outBuf, 0, len );
      if( cacheAF != null ) {
        cacheAF.writeFrames( outBuf, 0, len );
//        cacheOff += len;
      }
    }

    subsampleWrite2( outBuf, das, len );

//    // calculate remaining decimations from preceding ones
//    for( int i = 1; i < SUBNUM; i++ ) {
//      decim        = decimHelps[ i ].shift - decimHelps[ i - 1 ].shift;
//      len        >>= decim;
//      decimator.decimate( outBuf, outBuf, 0, len, 1 << decim );
//      das.continueWrite( i, outBuf, 0, len );
//    } // for( SUBNUM )
  }

  // same as subsampleWrite but input is already at first decim stage
  protected void subsampleWrite2( float[][] buf, DecimatedStake das, int len )
  throws IOException
  {
    int  decim;
   
    // calculate remaining decimations from preceding ones
    for( int i = 1; i < SUBNUM; i++ ) {
      decim        = decimHelps[ i ].shift - decimHelps[ i - 1 ].shift;
      len        >>= decim;
//      framesWritten  >>= decim;
      decimator.decimate( buf, buf, 0, len, 1 << decim );
//      ste[i].continueWrite( ts[i], framesWritten, outBuf, 0, len );
      das.continueWrite( i, buf, 0, len );
    } // for( SUBNUM )
  }

  // @synchronization  caller must have sync on fileSync !!!
   private DecimatedStake allocAsync( Span span )
  throws IOException
  {
    if( !Thread.holdsLock( fileSync )) throw new IllegalMonitorStateException();

    final long floorStart    = span.start & MAXMASK;
    final long ceilStop      = (span.stop + MAXCEILADD) & MAXMASK;
    final Span extSpan      = (floorStart == span.start) && (ceilStop == span.stop) ? span : new Span( floorStart, ceilStop );
    final Span[] fileSpans    = new Span[ SUBNUM ];
    final Span[] biasedSpans  = new Span[ SUBNUM ];
    long fileStart;
    long fileStop;
     
        if( tempFAsync == null ) {
      // XXX THIS IS THE PLACE TO OPEN WAVEFORM CACHE FILE
      tempFAsync = createTempFiles();
        }
    synchronized( tempFAsync ) {
      for( int i = 0; i < SUBNUM; i++ ) {
        fileStart      = tempFAsync[ i ].getFrameNum();
        fileStop      = fileStart + (extSpan.getLength() >> decimHelps[ i ].shift);
        tempFAsync[ i ].setFrameNum( fileStop );
        fileSpans[ i ]    = new Span( fileStart, fileStop );
        biasedSpans[ i = extSpan;
      }
    }
    return new DecimatedStake( extSpan, tempFAsync, fileSpans, biasedSpans, decimHelps );
  }

  /*
   *  @synchronization  call within synchronoized( bufSync ) block
   */
  private void createBuffers()
  {
    if( !Thread.holdsLock( bufSync )) throw new IllegalMonitorStateException();
 
    if( tmpBuf == null ) {
      tmpBuf    = new float[ fullChannels ][ tmpBufSize ];
      tmpBuf2    = new float[ decimChannels ][ tmpBufSize2 ];
    }
  }

  private void freeBuffers()
  {
    synchronized( bufSync ) {
      tmpBuf  = null;
      tmpBuf2  = null;
    }
  }

  private AudioFile[] createTempFiles()
    throws IOException
  {
    // simply use an AIFC file with float format as temp file
    final AudioFileDescr proto  = new AudioFileDescr();
    final AudioFile[] tempF    = new AudioFile[ SUBNUM ];
    AudioFileDescr afd;
    proto.type          = AudioFileDescr.TYPE_AIFF;
    proto.channels        = decimChannels;
    proto.bitsPerSample      = 32;
    proto.sampleFormat      = AudioFileDescr.FORMAT_FLOAT;
    try {
      for( int i = 0; i < SUBNUM; i++ ) {
        afd            = new AudioFileDescr( proto );
        afd.file        = File.createTempFile( "swing", null, null );
        afd.file.deleteOnExit();
        afd.rate        = decimHelps[ i ].rate;
        tempF[ i ]        = AudioFile.openAsWrite( afd );
      }
      return tempF;
    }
    catch( IOException e1 ) {
      for( int i = 0; i < SUBNUM; i++ ) {
        if( tempF[ i ] != null ) tempF[ i ].cleanUp();
      }
      throw e1;
    }
  }
 
  private void deleteTempFiles( AudioFile[] tempF )
  {
    for( int i = 0; i < tempF.length; i++ ) {
      if( tempF[ i ] != null ) {
        tempF[ i ].cleanUp();
        tempF[ i ].getFile().delete();
      }
    }
  }
 
  private void disposeFullScale()
  {
    if( fullScale != null ) {
      fullScale.cleanUp();
      if( deleteFullScale ) {
        final File f = fullScale.getFile();
        if( f.exists() && !f.delete() ) {
          System.err.println( "Warning: temp file '" + f.getAbsolutePath() +
            "could not be deleted" );
          // f.deleteOnExit() ?
        }
      }
      fullScale = null;
    }
  }
 
  public void readSndFile( String path, long startFrame, long numFrames )
  throws IOException
  {
    readSndFile( path, startFrame, numFrames, false );
  }
 
  public void readSndFile( String path, long startFrame, long numFrames,
                       boolean deleteWhenDisposed )
  throws IOException
  {
    if( threadAsync != null ) throw new IllegalStateException();
 
    boolean launched = false;
   
    try {
      disposeFullScale();
      freeBuffers();
      freeTempFiles();
     
      fullScale            = AudioFile.openAsRead( new File( path ));
      deleteFullScale          = deleteWhenDisposed;
      final AudioFileDescr  afd    = fullScale.getDescr();
     
      fullChannels  = afd.channels;
      decimChannels  = fullChannels * modelChannels;
      startFrame    = Math.max( 0, Math.min( afd.length, startFrame ));
      numFrames    = Math.max( 0, Math.min( afd.length - startFrame, numFrames ));
      viewSpan    = new Span( startFrame, startFrame + numFrames );
      totalSpan    = new Span( viewSpan );
   
      final DecimatedStake    das;
      final Span          extSpan;
      final long          fullrateStop, fullrateLen;
      final int          numFullBuf;
      final Object        enc_this  = this;
      final AudioFile        cacheReadAF;
      final AudioFile        cacheWriteAF;
 
      for( int i = 0; i < SUBNUM; i++ ) {
        this.decimHelps[ i ] = new DecimationHelp( afd.rate, decimations[ i ]);
      }
 
      synchronized( fileSync ) {
        das      = allocAsync( viewSpan );
      }
      extSpan      = das.getSpan();
 
  // XXX SWINGOSC XXX
  //    fullrateStop  = Math.min( extSpan.getStop(), fullScale.editGetSpan( ce ).stop );
      fullrateStop  = Math.min( extSpan.stop, viewSpan.stop );
      fullrateLen    = fullrateStop - extSpan.start;

//      numFullBuf    = (int) (fullrateLen >> MAXSHIFT);
     
      cacheReadAF    = openCacheForRead( model );
      if( cacheReadAF == null ) {
//        cacheWriteAS = fullScale.openCacheForWrite( model,
//            decimHelps[ 0 ].fullrateToSubsample( union.getLength() ));
        cacheWriteAF = openCacheForWrite( model, (fullrateLen + MAXCEILADD) & MAXMASK );
        numFullBuf  = (int) (fullrateLen >> MAXSHIFT);
      } else {
        numFullBuf  = (int) ((fullrateLen + MAXCEILADD) >> MAXSHIFT)// cached files always have integer fullBufs!
        cacheWriteAF = null;
      }

      synchronized( bufSync ) {
        createBuffers();
      }
 
  // XXX SWINGOSC XXX   
  //    editClear( source, das.getSpan(), ce );
  //    editAdd( source, das, ce );
  decimatedStake = das;
      launched = true;
   
      threadAsync = new Thread( new Runnable() {
        public void run() {
          final int  minCoarse;
          boolean    success        = false;
          long    pos          = extSpan.getStart();
//          long    framesWrittenCache   = 0;
          boolean    cacheWriteComplete  = false;
          long    framesWritten    = 0;
          Span    tag2;
          float    f1;
          int      len, repaint    = 0;
          long    time, nextTime    = System.currentTimeMillis() + 100;
 
          minCoarse  = MAXCOARSE >> decimHelps[ 0 ].shift;

          try {
            for( int i = 0; (i < numFullBuf) && keepAsyncRunning; i++ ) {
              synchronized( bufSync ) {
                if( cacheReadAF != null ) {
                  tag2       = new Span( pos, pos + minCoarse );
                  cacheReadAF.readFrames( tmpBuf2, 0, minCoarse );
                  das.continueWrite( 0, tmpBuf2, 0, minCoarse );
                  subsampleWrite2( tmpBuf2, das, minCoarse );
                  pos        += minCoarse;
                } else {
                  tag2       = new Span( pos, pos + MAXCOARSE );
      //            fullScale.readFrames( tmpBuf, 0, tag2, ce );
                  fullScale.readFrames( tmpBuf, 0, MAXCOARSE );
//  for( int k = 0; k < tmpBuf.length; k++ ) { for( int j = 0; j < MAXCOARSE; j++ ) { tmpBuf[ k ][ j ] = 0.125f; }}
                  subsampleWrite( tmpBuf, tmpBuf2, das, MAXCOARSE, cacheWriteAF );
                  pos        += MAXCOARSE;
//                  framesWrittenCache += minCoarse;
                }
                framesWritten += MAXCOARSE;
              }
              time = System.currentTimeMillis();
              if( time >= nextTime ) {
                readProgress = (float) ((double) framesWritten / (double) fullrateLen);
                nextTime = time + 100;
                if( asyncManager != null ) asyncManager.dispatchEvent( new AsyncEvent( enc_this, AsyncEvent.UPDATE, time ));
  // XXX SWINGOSC XXX
                repaint = (repaint + 1) % 20;
                if( repaint == 0 ) {
                  needsImageUpdate = true;
                  repaint();
                }
              }
            }
 
            if( (cacheReadAF == null) && keepAsyncRunning ) { // cached files always have integer fullBufs!
              len = (int) (fullrateStop - pos);
              if( len > 0 ) {
                synchronized( bufSync ) {
                  tag2 = new Span( pos, pos + len );
    // XXX SWINGOSC XXX
    //              fullScale.readFrames( tmpBuf, 0, tag2, null );
                  if( fullScale.getFramePosition() != tag2.start ) {
                    fullScale.seekFrame( tag2.start );
                  }
                  fullScale.readFrames( tmpBuf, 0, (int) tag2.getLength() );
                  for( int ch = 0; ch < fullChannels; ch++ ) {
                    f1 = tmpBuf[ ch ][ len - 1 ];
                    for( int i = len; i < MAXCOARSE; i++ ) {
                      tmpBuf[ ch ][ i ] = f1;
                    }
                  }
                  subsampleWrite( tmpBuf, tmpBuf2, das, MAXCOARSE, cacheWriteAF );
                  pos        += MAXCOARSE;
                  framesWritten  += MAXCOARSE;
//                  framesWrittenCache += minCoarse;
                }
              }
            }
            cacheWriteComplete = true;
            if( cacheWriteAF != null ) cm.addFile( cacheWriteAF.getFile() );
            success  = true;
          }
          catch( IOException e1 ) {
  // XXX SWINGOSC XXX
  //          System.err.println( e1 );
//            System.out.println( e1 );
            e1.printStackTrace( System.out );
          }
          finally {
            if( cacheReadAF != null ) cacheReadAF.cleanUp();
            if( cacheWriteAF != null ) {
              cacheWriteAF.cleanUp();
              if( !cacheWriteComplete ) { // indicates process was aborted ...
                final File f = createCacheFileName();
                if( (cm != null) && (f != null) ) {    // ... therefore delete incomplete cache files!
                  cm.removeFile( f );
                }
              }
            }

            if( asyncManager != null ) {
              asyncManager.dispatchEvent( new AsyncEvent( enc_this,
                success ? AsyncEvent.FINISHED : (keepAsyncRunning ? AsyncEvent.FAILED : AsyncEvent.CANCELLED),
                System.currentTimeMillis() ));
            }
           
            synchronized( threadAsync ) {
              threadAsync.notifyAll();
              threadAsync = null;
            }

            needsImageUpdate = true;
            repaint();
          }
        }
      });
     
      keepAsyncRunning   = true;
      threadAsync.start();
      launched      = true;
    }
    finally {
      if( !launched ) {
        keepAsyncRunning = false;
//         XXX should be fused with dispose()
        disposeFullScale();
        freeBuffers();
        freeTempFiles();
        if( asyncManager != null ) {
          asyncManager.dispatchEvent( new AsyncEvent( this, AsyncEvent.FAILED, System.currentTimeMillis()  ));
        }
      }
    }
  }
 
  public AudioFile getSoundFile()
  {
    return fullScale;
  }
 
  public float getReadProgress()
  {
    return readProgress;
  }

  // XXX should be fused with dispose()
  public void cancelAsyncRead()
  {
    killAsyncThread();
    disposeFullScale();
    freeBuffers();
    freeTempFiles();
  }
 
  private void killAsyncThread()
  {
    if( threadAsync != null ) {
      synchronized( threadAsync ) {
        if( threadAsync.isAlive() ) {
          keepAsyncRunning = false;
          try {
            threadAsync.wait();
          }
          catch( InterruptedException e1 ) {
            System.err.println( e1 );
          }
        }
      }
      threadAsync = null;
    }
  }
 
  public boolean isBusy()
  {
    return( (threadAsync != null) && threadAsync.isAlive() );
  }

  public void addAsyncListener( AsyncListener l )
  {
// XXX SWINGOSC XXX
//    if( !isBusy() ) {
//      l.asyncFinished( new AsyncEvent( this, AsyncEvent.FINISHED, System.currentTimeMillis() ));
//      return;
//    }
    if( asyncManager == null ) asyncManager = new EventManager( new EventManager.Processor() {
      public void processEvent( BasicEvent e )
      {
        AsyncListener    l;
        final AsyncEvent  ae = (AsyncEvent) e;
       
        for( int i = 0; i < asyncManager.countListeners(); i++ ) {
          l = (AsyncListener) asyncManager.getListener( i );
          switch( e.getID() ) {
          case AsyncEvent.UPDATE:
            l.asyncUpdate( ae );
            break;
          case AsyncEvent.FINISHED:
            l.asyncFinished( ae );
            break;
          case AsyncEvent.FAILED:
            l.asyncFailed( ae );
            break;
          case AsyncEvent.CANCELLED:
            l.asyncCancelled( ae );
            break;
          default:
            assert false : e.getID();
            break;
          }
        }
      }
    });
    asyncManager.addListener( l );
  }

  public void removeAsyncListener( AsyncListener l )
  {
    if( asyncManager != null ) asyncManager.removeListener( l );
  }

  public void dispose()
  {
    listeners.clear();
    killAsyncThread()// this has to be the first step
    if( asyncManager != null ) asyncManager.dispose();
// XXX SWINGOSC XXX
    disposeImage();
//    fullScale.removeDependant( this );
    disposeFullScale();
    freeBuffers();
    freeTempFiles();
// XXX SWINGOSC XXX
//    super.dispose();
  }

  private void freeTempFiles()
  {
    synchronized( fileSync ) {
// XXX SWINGOSC XXX
if( decimatedStake != null ) {
  decimatedStake.dispose();
  decimatedStake = null;
}
// XXX SWINGOSC XXX
//      if( tempF != null ) {
//        deleteTempFiles( tempF );
//      }
      // XXX THIS IS THE PLACE TO KEEP WAVEFORM CACHE FILE
      if( tempFAsync != null ) {
        deleteTempFiles( tempFAsync );
        tempFAsync = null;
      }
    }
  }

  protected File createCacheFileName()
    {
      if( (cm == null) || !cm.isActive() ) return null;
     
    return cm.createCacheFileName( fullScale.getFile() );
    }
 
//  private int[][] createCacheChannelMaps()
//  {
//    final int[][] fullChanMaps  = fullScale.getChannelMaps();
//    final int[][] cacheChanMaps  = new int[ fullChanMaps.length ][];
//   
//    for( int i = 0; i < fullChanMaps.length; i++ ) {
//      cacheChanMaps[ i ] = new int[ fullChanMaps[ i ].length * modelChannels ];
//      for( int j = 0; j < cacheChanMaps[ i ].length; j++ ) {
//        cacheChanMaps[ i ][ j ] = j;
//      }
//    }
//   
//    return cacheChanMaps;
//  }

  /*
   *   @returns  the cached stake or null if no cache file is available
   */
  private AudioFile openCacheForRead( int model )
  throws IOException
  {
    final File      f      = createCacheFileName();
    if( f == null ) return null;
   
    final String    ourCode    = "EisK"; // AbstractApplication.getApplication().getMacOSCreator();
    AudioFile      cacheAF    = null;
    AudioFileDescr    afd;
    boolean        success    = false;
    byte[]        appCode;
    AudioFileCacheInfo  infoA, infoB;

    try {
      if( !f.isFile() ) return null;
      cacheAF      = AudioFile.openAsRead( f );
      cacheAF.readAppCode();
      afd        = cacheAF.getDescr();
      appCode      = (byte[]) afd.getProperty( AudioFileDescr.KEY_APPCODE );
      if( ourCode.equals( afd.appCode ) && (appCode != null) ) {
        infoA    = AudioFileCacheInfo.decode( appCode );
        if( infoA != null ) {
          infoB  = new AudioFileCacheInfo( fullScale, model, fullScale.getFrameNum() );
          if( !infoA.equals( infoB )) {
            return null;
          }
        }
      } else {
        return null;
      }
      success = true;
      return cacheAF;
    }
    finally {
      if( !success ) {
        if( cacheAF != null ) {
          cacheAF.cleanUp();
        }
      }
    }
  }

  private AudioFile openCacheForWrite( int model, long decimFrameNum )
  throws IOException
  {
    final File          f      = createCacheFileName();
    if( f == null ) return null;

    final AudioFileDescr    afd      = new AudioFileDescr();
    final String        ourCode    = "EisK"; // AbstractApplication.getApplication().getMacOSCreator();
    final AudioFileCacheInfo  info;

    afd.type      = AudioFileDescr.TYPE_AIFF;
    afd.bitsPerSample  = 32;
    afd.sampleFormat  = AudioFileDescr.FORMAT_FLOAT;
    afd.rate      = decimHelps[ 0 ].rate; // getRate();
    afd.appCode      = ourCode;

    cm.removeFile( f );    // in case it existed
    afd.channels    = fullScale.getChannelNum() * modelChannels;
    afd.file      = f;
    info        = new AudioFileCacheInfo( fullScale, model, fullScale.getFrameNum() );
    afd.setProperty( AudioFileDescr.KEY_APPCODE, info.encode() );
    return AudioFile.openAsWrite( afd );
  }
   
  private int drawPCM( float[] frames, int len, int[] polyX, int[] polyY, int off,
              float offX, float scaleX, float scaleY, boolean sampleAndHold )
  {
    int    x, y;

    if( sampleAndHold ) {
      x = (int) offX;
      for( int i = 0; i < len; ) {
        y        = (int) (frames[ i ] * scaleY);
        polyX[ off = x;
        polyY[ off = y;
        off++;
        i++;
        x        = (int) (i * scaleX + offX);
        polyX[ off = x;
        polyY[ off = y;
        off++;
      }
    } else {
      for( int i = 0; i < len; i++, off++ ) {
        x        = (int) (i * scaleX + offX);
        polyX[ off = x;
        polyY[ off = (int) (frames[ i ] * scaleY);
      }
    }
   
    return off;
  }

  private int drawHalfWavePeakRMS( float[] sPeakP, float[] sPeakN, float[] sRMSP, float[] sRMSN, int len,
                   int[] peakPolyX, int[] peakPolyY, int[] rmsPolyX, int[] rmsPolyY, int off,
                   float offX, float scaleX, float scaleY )
  {
    final float  scaleYN  = -scaleY;
    int      x;
   
    for( int i = 0, k = peakPolyX.length - 1 - off; i < len; i++, off++, k-- ) {
      x          = (int) (i * scaleX + offX);
      peakPolyX[ off = x;
      peakPolyX[ k ]    = x;
      rmsPolyX[ off ]    = x;
      rmsPolyX[ k ]    = x;
      peakPolyY[ off = (int) (sPeakP[ i ] * scaleY);
      peakPolyY[ k ]    = (int) (sPeakN[ i ] * scaleY);
      rmsPolyY[ off ]    = (int) ((float) Math.sqrt( sRMSP[ i ]) * scaleY);
      rmsPolyY[ k ]    = (int) ((float) Math.sqrt( sRMSN[ i ]) * scaleYN);
    }
   
    return off;
  }

  private int drawFullWavePeakRMS( float[] sPeakP, float[] sPeakN, float[] sRMS, int len,
        int[] peakPolyX, int[] peakPolyY, int[] rmsPolyX, int[] rmsPolyY, int off,
        float offX, float scaleX, float scaleY )
  {
//    final float  scaleYN  = -scaleY;
    int      x;
    float    peakP, peakN, rms;
   
    for( int i = 0, k = peakPolyX.length - 1 - off; i < len; i++, off++, k-- ) {
      x          = (int) (i * scaleX + offX);
      peakPolyX[ off = x;
      peakPolyX[ k ]    = x;
      rmsPolyX[ off ]    = x;
      rmsPolyX[ k ]    = x;
      peakP        = sPeakP[ i ];
      peakN        = sPeakN[ i ];
      peakPolyY[ off = (int) (peakP * scaleY) + 2;
      peakPolyY[ k ]    = (int) (peakN * scaleY) - 2;
//      peakC        = (peakP + peakN) / 2;
      rms          = (float) Math.sqrt( sRMS[ i ]); // / 2;
      rmsPolyY[ off ]    = (int) (Math.min( peakP, rms ) * scaleY);
      rmsPolyY[ k ]    = (int) (Math.max( peakN, -rms ) * scaleY);
    }
 
    return off;
  }

  private Rectangle rectForChannel( int ch )
  {
    final int     y, h;
    final Insets  ins    = getInsets();
    final int     ht    = getHeight() - (ins.top + ins.bottom);
    final int    w    = getWidth() - (ins.left + ins.right);

    if( overlay || lissajou ) {
      y          = 0; // insets.top;
      h          = ht;
    } else {
      final int temp    = ht * ch / fullChannels;
      y          = temp; // insets.top + temp;
      h          = (ht * (ch + 1) / fullChannels) - temp - vGap;
    }     
    r.setBounds( 0, y, w, h );
    return r;
  }

//  private Rectangle rectForChannel( int ch )
//  {
//    final int y, h;
//    final int ht      = getHeight();
// 
//    if( overlay || lissajou ) {
//      y          = insets.top;
//      h          = ht;
//    } else {
//      final int temp    = ht * ch / fullChannels;
//      y          = insets.top + temp;
//      h          = (ht * (ch + 1) / fullChannels) - temp - vGap;
//    }     
//    r.setBounds( insets.left, y, getWidth(), h );
//   
//    return r;
//  }

  /*
   *  Speed measurements (feb 2006): for HalfwavePeakRMS, using g2.fillPolygon is about twice as fast as using
   *  GeneralPath objects. The integer resolution can be compensated for by scaling the points by
   *  factor 4.0 and scaling the Graphics2D by 1/4 at no significant CPU cost.
   *
   *  @synchronization  must be called in the event thread
   */
//  private void drawWaveform( DecimationInfo info, WaveformView view, Graphics2D g2 )
  private void drawWaveform( DecimationInfo info, Graphics2D g2 )
  {
    final boolean    fromPCM      = info.idx == -1;
    final boolean    toPCM      = fromPCM && (info.inlineDecim == 1);
    final long      maxLen      = toPCM ? tmpBufSize :
                        (fromPCM ? Math.min( tmpBufSize, tmpBufSize2 * info.getDecimationFactor() ) :
                        tmpBufSize2 << info.shift);
    final int      polySize    = (int) (info.sublength << 1);
    final AffineTransform atOrig    = g2.getTransform();
    final Shape      clipOrig    = g2.getClip();
   
    final int[][]    peakPolyX    = new int[ fullChannels ][ polySize ];
    final int[][]    peakPolyY    = new int[ fullChannels ][ polySize ];
    final int[][]    rmsPolyX    = toPCM ? null : new int[ fullChannels ][ polySize ];
    final int[][]    rmsPolyY    = toPCM ? null : new int[ fullChannels ][ polySize ];
    final boolean[]    sampleAndHold  = toPCM ? new boolean[ fullChannels ] : null;
// XXX SWINGOSC XXX
//    final float      deltaYN      = 4f / (view.getMin() - view.getMax());
    final float      deltaYN      = -2f * yZoom;
   
    float[]        sPeakP, sPeakN, sRMSP, sRMSN;
    float        offX, scaleX, scaleY;
    long        start      = info.span.start;
    long        totalLength    = info.getTotalLength();
    Span        chunkSpan;
    long        fullLen, fullStop;
    int          chunkLen, decimLen;
    final int[]      off        = new int[ fullChannels ];
    Rectangle      r;
   
    try {
      busyList.clear()// "must be called in the event thread"
   
      synchronized( bufSync ) {
        createBuffers();

        while( totalLength > 0 ) {
          fullLen    = Math.min( maxLen, totalLength );
          chunkLen  = (int) (fromPCM ? fullLen : decimHelps[ info.idx ].fullrateToSubsample( fullLen ));
          decimLen  = chunkLen / info.inlineDecim;
          chunkLen    = decimLen * info.inlineDecim;
          fullLen    = (long) chunkLen << info.shift;
          chunkSpan  = new Span( start, start + fullLen );

          if( fromPCM ) {
// XXX SWINGOSC XXX
//            fullStop = fullScale.getSpan().stop;
            fullStop = fullScale.getFrameNum();
            if( start + fullLen <= fullStop ) {
              chunkSpan  = new Span( start, start + fullLen );
// XXX SWINGOSC
//              fullScale.readFrames( tmpBuf, 0, chunkSpan );
              if( fullScale.getFramePosition() != chunkSpan.start ) {
                fullScale.seekFrame( chunkSpan.start );
              }
              fullScale.readFrames( tmpBuf, 0, (int) chunkSpan.getLength() );
            } else {
              chunkSpan  = new Span( start, fullStop );
//  XXX SWINGOSC
//              fullScale.readFrames( tmpBuf, 0, chunkSpan );
              if( fullScale.getFramePosition() != chunkSpan.start ) {
                fullScale.seekFrame( chunkSpan.start );
              }
              fullScale.readFrames( tmpBuf, 0, (int) chunkSpan.getLength() );
              // duplicate last frames
              for( int i = (int) chunkSpan.getLength(), j = i - 1; i < (int) fullLen; i++ ) {
                for( int ch = 0; ch < fullChannels; ch++ ) {
                  sPeakP = tmpBuf[ ch ];
                  sPeakP[ i ] = sPeakP[ j ];
                }
              }
            }
            if( !toPCM ) decimator.decimatePCM( tmpBuf, tmpBuf2, 0, decimLen, info.inlineDecim );
          } else {
            chunkSpan  = new Span( start, start + fullLen );
// XXX SWINGOSC XXX
//            readFrames( info.idx, tmpBuf2, 0, busyList, chunkSpan, null );
            readFrames( info.idx, tmpBuf2, 0, busyList, chunkSpan );
            if( info.inlineDecim > 1 ) decimator.decimate( tmpBuf2, tmpBuf2, 0, decimLen, info.inlineDecim );
          }
          if( toPCM ) {
            for( int ch = 0; ch < fullChannels; ch++ ) {
              sPeakP        = tmpBuf[ ch ];
// XXX SWINGOSC XXX
//              r          = view.rectForChannel( ch );
              r          = rectForChannel( ch );
              scaleX        = 4 * r.width / (float) (info.sublength - 1);
              scaleY        = r.height * deltaYN;
              offX        = scaleX * off[ ch ];
              sampleAndHold[ ch = scaleX > 16;

              off[ ch ]      = drawPCM( sPeakP, decimLen, peakPolyX[ ch ], peakPolyY[ ch ], off[ ch ], offX, scaleX, scaleY, sampleAndHold[ ch ]);
            }
          } else {
            switch( model ) {
            case MODEL_HALFWAVE_PEAKRMS:
              for( int ch = 0, chPeakN = fullChannels, chRMSP = 2 * fullChannels, chRMSN = 3 * fullChannels; ch < fullChannels;
                ch++, chPeakN++, chRMSP++, chRMSN++ ) {
              
              sPeakP  = tmpBuf2[ ch ];
              sPeakN  = tmpBuf2[ chPeakN ];
              sRMSP  = tmpBuf2[ chRMSP ];
              sRMSN  = tmpBuf2[ chRMSN ];

//XXX SWINGOSC XXX
//              r    = view.rectForChannel( ch );
              r    = rectForChannel( ch );
              scaleX  = 4 * r.width / (float) (info.sublength - 1);
              scaleY  = r.height * deltaYN;
              offX  = scaleX * off[ ch ];

              off[ ch ] = drawHalfWavePeakRMS( sPeakP, sPeakN, sRMSP, sRMSN, decimLen,
                               peakPolyX[ ch ], peakPolyY[ ch ], rmsPolyX[ ch ], rmsPolyY[ ch ],
                               off[ ch ], offX, scaleX, scaleY );
            }
            break;
// XXX SWINGOSC XXX
//            case MODEL_MEDIAN:
//              throw new IllegalStateException( "Median drawing not yet working" );
//             
            case MODEL_FULLWAVE_PEAKRMS:
              for( int ch = 0, chPeakN = fullChannels, chRMS = 2 * fullChannels; ch < fullChannels;
               ch++, chPeakN++, chRMS++ ) {
              
              sPeakP  = tmpBuf2[ ch ];
              sPeakN  = tmpBuf2[ chPeakN ];
              sRMSP  = tmpBuf2[ chRMS ];

//XXX SWINGOSC XXX
//              r    = view.rectForChannel( ch );
              r    = rectForChannel( ch );
              scaleX  = 4 * r.width / (float) (info.sublength - 1);
              scaleY  = r.height * deltaYN;
              offX  = scaleX * off[ ch ];

              off[ ch ] = drawFullWavePeakRMS( sPeakP, sPeakN, sRMSP, decimLen,
                                peakPolyX[ ch ], peakPolyY[ ch ], rmsPolyX[ ch ], rmsPolyY[ ch ],
                                off[ ch ], offX, scaleX, scaleY );
            }
            break;

            default:
              assert false : model;
              break;
            }
          }
          start    += fullLen;
          totalLength -= fullLen;
        }
      } // synchronized( bufSync )

      if( toPCM ) {
        final Stroke strkOrig = g2.getStroke();
        g2.setStroke( strkLine );
// XXX SWINGOSC XXX
//        g2.setPaint( Color.black );
        for( int ch = 0; ch < fullChannels; ch++ ) {
// XXX SWINGOSC XXX
          g2.setColor( colrWave.length > ch ? colrWave[ ch ] : colrFg );
// XXX SWINGOSC XXX
//          r    = view.rectForChannel( ch );
          r    = rectForChannel( ch );
          g2.clipRect( r.x, r.y, r.width, r.height );
          g2.translate( r.x, r.y + r.height * 0.5f );
          g2.scale( 0.25f, 0.25f );
          g2.drawPolyline( peakPolyX[ ch ], peakPolyY[ ch ], off[ ch ]);
          g2.setTransform( atOrig );
          g2.setClip( clipOrig );
        }
        g2.setStroke( strkOrig );
      } else {
        for( int ch = 0; ch < fullChannels; ch++ ) {
//           XXX SWINGOSC XXX
//          r    = view.rectForChannel( ch );
          r    = rectForChannel( ch );
          g2.clipRect( r.x, r.y, r.width, r.height );
          if( !busyList.isEmpty() ) {
            g2.setPaint( pntBusy );
            for( int i = 0; i < busyList.size(); i++ ) {
              chunkSpan = (Span) busyList.get( i );
              scaleX  = r.width / (float) info.getTotalLength(); // (info.sublength - 1);
              g2.fillRect( (int) ((chunkSpan.start - info.span.start) * scaleX) + r.x, r.y, (int) (chunkSpan.getLength() * scaleX), r.height );
            }
          }
          g2.translate( r.x, r.y + r.height * 0.5f );
          g2.scale( 0.25f, 0.25f );
// XXX SWINGOSC XXX
//          g2.setColor( Color.gray );
          g2.setColor( colrWave2.length > ch ? colrWave2[ ch ] : colrFg2 );
          g2.fillPolygon( peakPolyX[ ch ], peakPolyY[ ch ], polySize );
// XXX SWINGOSC XXX
//          g2.setColor( Color.black );
//          g2.setColor( colrWave.length > ch ? colrWave[ ch ] : colrFg );
//g2.setPaint( pntRMS );
//g2.setColor( getBackground() );
          g2.setColor( colrWave.length > ch ? colrWave[ ch ] : colrFg );
          g2.fillPolygon( rmsPolyX[ ch ], rmsPolyY[ ch ], polySize );
          g2.setTransform( atOrig );
          g2.setClip( clipOrig );
        }
      }
    }
    catch( IOException e1 ) {
      System.err.println( e1 );
    }
  }

    private void readFrames( int sub, float[][] data, int dataOffset, List busyList, Span readSpan )
    throws IOException
    {
      final DecimatedStake   stake    = decimatedStake;
    final long        startR    = decimHelps[ sub ].roundAdd - readSpan.start;
    final MutableInt    readyLen  = new MutableInt( 0 );
    final MutableInt    busyLen    = new MutableInt( 0 );
    int            chunkLen, discrepancy;
    Span          subSpan;
    int            readOffset, nextOffset = dataOffset;
    int            len      = (int) (readSpan.getLength() >> decimHelps[ sub ].shift);
   
    subSpan    = new Span( Math.max( stake.getSpan().start, readSpan.start ),
                Math.min( stake.getSpan().stop, readSpan.stop ));
    stake.readFrames( sub, data, nextOffset, subSpan, readyLen, busyLen );
    chunkLen  = readyLen.value() + busyLen.value();
    readOffset  = nextOffset + readyLen.value(); // chunkLen;
    nextOffset  = (int) ((subSpan.stop + startR) >> decimHelps[ sub ].shift) + dataOffset;
    discrepancy  = nextOffset - readOffset;
    len       -= readyLen.value() + discrepancy;
    if( busyLen.value() == 0 ) {
      if( discrepancy > 0 ) {
        if( readOffset > 0 ) {
          for( int i = readOffset, k = readOffset - 1; i < nextOffset; i++ ) {
            for( int j = 0; j < data.length; j++ ) {
              data[ j ][ i ] = data[ j ][ k ];
            }
          }
        }
      }
    } else {
      busyList.add( new Span( subSpan.stop - (subSpan.getLength() * busyLen.value() / chunkLen), subSpan.stop ));
      for( int i = Math.max( 0, readOffset ); i < nextOffset; i++ ) {
        for( int j = 0; j < data.length; j++ ) {
          data[ j ][ i ] = 0f;
        }
      }
    }
    }

  public DecimationInfo getBestSubsample( Span tag, int minLen )
  {
    final DecimationInfo  info;
    final boolean      fromPCM, toPCM;
    long          subLength, n;
    int            idx, inlineDecim;
   
    subLength    = tag.getLength();
    for( idx = 0; idx < SUBNUM; idx++ ) {
      n      = decimHelps[ idx ].fullrateToSubsample( tag.getLength() );
      if( n < minLen ) break;
      subLength  = n;
    }
    idx--;
    // had to change '>= minLen' to '> minLen' because minLen could be zero!
    switch( model ) {
    case MODEL_HALFWAVE_PEAKRMS:
    case MODEL_FULLWAVE_PEAKRMS:
      for( inlineDecim = 2; subLength / inlineDecim > minLen; inlineDecim++ ) ;
      inlineDecim--;
      break;
   
    case MODEL_MEDIAN:
      inlineDecim = 1;
      break;
   
    default:
      assert false : model;
      inlineDecim = 1// never gets here
    }
    subLength /= inlineDecim;
    fromPCM    = idx == -1;
    toPCM    = fromPCM && inlineDecim == 1;
    info    = new DecimationInfo( tag, subLength,
                      toPCM ? fullChannels : decimChannels,
                      idx, fromPCM ? 0 : decimHelps[ idx ].shift,
                      inlineDecim, toPCM ? MODEL_PCM : model );
    return info;
  }
 
  protected void efficientRepaint( Span dirty )
  { 
    final double   hScale;
    final Insets  ins  = getInsets();
    final int    w  = getWidth() - ins.left - ins.right;
    final int    h  = getHeight() - ins.top - ins.bottom;
    final int    x, x2;
   
    hScale   = (double) w / (double) Math.max( 1, viewSpan.getLength() );
    x    = Math.max( 0, (int) ((dirty.start - viewSpan.start) * hScale) );
    x2    = Math.min( w, (int) ((dirty.stop - viewSpan.start) * hScale) + 1 );
    if( x2 >= x ) {
      repaint( new Rectangle( x + ins.left, ins.top, x2 - x + 1, h ));
    }
  }
 
  private void disposeImage()
  {
    if( img != null ) {
      img.flush();
      img = null;
    }
  }

  private void updateImage( int w, int h )
  {
    final double hScale;
   
    if( img != null && ((img.getWidth( this ) != w) || (img.getHeight( this ) != h ))) {
//    if( img != null ) {
      disposeImage();
    }
    if( img == null ) {
// doesn't support alpha composite on window + linux!!
//      img = createImage( w, h );
// doesn't support alpha either
//      img = createVolatileImage( w, h );
      img = new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB );
    }
//    final Graphics2D g2 = (Graphics2D) img.getGraphics();
    final Graphics2D g2 = img.createGraphics();
//g2.setColor( getBackground() );
//g2.setColor( new Color( 0, 0, 0, 0 ));
//g2.setColor( Color.green );
//g2.fillRect( 0, 0, w, h );
   
// trick from here: http://www-128.ibm.com/developerworks/library/j-begjava/index.html#h2
//    Clear image with transparent alpha by drawing a rectangle
final Composite cmpOrig = g2.getComposite();
    g2.setComposite( AlphaComposite.getInstance( AlphaComposite.CLEAR, 0.0f ));
//    Rectangle2D.Double rect = new Rectangle2D.Double(0,0,spriteSize,spriteSize);
//    g2.fill(rect);
    g2.fillRect( 0, 0, w, h );
g2.setComposite( cmpOrig );
   
    g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );

//    decZoomReciprocal = 1.0 / (decimation * xZoom);
    hScale   = (double) w / (double) Math.max( 1, viewSpan.getLength() );
//    hOffset  = viewSpan.start * hScale;
   
    // ------------------------ draw grid ------------------------

    if( gridOn ) {
      final double step     = gridResolution * sampleRate * hScale;
      float gridOff      = (float) ((gridOffset - viewSpan.start) * hScale);
      shpGrid.reset();
      g2.setColor( colrGrid );
      if( step > 1.0 ) {
        if( gridOff < 0 ) {
          gridOff = gridOff += Math.ceil( -gridOff / step ) * step;
        }
        float gridX = gridOff;
        for( int i = 0; gridX < w; i++ ) {
          gridX = gridOff + (float) (i * step);
          shpGrid.moveTo( gridX, 0 );
          shpGrid.lineTo( gridX, h );
        }
        g2.draw( shpGrid );
      } else {
        shpGrid.append( new Rectangle2D.Float( gridOff, 0, w - gridOff, h ), false );
        g2.fill( shpGrid );
      }
    }
   
    // ------------------------ draw waveform(s) ------------------------

//    if( waveOn && (numChannels > 0) && (decimatedStake != null) ) {
    if( waveOn && (decimatedStake != null) ) {
      final DecimationInfo info;
      info = getBestSubsample( new Span( viewSpan.start, viewSpan.stop + 1 ), w );
      drawWaveform( info, g2 );
    } // if( waveOn )
   
    g2.dispose();
    needsImageUpdate = false;
  }
 
  public void paintComponent( Graphics g )
  {
    super.paintComponent( g );
   
    final Graphics2D    g2      = (Graphics2D) g;
    final Insets      ins     = getInsets();
    final int        w      = getWidth() - (ins.left + ins.right);
    final int        h      = getHeight() - (ins.top + ins.bottom);
    final AffineTransform  atOrig    = g2.getTransform();
//    final Stroke      strkOrig  = g2.getStroke();
//    final float        sy;
    final boolean      hResized  = recentWidth != w;
    final boolean      vResized  = recentHeight != h;
    final boolean      resized    = hResized || vResized;
//    float          offY    = 0f;
    int            x, x2;
    float          fx;
    final double      hScale     = (double) w / (double) Math.max( 1, viewSpan.getLength() );
    Selection        sel;

    g2.translate( ins.left, ins.top );
   
    // ------------------------ draw background ------------------------

    g2.setColor( getBackground() );
//g2.setColor( Color.black );
    g2.fillRect( 0, 0, w, h );

    // ------------------------ draw selections ------------------------
   
    for( int i = 0; i < selections.length; i++ ) {
      sel = selections[ i ];
      if( (sel != null) && !sel.span.isEmpty() && sel.span.touches( viewSpan )) {
        g2.setColor( sel.colr );
        x   = (int) ((sel.span.start - viewSpan.start) * hScale + 0.5);
        x2   = (int) ((sel.span.stop - viewSpan.start) * hScale + 0.5);
        g2.fillRect( x, 0, x2 - x, h );
      }
    }

    // ------------------------ draw grid / waveform ------------------------

//    if( resized || needsImageUpdate
//        || (img.validate( getGraphicsConfiguration() ) == VolatileImage.IMAGE_INCOMPATIBLE)
//        || img.contentsLost() ) {
    if( resized || needsImageUpdate ) {
      recentWidth    = w;
      recentHeight  = h;
      updateImage( w, h );
    }
    g2.drawImage( img, 0, 0, this );

    // ------------------------ draw cursor ------------------------
   
    if( timeCursorOn ) {
//      if( updateCursor || resized )
      fx = (float) ((timeCursorPos - viewSpan.start) * hScale);
      g2.setColor( colrTimeCursor );
      shpCursor.setLine( fx, 0, fx, h );
      g2.draw( shpCursor );
    }
   
    g2.setTransform( atOrig );
  }

  public void addListener( Listener l ) {
    // really simple now!
    listeners.add( l );
  }
 

  public void removeListener( Listener l ) {
    // really simple now!
    listeners.remove( l );
  }

  protected void dispatchCursorChange()
  {
    for( int i = 0; i < listeners.size(); i++ ) {
      ((Listener) listeners.get( i )).cursorChanged( this, timeCursorPos );
    }
  }

  protected void dispatchSelectionChange()
  {
    final Selection sel = selections[ selectionIndex ];
    for( int i = 0; i < listeners.size(); i++ ) {
      ((Listener) listeners.get( i )).selectionChanged( this, selectionIndex, sel.span );
    }
  }

//   ---------------- FocusListener interface ----------------

  public void focusGained( FocusEvent e )
  {
    repaint();
  }

  public void focusLost( FocusEvent e )
  {
    repaint();
  }

//   ---------------- internal classes ----------------

  public static interface Listener
  {
    public void cursorChanged( SoundFileView v, long newPosition );
    public void selectionChanged( SoundFileView v, int selectionIndex, Span newSpan );
  }
 
  private class MouseAdapter
  extends MouseInputAdapter
  {
    private boolean shiftDrag, ctrlDrag, validDrag = false, dragStarted = false;
    private long startPos;
    private int startX;
    private long dragOffset;
   
    protected MouseAdapter() { /* empty */ }
   
    public void mousePressed( MouseEvent e )
    {
      if( !isEnabled() ) return;

      requestFocus();

      if( e.isMetaDown() ) {
//        editSelectAll( null );
        selectRegion( e );
        dragStarted = false;
        validDrag  = false;
      } else {
        shiftDrag  = e.isShiftDown();
        ctrlDrag  = e.isControlDown();
        dragStarted = false;
        validDrag  = true;
        startX    = e.getX();
        processDrag( e, false );
      }
    }
   
    public void mouseReleased( MouseEvent e )
    {
      dragStarted = false;
      validDrag  = false;
    }
   
    public void mouseDragged( MouseEvent e )
    {
      if( validDrag ) {
        if( !dragStarted ) {
          if( shiftDrag || ctrlDrag || Math.abs( e.getX() - startX ) > 2 ) {
            dragStarted = true;
          } else return;
        }
        processDrag( e, true );
      }
    }
   
    public void mouseMoved( MouseEvent e )
    {
      // on mac, ctrl+press and moving will
      // not generate mouseDragged messages
      // but mouseMoved instead
      mouseDragged( e );
    }
   
    private long screenToVirtual( int x )
    {
      final double   hScale;
      final Insets  ins  = getInsets();
      final int    w  = getWidth() - ins.left - ins.right;
     
      hScale = (double) viewSpan.getLength() / Math.max( 1, w );
     
      return( Math.max( totalSpan.start, Math.min( totalSpan.stop,
          (long) ((x - ins.left) * hScale + viewSpan.start + 0.5) )));
    }

    private void selectRegion( MouseEvent e )
    {
      final Selection  sel;
     
      ensureSelection( selectionIndex );
      sel     = selections[ selectionIndex ];
     
      if( (sel.span.start == totalSpan.start || sel.editableStart) && sel.editableSize &&
          !sel.span.equals( totalSpan )) {
        sel.span = new Span( totalSpan );
        dispatchSelectionChange();
        repaint();
      }
    }
   
    private void processDrag( MouseEvent e, boolean hasStarted )
    {
      final long     position;
      final Selection  sel;
      Span      span, span2;
      long      n;
     
      ensureSelection( selectionIndex );
      sel     = selections[ selectionIndex ];
      
      span        = viewSpan;
      span2    = sel.span;
      position    = screenToVirtual( e.getX() );
      if( !hasStarted && !ctrlDrag ) {
        if( shiftDrag ) {
          if( !sel.editableSize ) return;
         
          if( span2.isEmpty() ) {
            span2 = new Span( timeCursorPos, timeCursorPos );
          }
          if( !sel.editableStart ) {
            startPos = span2.start;
            span2  = new Span( sel.span.start,
                  Math.max( sel.span.start, position ));
          } else {
            startPos = Math.abs( span2.start - position ) >
                  Math.abs( span2.stop - position ) ?
                  span2.start : span2.stop;
                  span2  = new Span( Math.min( startPos, position ),
                      Math.max( startPos, position ));
          }
//          edit  = TimelineVisualEdit.select( this, doc, span2 ).perform();
          if( !span2.equals( sel.span )) {
            span    = sel.span.union( span2 );
            sel.span  = span2;
            dispatchSelectionChange();
            efficientRepaint( span );
          }
        } else {
          startPos = position;
          if( span2.isEmpty() || !sel.editableStart || !sel.editableSize ) {
//            edit = TimelineVisualEdit.position( this, doc, position ).perform();
            if( timeCursorEditable && timeCursorOn && (position != timeCursorPos) ) {
              span = new Span( Math.min( position, timeCursorPos ), Math.max( position, timeCursorPos ));
              timeCursorPos = position;
              dispatchCursorChange();
              efficientRepaint( span );
            }
          } else {
//            edit = new CompoundEdit();
//            edit.addEdit( TimelineVisualEdit.select( this, doc, new Span() ).perform() );
//            edit.addEdit( TimelineVisualEdit.position( this, doc, position ).perform() );
//            ((CompoundEdit) edit).end();
            if( sel.editableStart && sel.editableSize ) {
              sel.span = new Span( position, position );
              dispatchSelectionChange();
            }
            span = span2.union( new Span( Math.min( position, timeCursorPos ), Math.max( position, timeCursorPos )));
            if( timeCursorOn && (timeCursorPos != position )) {
              timeCursorPos = position;
              dispatchCursorChange();
            }
            efficientRepaint( span );
          }
        }
      } else {
        if( ctrlDrag ) {
          if( shiftDrag ) {
            if( !sel.editableStart ) return;
            if( !hasStarted ) {
              dragOffset = sel.span.start - position;
            }
            n = Math.max( totalSpan.start, Math.min( totalSpan.stop - sel.span.getLength(),
                dragOffset + position ));
            span = new Span( n, n + sel.span.getLength() );
            if( !span.equals( sel.span )) {
              span2 = span.union( sel.span );
              sel.span = span;
              dispatchSelectionChange();
              efficientRepaint( span2 );
            }
          } else {
  //          edit  = TimelineVisualEdit.position( this, doc, position ).perform();
            if( timeCursorOn && (position != timeCursorPos) ) {
              span = new Span( Math.min( position, timeCursorPos ), Math.max( position, timeCursorPos ));
              timeCursorPos = position;
              dispatchCursorChange();
              efficientRepaint( span );
            }
          }
        } else {
          if( !sel.editableSize ) return;
          if( !sel.editableStart ) {
            span2  = new Span( sel.span.start,
                Math.max( sel.span.start, position ));
          } else {
            span2  = new Span( Math.min( startPos, position ),
                Math.max( startPos, position ));
          }
//          edit    = TimelineVisualEdit.select( this, doc, span2 ).perform();
          span    = sel.span.union( span2 );
          sel.span  = span2;
          dispatchSelectionChange();
          efficientRepaint( span );
        }
      }
    }
  }

  private static class DecimationHelp
  {
    public final double  rate;
    public final int  shift;
    public final int  factor;
    public final int  roundAdd;
    public final long  mask;
   
    public DecimationHelp( double fullRate, int shift )
    {
      this.shift    = shift;
      factor      = 1 << shift;
      this.rate    = fullRate / factor;
      roundAdd    = factor >> 1;
      mask      = -factor;
    }
   
    /**
     *  Converts a frame length from full rate to
     *  decimated rate and rounds to nearest integer.
     *
     *  @param  full  number of frame at full rate
     *  @return number of frames at this editor's decimated rate
     */
    public long fullrateToSubsample( long full )
    {
      return( (full + roundAdd) >> shift );
    }
  }

  private abstract class Decimator
  {
    protected Decimator() { /* empty */ }
    protected abstract void decimate( float[][] inBuf, float[][] outBuf, int outOff, int len, int decim );
    protected abstract void decimatePCM( float[][] inBuf, float[][] outBuf, int outOff, int len, int decim );
//    protected abstract void decimatePCMFast( float[][] inBuf, float[][] outBuf, int outOff, int len, int decim );
  }

  private class HalfPeakRMSDecimator
  extends Decimator
  {
    protected HalfPeakRMSDecimator() { /* empty */ }

    protected void decimate( float[][] inBuf, float[][] outBuf, int outOff, int len, int decim )
    {
      int    stop, j, k, m, ch, ch2;
      float   f1, f2, f3, f4, f5;
      float[] inBufCh1, inBufCh2, inBufCh3, inBufCh4, outBufCh1, outBufCh2, outBufCh3, outBufCh4;

      for( ch = 0; ch < fullChannels; ch++ ) {
        inBufCh1  = inBuf[ ch ];
        outBufCh1  = outBuf[ ch ];
        ch2      = ch + fullChannels;
        inBufCh2  = inBuf[ ch2 ];
        outBufCh2  = outBuf[ ch2 ];
        ch2       += fullChannels;
        inBufCh3  = inBuf[ ch2 ];
        outBufCh3  = outBuf[ ch2 ];
        ch2       += fullChannels;
        inBufCh4  = inBuf[ ch2 ];
        outBufCh4  = outBuf[ ch2 ];

        for( j = outOff, stop = outOff + len, k = 0; j < stop; j++ ) {
          f1 = inBufCh1[ k ];
          f2 = inBufCh2[ k ];
          f3 = inBufCh3[ k ];
          f4 = inBufCh4[ k ];
          for( m = k + decim, k++; k < m; k++ ) {
            f5 = inBufCh1[ k ];
            if( f5 > f1 ) f1 = f5;
            f5 = inBufCh2[ k ];
            if( f5 < f2 ) f2 = f5;
            f3 += inBufCh3[ k ];
            f4 += inBufCh4[ k ];
          }
          outBufCh1[ j = f1;      // positive halfwave peak
          outBufCh2[ j = f2;      // negative halfwave peak
          outBufCh3[ j = f3 / decim;  // positive halfwave mean square
          outBufCh4[ j = f4 / decim;  // negative halfwave mean square
        }
      } // for( ch )
    }
   
    protected void decimatePCM( float[][] inBuf, float[][] outBuf, int outOff, int len, int decim )
    {
      int    stop, j, k, m, ch, ch2;
      float   f1, f2, f3, f4, f5;
      float[] inBufCh1, outBufCh1, outBufCh2, outBufCh3, outBufCh4;

      for( ch = 0; ch < fullChannels; ch++ ) {
        inBufCh1  = inBuf[ ch ];
        outBufCh1  = outBuf[ ch ];
        ch2      = ch + fullChannels;
        outBufCh2  = outBuf[ch2];
        ch2       += fullChannels;
        outBufCh3  = outBuf[ ch2 ];
        ch2       += fullChannels;
        outBufCh4  = outBuf[ ch2 ];

        for( j = outOff, stop = outOff + len, k = 0; j < stop; j++ ) {
          f5 = inBufCh1[ k++ ];
          if( f5 >= 0.0f ) {
            f1 = f5;
            f3 = f5 * f5;
            f2 = 0.0f;
            f4 = 0.0f;
          } else {
            f2 = f5;
            f4 = f5 * f5;
            f1 = 0.0f;
            f3 = 0.0f;
          }
          for( m = 1; m < decim; m++ ) {
            f5 = inBufCh1[ k++ ];
            if( f5 >= 0.0f ) {
              if( f5 > f1 ) f1 = f5;
              f3 += f5 * f5;
            } else {
              if( f5 < f2 ) f2 = f5;
              f4 += f5 * f5;
            }
          }
          outBufCh1[ j = f1;      // positive halfwave peak
          outBufCh2[ j = f2;      // negative halfwave peak
          outBufCh3[ j = f3 / decim;  // positive halfwave mean square
          outBufCh4[ j = f4 / decim;  // negative halfwave mean square
        }
      } // for( ch )
    }
  } // class HalfPeakRMSDecimator

  private class FullPeakRMSDecimator
  extends Decimator
  {
    protected FullPeakRMSDecimator() { /* empty */ }
   
    protected void decimate( float[][] inBuf, float[][] outBuf, int outOff, int len, int decim )
    {
      int    stop, j, k, m, ch, ch2;
      float   f1, f2, f3, f5;
      float[] inBufCh1, inBufCh2, inBufCh3, outBufCh1, outBufCh2, outBufCh3;

      for( ch = 0; ch < fullChannels; ch++ ) {
        inBufCh1  = inBuf[ ch ];
        outBufCh1  = outBuf[ ch ];
        ch2      = ch + fullChannels;
        inBufCh2  = inBuf[ ch2 ];
        outBufCh2  = outBuf[ ch2 ];
        ch2       += fullChannels;
        inBufCh3  = inBuf[ ch2 ];
        outBufCh3  = outBuf[ ch2 ];

        for( j = outOff, stop = outOff + len, k = 0; j < stop; j++ ) {
          f1 = inBufCh1[ k ];
          f2 = inBufCh2[ k ];
          f3 = inBufCh3[ k ];
          for( m = k + decim, k++; k < m; k++ ) {
            f5 = inBufCh1[ k ];
            if( f5 > f1 ) f1 = f5;
            f5 = inBufCh2[ k ];
            if( f5 < f2 ) f2 = f5;
            f3 += inBufCh3[ k ];
          }
          outBufCh1[ j = f1;      // positive halfwave peak
          outBufCh2[ j = f2;      // negative halfwave peak
          outBufCh3[ j = f3 / decim;  // fullwave mean square
        }
      } // for( ch )
    }

    protected void decimatePCM( float[][] inBuf, float[][] outBuf, int outOff, int len, int decim )
    {
      int    stop, j, k, m, ch, ch2;
      float   f1, f2, f3, f5;
      float[] inBufCh1, outBufCh1, outBufCh2, outBufCh3;

      for( ch = 0; ch < fullChannels; ch++ ) {
        inBufCh1  = inBuf[ ch ];
        outBufCh1  = outBuf[ ch ];
        ch2      = ch + fullChannels;
        outBufCh2  = outBuf[ch2];
        ch2       += fullChannels;
        outBufCh3  = outBuf[ ch2 ];

        for( j = outOff, stop = outOff + len, k = 0; j < stop; j++ ) {
          f5 = inBufCh1[ k++ ];
          f1 = f5;
          f2 = f5;
          f3 = f5 * f5;
          for( m = 1; m < decim; m++ ) {
            f5 = inBufCh1[ k++ ];
            if( f5 > f1 ) f1 = f5;
            if( f5 < f2 ) f2 = f5;
            f3 += f5 * f5;
          }
          outBufCh1[ j = f1;      // positive halfwave peak
          outBufCh2[ j = f2;      // negative halfwave peak
          outBufCh3[ j = f3 / decim;  // fullwave mean square
        }
      } // for( ch )
    }
  } // class FullPeakRMSDecimator

  private static class DecimatedStake
//  extends BasicStake
  {
    private final InterleavedStreamFile[]  fs;
    private final Span[]          fileSpans;
//     XXX SWINGOSC XXX
//    private final Span[]          maxFileSpans;
    private final MutableLong[]        framesWritten;
    private final Span[]          biasedSpans;
    private final DecimationHelp[]      decimations;
// XXX SWINGOSC XXX
//    private final int            SUBNUM;
   
    // XXX SWINGOSC XXX
    private final Span            span;

    public DecimatedStake( Span span, InterleavedStreamFile[] fs, Span[] fileSpans, Span[] biasedSpans,
                 DecimationHelp[] decimations )
    {
      this( span, fs, fileSpans, fileSpans, null, biasedSpans, decimations );
    }
   
    private DecimatedStake( Span span, InterleavedStreamFile[] fs, Span[] fileSpans,
                Span[] maxFileSpans, MutableLong[] framesWritten, Span[] biasedSpans,
                DecimationHelp[] decimations )
    {
// XXX SWINGOSC XXX
//      super( span );
     
      this.span      = span;

      this.fs        = fs;
      this.fileSpans    = fileSpans;
// XXX SWINGOSC XXX
//      this.maxFileSpans  = maxFileSpans;
      if( framesWritten == null ) {
        this.framesWritten = new MutableLong[ fs.length ];
        for( int i = 0; i < fs.length; i++ ) this.framesWritten[ i ] = new MutableLong( 0L );
      } else {
        this.framesWritten  = framesWritten;
      }
      this.biasedSpans  = biasedSpans;
      this.decimations  = decimations;
     
// XXX SWINGOSC XXX
//      SUBNUM        = decimations.length;
    }
   
    // XXX SWINGOSC XXX
    public Span getSpan()
    {
      return span;
    }
   
    public void dispose()
    {
      // XXX
// XXX SWINGOSC XXX
//      super.dispose();
    }
   
//     XXX SWINGOSC XXX
//    public Stake duplicate()
//    {
//      return new DecimatedStake( span, fs, fileSpans, maxFileSpans, framesWritten, biasedSpans, decimations );
//    }
//
//    public Stake replaceStart( long newStart )
//    {
//      final Span[] newBiasedSpans  = new Span[ SUBNUM ];
//      final Span[] newFileSpans  = new Span[ SUBNUM ];
//      long testBias, newBiasedStart, delta;
//      DecimationHelp decim;
//     
//      for( int i = 0; i < SUBNUM; i++ ) {
//        decim        = decimations[ i ];
//        testBias      = biasedSpans[ i ].start + ((newStart - span.start + decim.roundAdd) & decim.mask) - newStart;
//        newBiasedStart    = newStart + (testBias < -decim.roundAdd ? testBias + decim.factor :
//                         (testBias > decim.roundAdd ? testBias - decim.factor : testBias));
//        delta        = (newBiasedStart - biasedSpans[ i ].start) >> decim.shift;
//        newBiasedSpans[ i ]  = biasedSpans[ i ].replaceStart( newBiasedStart );
//        newFileSpans[ i ]  = fileSpans[ i ].replaceStart( fileSpans[ i ].start + delta );
//        // XXX modify framesWritten ?
//      }
//      return new DecimatedStake( span.replaceStart( newStart ), fs, newFileSpans, maxFileSpans, framesWritten, newBiasedSpans, decimations );
//    }
//
//    public Stake replaceStop( long newStop )
//    {
//      final Span[] newBiasedSpans  = new Span[ SUBNUM ];
//      final Span[] newFileSpans  = new Span[ SUBNUM ];
//      long testBias, newBiasedStop, delta;
//      int  startBias;
//      DecimationHelp decim;
//
//      for( int i = 0; i < SUBNUM; i++ ) {
//        decim        = decimations[ i ];
//        startBias      = (int) (biasedSpans[ i ].start - span.start);
//        testBias      = (int) (((startBias + newStop + decim.roundAdd) & decim.mask) - newStop);
//        newBiasedStop    = newStop + (testBias < -decim.roundAdd ? testBias + decim.factor :
//                        (testBias > decim.roundAdd ? testBias - decim.factor : testBias));
//        newBiasedSpans[ i ]  = biasedSpans[ i ].replaceStop( newBiasedStop );
//        newFileSpans[ i ]  = fileSpans[ i ].replaceStop( fileSpans[ i ].start + newBiasedSpans[ i ].getLength() ); // XXX richtig?
//      }
//      return new DecimatedStake( span.replaceStop( newStop ), fs, newFileSpans, maxFileSpans, framesWritten, newBiasedSpans, decimations );
//    }
//
//    public Stake shiftVirtual( long delta )
//    {
//      final Span[] newBiasedSpans  = new Span[ SUBNUM ];
//
//      for( int i = 0; i < SUBNUM; i++ ) {
//        newBiasedSpans[ i ]  = biasedSpans[ i ].shift( delta );
//      }
//      return new DecimatedStake( span.shift( delta ), fs, fileSpans, maxFileSpans, framesWritten, newBiasedSpans, decimations );
//    }

    public void readFrames( int sub, float[][] data, int dataOffset, Span readSpan, MutableInt framesRead, MutableInt framesBusy )
    throws IOException
    {
      if( data.length == 0 ) {
        framesRead.set( 0 );
        framesBusy.set( 0 );
        return;
      }
     
      final DecimationHelp decim  = decimations[ sub ];
      final int  startBias    = (int) (biasedSpans[ sub ].start - span.start);
      final int  newStartBias  = (int) (((readSpan.start + decim.roundAdd) & decim.mask) - readSpan.start) + startBias;
      final long  newBiasedStart  = readSpan.start + (newStartBias < -decim.roundAdd ? newStartBias + decim.factor :
                               (newStartBias > decim.roundAdd ? newStartBias - decim.factor : newStartBias));
      final long  fOffset      = fileSpans[ sub ].start + ((newBiasedStart - (span.start + startBias)) >> decim.shift);
      final int  newStopBias    = (int) (((startBias + readSpan.stop + decim.roundAdd) & decim.mask) - readSpan.stop);
      final long  newBiasedStop  = readSpan.stop + (newStopBias < -decim.roundAdd ? newStopBias + decim.factor :
                              (newStopBias > decim.roundAdd ? newStopBias - decim.factor : newStopBias));
      final int  len        = (int) Math.min( data[0].length - dataOffset, (newBiasedStop - newBiasedStart) >> decim.shift );
      final int  readyLen;
     
      if( len <= 0 ) {
        framesRead.set( 0 );
        framesBusy.set( 0 );
        return;
      }

      synchronized( fs ) {
        readyLen = (int) Math.min( len, Math.max( 0, fileSpans[ sub ].start + framesWritten[ sub ].value() - fOffset ));
        if( readyLen > 0 ) {
          if( fs[ sub ].getFramePosition() != fOffset ) {
            fs[ sub ].seekFrame( fOffset );
          }
          fs[ sub ].readFrames( data, dataOffset, readyLen );
        }
      }
     
      framesRead.set( readyLen );
      framesBusy.set( len - readyLen );
    }

    public void continueWrite( int sub, float[][] data, int dataOffset, int len )
    throws IOException
    {
      if( len == 0 ) return; // return 0;
      synchronized( fs ) {
        final long  fOffset = fileSpans[ sub ].start + framesWritten[ sub ].value();
   
        if( (fOffset < fileSpans[ sub ].start) || ((fOffset + len) > fileSpans[ sub ].stop) ) {
          throw new IllegalArgumentException( fOffset + " ... " + (fOffset + len) + " not within " + fileSpans[ sub ].toString() );
        }

        if( fs[ sub ].getFramePosition() != fOffset ) {
          fs[ sub ].seekFrame( fOffset );
        }
        fs[ sub ].writeFrames( data, dataOffset, len );
       
        framesWritten[ sub ].set( framesWritten[ sub ].value() + len );
      }
    }

//    public void flush()
//    throws IOException
//    {
//      synchronized( fs ) {
//        for( int i = 0; i < fs.length; i++ ) {
//          fs[ i ].flush();
//        }
//      }
//    }

//    public void debugDump()
//    {
//      debugDumpBasics();
//      for( int i = 0; i < SUBNUM; i++ ) {
//        System.err.println( "  decim "+decimations[i].factor+" biased span "+biasedSpans[i].toString()+
//          "; f = " + fs[i].getFile().getName() + " (file span " + fileSpans[i].toString() + " )" );
//      }
//    }
//   
//    protected void debugDumpBasics()
//    {
//      System.err.println( "Span " + span.toString() );
//    }
  }
 
  private static class DecimationInfo
  {
    /**
     *  Internal index for MultirateTrackEditor
     */
    protected final int  idx;
    protected final int  shift;
    protected final int  inlineDecim;
    /**
     *  Time span (in fullrate frames) covered by this subsample
     */
    public final Span  span;
    /**
     *  Length (rounded) of the time span decimated through subsampling
     */
    public final long  sublength;
//    public final int  model;
//    public final int  channels;

    /**
     *  Creates a new <code>DecimationInfo</code>
     *  data structure with the given decimation.
     *
     *  @param  idx      internal index for <code>MultirateTrackEditor</code>
     @param  span    the originally covered time span
     *  @param  sublength   the translated span length in deimated
     *            frames (rounded to integer)
     */
    protected DecimationInfo( Span span, long sublength, int channels,
                  int idx, int shift, int inlineDecim, int model )
    {
      this.span      = span;
      this.sublength    = sublength;
//      this.channels    = channels;
      this.idx      = idx;
      this.shift      = shift;
      this.inlineDecim  = inlineDecim;
//      this.model      = model;
    }

    /**
     *  Returns the decimation
     *  rate factor.
     *
     *  @return the factor by which the full rate is decimated,
     *      that is, <code>decimatedRate = fullRate / returnedFactor</code>
     */
    public int getDecimationFactor()
    {
      return( (1<<shift) * inlineDecim );
    }
   
    public long getTotalLength()
    {
      return( (sublength * inlineDecim) << shift );
    }
  }
 
  private class Selection
  {
    protected Span    span      = new Span();
    protected boolean  editableStart  = true;
    protected boolean  editableSize  = true;
    protected Color    colr      = colrSelection;
   
    protected Selection() { /* empty */ }
  }
}
TOP

Related Classes of de.sciss.swingosc.SoundFileView$Decimator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.