/* * DecimatedTrail.java * Eisenkraut * * Copyright (c) 2004-2016 Hanns Holger Rutz. All rights reserved. * * This software is published under the GNU General Public License v3+ * * * For further information, please contact Hanns Holger Rutz at * contact@sciss.de */ package de.sciss.eisenkraut.io; import de.sciss.app.BasicEvent; import de.sciss.app.EventManager; import de.sciss.common.ProcessingThread; import de.sciss.eisenkraut.math.MathUtil; import de.sciss.io.AudioFile; import de.sciss.io.AudioFileDescr; import de.sciss.io.IOUtil; import de.sciss.io.Span; import de.sciss.timebased.BasicTrail; import de.sciss.timebased.Stake; import javax.swing.*; import javax.swing.undo.CompoundEdit; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public abstract class DecimatedTrail extends BasicTrail { protected static final boolean DEBUG = false; protected int SUBNUM; protected int MAXSHIFT; protected int MAXCOARSE; protected long MAXMASK; protected int MAXCEILADD; 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; public static final int MODEL_SONA = 10; protected int modelChannels; protected int decimChannels; protected int fullChannels; protected int model; protected AudioFile[] tempF = null; // lazy protected DecimationHelp[] decimHelps; protected AudioTrail fullScale; protected float[][] tmpBuf = null; // lazy protected int tmpBufSize; protected float[][] tmpBuf2 = null; // lazy protected int tmpBufSize2; protected static final Paint pntBusyDark; protected static final Paint pntBusyLight; private static final int[] busyPixelsDark = { 0xFF343434, 0xFF3F3F3F, 0xFF575757, 0xFF191919, 0xFF4D4D4D, 0xFF353535, 0xFF4E4E4E, 0xFF2A2A2A, 0xFF3F3F3F }; private static final int[] busyPixelsLight = { 0xFFCBCBCB, 0xFFC0C0C0, 0xFFA8A8A8, 0xFFE6E6E6, 0xFFB2B2B2, 0xFFCACACA, 0xFFB1B1B1, 0xFFD5D5D5, 0xFFC0C0C0 }; protected final Object bufSync = new Object(); protected final Object fileSync = new Object(); protected final List<Span> drawBusyList = new ArrayList<Span>(); protected Thread threadAsync = null; protected AudioFile[] tempFAsync = null; // lazy protected volatile boolean keepAsyncRunning = false; protected EventManager asyncManager = null; protected static final double TWENTYBYLOG10 = 20 / MathUtil.LN10; // 8.685889638065; protected static final double TENBYLOG10 = 10 / MathUtil.LN10; static { final BufferedImage imgDark = new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB); imgDark.setRGB(0, 0, 3, 3, busyPixelsDark, 0, 3); pntBusyDark = new TexturePaint(imgDark, new Rectangle(0, 0, 3, 3)); final BufferedImage imgLight = new BufferedImage(3, 3, BufferedImage.TYPE_INT_ARGB); imgLight.setRGB(0, 0, 3, 3, busyPixelsLight, 0, 3); pntBusyLight = new TexturePaint(imgLight, new Rectangle(0, 0, 3, 3)); } protected final boolean isDark = UIManager.getBoolean("dark-skin"); protected final Paint pntBusy = isDark ? pntBusyDark : pntBusyLight; protected DecimatedTrail() { super(); } protected BasicTrail createEmptyCopy() { throw new IllegalStateException( "Not allowed" ); } public final int getDefaultTouchMode() { return TOUCH_SPLIT; } public final int getChannelNum() { return decimChannels; } public final int getNumModelChannels() { return modelChannels; } public final int getNumDecimations() { return SUBNUM; } public final int getModel() { return model; } public abstract DecimationInfo getBestSubsample( Span tag, int minLen ); protected static void setProgression(long len, double progWeight) throws ProcessingThread.CancelledException { ProcessingThread.update((float) (len * progWeight)); } protected static void flushProgression() { ProcessingThread.flushProgression(); } protected final void killAsyncThread() { if (threadAsync != null) { synchronized (threadAsync) { if (threadAsync.isAlive()) { keepAsyncRunning = false; try { threadAsync.wait(); } catch (InterruptedException e1) { System.err.println("DecimatedTrail - killAsyncThread:"); e1.printStackTrace(); } } } } } protected void removeAllDep(Object source, List<Stake> stakes, CompoundEdit ce, Span union) { if (DEBUG) System.err.println("removeAllDep " + union.toString()); throw new IllegalArgumentException("n.y.i."); } protected abstract File[] createCacheFileNames(); protected int[][] createCacheChannelMaps() { final int[][] fullChanMaps = fullScale.getChannelMaps(); final int[][] cacheChanMaps = new int[ fullChanMaps.length ][]; for( int i = 0; i < fullChanMaps.length; i++ ) { // System.out.println( "fullChanMaps[ " + i + " ] = " ); // for( int k = 0; k < fullChanMaps[ i ].length; k++ ) { // System.out.println( " " + fullChanMaps[ i ][ k ]); // } cacheChanMaps[ i ] = new int[ fullChanMaps[ i ].length * modelChannels ]; for( int j = 0; j < cacheChanMaps[ i ].length; j++ ) { cacheChanMaps[ i ][ j ] = j; } // System.out.println( "cacheChanMaps[ " + i + " ] = " ); // for( int k = 0; k < cacheChanMaps[ i ].length; k++ ) { // System.out.println( " " + cacheChanMaps[ i ][ k ]); // } } return cacheChanMaps; } // @synchronization caller must have sync on fileSync !!! protected DecimatedStake alloc(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 (tempF == null) { tempF = createTempFiles(); // XXX sync } synchronized (tempF) { for (int i = 0; i < SUBNUM; i++) { fileStart = tempF[i].getFrameNum(); fileStop = fileStart + (extSpan.getLength() >> decimHelps[i].shift); tempF[i].setFrameNum(fileStop); fileSpans[i] = new Span(fileStart, fileStop); biasedSpans[i] = extSpan; } } return new DecimatedStake( extSpan, tempF, fileSpans, biasedSpans, decimHelps ); } // @synchronization caller must have sync on fileSync !!! protected 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); } protected AudioFile[] createTempFiles() throws IOException { // simply use an AIFC file with float format as temp file final AudioFileDescr proto = new AudioFileDescr(); final AudioFile[] tempFiles = new AudioFile[ SUBNUM ]; AudioFileDescr afd; proto.type = AudioFileDescr.TYPE_AIFF; proto.channels = decimChannels; proto.bitsPerSample = 32; proto.sampleFormat = AudioFileDescr.FORMAT_FLOAT; // proto.bitsPerSample = 8; // proto.sampleFormat = AudioFileDescr.FORMAT_INT; try { for( int i = 0; i < SUBNUM; i++ ) { afd = new AudioFileDescr( proto ); afd.file = IOUtil.createTempFile(); afd.rate = decimHelps[ i ].rate; tempFiles[ i ] = AudioFile.openAsWrite( afd ); } return tempFiles; } catch( IOException e1 ) { for( int i = 0; i < SUBNUM; i++ ) { if( tempFiles[ i ] != null ) tempFiles[ i ].cleanUp(); } throw e1; } } protected void deleteTempFiles(AudioFile[] tempFiles) { for (AudioFile tempFile : tempFiles) { if (tempFile != null) { tempFile.cleanUp(); tempFile.getFile().delete(); } } } protected void freeTempFiles() { synchronized( fileSync ) { if( tempF != null ) { deleteTempFiles( tempF ); } // XXX THIS IS THE PLACE TO KEEP WAVEFORM CACHE FILE if( tempFAsync != null ) { deleteTempFiles( tempFAsync ); } } } /* * @synchronization call within synchronoized( bufSync ) block */ protected void createBuffers() { if( !Thread.holdsLock( bufSync )) throw new IllegalMonitorStateException(); if( tmpBuf == null ) { //System.out.println( "tmpBuf : [ " + fullChannels + " ][ " + tmpBufSize + " ]" ); //System.out.println( "tmpBuf2 : [ " + decimChannels + " ][ " + tmpBufSize2 + " ]" ); tmpBuf = new float[ fullChannels ][ tmpBufSize ]; tmpBuf2 = new float[ decimChannels ][ tmpBufSize2 ]; } } protected void freeBuffers() { synchronized( bufSync ) { tmpBuf = null; tmpBuf2 = null; } } // ----------- dependant implementation ----------- public void dispose() { //System.err.println( "DecimatedTrail.dispose() " + this ); killAsyncThread(); // this has to be the first step fullScale.removeDependant( this ); freeBuffers(); freeTempFiles(); super.dispose(); } public final boolean isBusy() { return( (threadAsync != null) && threadAsync.isAlive() ); } public final void addAsyncListener( AsyncListener l ) { if( !isBusy() ) { l.asyncFinished( new AsyncEvent( this, AsyncEvent.FINISHED, System.currentTimeMillis(), this )); return; } if( asyncManager == null ) { asyncManager = new EventManager( new EventManager.Processor() { public void processEvent( BasicEvent e ) { final AsyncEvent ae = (AsyncEvent) e; AsyncListener al; for( int i = 0; i < asyncManager.countListeners(); i++ ) { al = (AsyncListener) asyncManager.getListener( i ); switch( e.getID() ) { case AsyncEvent.UPDATE: al.asyncUpdate( ae ); break; case AsyncEvent.FINISHED: al.asyncFinished( ae ); break; default: assert false : e.getID(); break; } } } }); } asyncManager.addListener( l ); } public final void removeAsyncListener( AsyncListener l ) { if( asyncManager != null ) asyncManager.removeListener( l ); } // ---------------------- internal classes and interfaces ---------------------- public static interface AsyncListener { public void asyncFinished( AsyncEvent e ); public void asyncUpdate( AsyncEvent e ); } @SuppressWarnings("serial") public static class AsyncEvent extends BasicEvent { protected static final int UPDATE = 0; protected static final int FINISHED = 1; private final DecimatedTrail t; protected AsyncEvent( Object source, int id, long when, DecimatedTrail t ) { super( source, id, when ); this.t = t; } public DecimatedTrail getDecimatedTrail() { return t; } public boolean incorporate(BasicEvent oldEvent) { return (oldEvent instanceof AsyncEvent) && (this.getSource() == oldEvent.getSource()) && (this.getID() == oldEvent.getID()) && (t == ((AsyncEvent) oldEvent).t); } } }