/* * Session.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.session; import java.awt.EventQueue; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.swing.JOptionPane; import de.sciss.eisenkraut.Main; import de.sciss.eisenkraut.edit.BasicCompoundEdit; import de.sciss.eisenkraut.edit.EditSetTimelineLength; import de.sciss.eisenkraut.edit.TimelineVisualEdit; import de.sciss.eisenkraut.edit.UndoManager; import de.sciss.eisenkraut.gui.BlendingAction; import de.sciss.eisenkraut.io.AudioTrail; import de.sciss.eisenkraut.io.BlendContext; import de.sciss.eisenkraut.io.DecimatedSonaTrail; import de.sciss.eisenkraut.io.DecimatedTrail; import de.sciss.eisenkraut.io.DecimatedWaveTrail; import de.sciss.eisenkraut.io.MarkerTrail; import de.sciss.eisenkraut.net.OSCRoot; import de.sciss.eisenkraut.net.OSCRouter; import de.sciss.eisenkraut.net.OSCRouterWrapper; import de.sciss.eisenkraut.net.RoutedOSCMessage; import de.sciss.eisenkraut.realtime.Transport; import de.sciss.eisenkraut.render.FilterDialog; import de.sciss.eisenkraut.render.RenderPlugIn; import de.sciss.eisenkraut.render.Replace; import de.sciss.eisenkraut.timeline.AudioTrack; import de.sciss.eisenkraut.timeline.AudioTracks; import de.sciss.eisenkraut.timeline.MarkerTrack; import de.sciss.eisenkraut.timeline.Timeline; import de.sciss.eisenkraut.timeline.Track; import de.sciss.gui.GUIUtil; import de.sciss.gui.MenuAction; import de.sciss.gui.ParamField; import de.sciss.gui.SpringPanel; import de.sciss.io.AudioFile; import de.sciss.io.AudioFileDescr; import de.sciss.io.Span; import de.sciss.timebased.Trail; import de.sciss.util.DefaultUnitTranslator; import de.sciss.util.Flag; import de.sciss.util.Param; import de.sciss.util.ParamSpace; import de.sciss.app.AbstractApplication; import de.sciss.app.AbstractCompoundEdit; import de.sciss.common.BasicDocument; import de.sciss.common.BasicWindowHandler; import de.sciss.common.ProcessingThread; public class Session extends BasicDocument implements OSCRouter { private final AudioFileDescr displayAFD; private AudioFileDescr[] afds; private String name; public final Timeline timeline; protected AudioTrail at = null; private DecimatedWaveTrail dwt = null; private DecimatedSonaTrail dst = null; public final MarkerTrail markers; public final MarkerTrack markerTrack; private static final int[] waveDecims = { 8, 12, 16 }; // private static final int[] sonaDecims = { 0, 8 /*, 16 */ }; /** * Bitmask for putting a lock on the <code>timeline</code> */ public static final int DOOR_TIME = 0x01; /** * Bitmask for putting a lock on a <code>AudioTrail</code> */ public static final int DOOR_MTE = 0x02; /** * Bitmask for putting a lock on the channel tracks */ public static final int DOOR_TRACKS = 0x04; public static final int DOOR_ALL = DOOR_TIME | DOOR_TRACKS | DOOR_MTE; public final SessionCollection tracks = new SessionCollection(); // should be tracking audioTracks automatically // public final SessionCollection audioTracks = new SessionCollection(); public final AudioTracks audioTracks; public final SessionCollection selectedTracks = new SessionCollection(); private Transport transport = null; private DocumentFrame frame = null; // --- actions --- private final ActionSave actionSave; private final ActionCut actionCut; protected final ActionCopy actionCopy; private final ActionPaste actionPaste; private final ActionDelete actionDelete; private final ActionSilence actionSilence; private final ActionTrim actionTrim; // --- --- private final UndoManager undo = new UndoManager( this ); private boolean dirty = false; public static final int EDIT_INSERT = 0; public static final int EDIT_OVERWRITE = 1; public static final int EDIT_MIX = 2; private static final String[] EDITMODES = { "insert", "overwrite", "mix" }; private int editMode = EDIT_INSERT; private static int nodeIDAlloc = 0; private final int nodeID; private final OSCRouterWrapper osc; private final BlendingAction blending; protected ProcessingThread pt = null; private Session(AudioFileDescr[] afds, boolean createOSC) throws IOException { this(afds, null, createOSC); } private Session(AudioFileDescr afd, boolean createOSC) throws IOException { this(new AudioFileDescr[]{afd}, afd, createOSC); } private Session(AudioFileDescr[] afds, AudioFileDescr displayAFD, boolean createOSC) throws IOException { super(); this.afds = afds; if( displayAFD == null ) { this.displayAFD = new AudioFileDescr(); autoCreateDisplayDescr(); } else { this.displayAFD = displayAFD; name = displayAFD.file == null ? null : displayAFD.file.getName(); } // System.out.println( "name = '" + name + "'" ); nodeID = ++nodeIDAlloc; if( createOSC ) { osc = new OSCRouterWrapper( null, this ); } else { osc = null; } timeline = new Timeline( this ); markerTrack = new MarkerTrack( this ); markers = (MarkerTrail) markerTrack.getTrail(); markers.copyFromAudioFile( afds[ 0 ]); // XXX tracks.add( null, markerTrack ); audioTracks = new AudioTracks( this ); actionSave = new ActionSave(); actionCut = new ActionCut(); actionCopy = new ActionCopy(); actionPaste = new ActionPaste(); actionDelete = new ActionDelete(); actionSilence = new ActionSilence(); actionTrim = new ActionTrim(); timeline.setRate( this, this.displayAFD.rate ); timeline.setLength( this, this.displayAFD.length ); timeline.setVisibleSpan( this, new Span( 0, this.displayAFD.length )); selectedTracks.add( this, markerTrack ); blending = new BlendingAction( timeline, null ); } /** * Checks if a process is currently running. This method should be called * before launching a process using the <code>start()</code> method. * If a process is ongoing, this method waits for a default timeout period * for the thread to finish. * * @return <code>true</code> if a new process can be launched, <code>false</code> * if a previous process is ongoing and a new process cannot be launched * @throws IllegalMonitorStateException if called from outside the event thread */ public boolean checkProcess() { return checkProcess( 500 ); } /** * Checks if a process is currently running. This method should be called * before launching a process using the <code>start()</code> method. * If a process is ongoing, this method waits for a given timeout period * for the thread to finish. * * @param timeout the maximum duration in milliseconds to wait for an ongoing process * @return <code>true</code> if a new process can be launched, <code>false</code> * if a previous process is ongoing and a new process cannot be launched * @throws IllegalMonitorStateException if called from outside the event thread */ public boolean checkProcess(int timeout) { if (!EventQueue.isDispatchThread()) throw new IllegalMonitorStateException(); if (pt == null) return true; if (timeout == 0) return false; pt.sync( timeout ); return( (pt == null) || !pt.isRunning() ); } public void cancelProcess(boolean sync) { if (!EventQueue.isDispatchThread()) throw new IllegalMonitorStateException(); if (pt == null) return; pt.cancel(sync); } public String getProcessName() { if (!EventQueue.isDispatchThread()) throw new IllegalMonitorStateException(); if (pt == null) return null; return pt.getName(); } /** * Starts a <code>ProcessingThread</code>. Only one thread * can exist at a time. To ensure that no other thread is running, * call <code>checkProcess()</code>. * * @throws IllegalMonitorStateException if called from outside the event thread * @throws IllegalStateException if another process is still running * @see #checkProcess() */ public void start( ProcessingThread process ) { if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException(); if( this.pt != null ) throw new IllegalStateException( "Process already running" ); pt = process; pt.addListener( new ProcessingThread.Listener() { public void processStarted( ProcessingThread.Event e ) { /* empty */ } public void processStopped( ProcessingThread.Event e ) { pt = null; } }); pt.start(); } public BlendingAction getBlendingAction() { return blending; } public int getNodeID() { return nodeID; } public void setEditMode( int mode ) { editMode = mode; } public int getEditMode() { return editMode; } // public Session( AudioFileDescr[] afds, AudioFileDescr displayAFD ) // { // this.displayAFD = displayAFD; // this.afds = afds; // init(); // } // public Session( AudioFile af ) // { // this.afd = af.getDescr(); // init(); // } // public void createOSC() // { // if( osc != null ) throw new IllegalStateException( "OSC already exists" ); // osc = new OSCRouterWrapper( null, this ); // } public void createTransport() { if( transport != null ) throw new IllegalStateException( "Transport already exists" ); transport = new Transport( this ); } public Transport getTransport() { return transport; } public void createFrame() { if( frame != null ) throw new IllegalStateException( "Frame already exists" ); frame = new DocumentFrame( this ); } public DocumentFrame getFrame() { return frame; } // public void setFrame( DocumentFrame frame ) // { // this.frame = frame; // } // public void clear( Object source ) // throws IOException // { // timeline.clear( source ); // selectedTracks.clear( source ); // audioTracks.clear( source ); // tracks.clear( source ); // if( mte != null ) { // mte.clear( null ); // } // markers.clear( source ); // updateTitle(); // } // pausing dispatcher is up to the caller! // should ensure that old mte was cleared! // public void setAudioTrail( Object source, AudioTrail mte ) private void setAudioTrail( Object source, AudioTrail at ) { this.at = at; // tracks.pauseDispatcher(); // audioTracks.clear( source ); if( !audioTracks.isEmpty() ) throw new IllegalStateException( "Cannot call repeatedly" ); final List<Track> collNewTracks = new ArrayList<Track>(); final int numChannels = at.getChannelNum(); final double deltaAngle = 360.0 / numChannels; final double startAngle = numChannels < 2 ? 0.0 : -deltaAngle/2; // reasonable for mono to octo AudioTrack t; audioTracks.setTrail(at); for (int ch = 0; ch < at.getChannelNum(); ch++) { // t = new AudioTrack( at, ch ); t = new AudioTrack(audioTracks, ch); t.setName(String.valueOf(ch + 1)); t.getMap().putValue(source, AudioTrack.MAP_KEY_PANAZIMUTH, startAngle + ch * deltaAngle); collNewTracks.add(t); } audioTracks .addAll(source, collNewTracks); tracks .addAll(source, collNewTracks); selectedTracks.addAll(source, collNewTracks); // tracks.resumeDispatcher(); // dispatchChange( source ); updateTitle(); } public AudioTrail getAudioTrail() { return at; } public DecimatedWaveTrail getDecimatedWaveTrail() { return dwt; } public DecimatedSonaTrail getDecimatedSonaTrail() { return dst; } public void setDescr( AudioFileDescr[] afds ) { this.afds = afds; autoCreateDisplayDescr(); updateTitle(); } private void autoCreateDisplayDescr() { if( afds.length == 0 ) { displayAFD.file = null; name = null; } else { final AudioFileDescr proto = afds[ 0 ]; displayAFD.type = proto.type; displayAFD.rate = proto.rate; displayAFD.bitsPerSample = proto.bitsPerSample; displayAFD.sampleFormat = proto.sampleFormat; displayAFD.length = proto.length; displayAFD.channels = proto.channels; if( proto.file == null ) { displayAFD.file = null; name = null; } else { final String pname = proto.file.getName(); int left = pname.length(); int right = pname.length(); String name2; int trunc; displayAFD.type = proto.type; displayAFD.rate = proto.rate; displayAFD.bitsPerSample = proto.bitsPerSample; displayAFD.sampleFormat = proto.sampleFormat; displayAFD.length = proto.length; displayAFD.channels = proto.channels; for( int i = 1; i < afds.length; i++ ) { name2 = afds[ i ].file.getName(); displayAFD.channels += afds[ i ].channels; for( trunc = 0; trunc < Math.min( name2.length(), left ); trunc++ ) { if( !(name2.charAt( trunc ) == pname.charAt( trunc ))) break; } left = trunc; for( trunc = 0; trunc < Math.min( name2.length(), right ); trunc++ ) { if( !(name2.charAt( name2.length() - trunc - 1 ) == pname.charAt( pname.length() - trunc - 1 ))) break; } right = trunc; // System.out.println( "for '" + name2 + "' left = "+left+"; right = "+right ); } if( left >= pname.length() - right ) { displayAFD.file = afds[ 0 ].file; name = displayAFD.file.getName(); } else { final StringBuilder strBuf = new StringBuilder(); strBuf.append( pname.substring( 0, left )); for( int i = 0; i < afds.length; i++ ) { strBuf.append( i == 0 ? '[' : ',' ); name2 = afds[ i ].file.getName(); strBuf.append( name2.substring( left, name2.length() - right )); } strBuf.append( ']' ); final int idxBraClose = strBuf.length(); strBuf.append( pname.substring( pname.length() - right )); displayAFD.file = new File( afds[ 0 ].file.getParentFile(), strBuf.toString() ); if( (idxBraClose - left) > 25 ) { // strBuf.delete( left + 12, strBuf.length() - 13 ); // strBuf.insert( left + 12, '…' ); strBuf.replace( left + 12, idxBraClose - 13, "\u2026" ); } name = strBuf.toString(); } } } } public DecimatedWaveTrail createDecimatedWaveTrail() throws IOException { if( dwt == null ) { dwt = new DecimatedWaveTrail( at, DecimatedTrail.MODEL_FULLWAVE_PEAKRMS, waveDecims ); } return dwt; } public DecimatedSonaTrail createDecimatedSonaTrail() throws IOException { if( dst == null ) { dst = new DecimatedSonaTrail( at, DecimatedTrail.MODEL_SONA /*, sonaDecims */ ); } return dst; } public AudioFileDescr getDisplayDescr() { return displayAFD; } public String getName() { return name; } public AudioFileDescr[] getDescr() { return afds; } private void updateTitle() { if( frame != null ) frame.updateTitle(); } public ProcessingThread procDelete( String procName, Span span, int mode ) { return actionDelete.initiate( procName, span, mode ); } public ProcessingThread procSave( String procName, Span span, AudioFileDescr[] targetAFDs, int[] channelMap, boolean saveMarkers, boolean asCopy ) { return actionSave.initiate( procName, span, targetAFDs, channelMap, saveMarkers, asCopy ); } public MenuAction getCutAction() { return actionCut; } public MenuAction getCopyAction() { return actionCopy; } public MenuAction getPasteAction() { return actionPaste; } public MenuAction getDeleteAction() { return actionDelete; } public MenuAction getSilenceAction() { return actionSilence; } public MenuAction getTrimAction() { return actionTrim; } public ProcessingThread insertSilence(long pos, long numFrames) { return actionSilence.initiate(pos, numFrames); } public ProcessingThread closeDocument(boolean force, Flag wasClosed) { return frame.closeDocument(force, wasClosed); // XXX should be in here not frame!!! } public ClipboardTrackList getSelectionAsTrackList() { return actionCopy.getSelectionAsTrackList(); } public ProcessingThread pasteTrackList(ClipboardTrackList tl, long insertPos, String procName, int mode) { return actionPaste.initiate(tl, insertPos, procName, mode); } public static Session newEmpty(AudioFileDescr afd) throws IOException { return newEmpty(afd, true, true); } // NOTE: does not add the document to a handler public static Session newEmpty(AudioFileDescr afd, boolean createTransport, boolean createOSC) throws IOException { final Session doc = new Session(afd, createOSC); final AudioTrail at = AudioTrail.newFrom(afd); // does _not_ throw an IOException doc.setAudioTrail(null, at); if (createTransport) doc.createTransport(); return doc; } public static Session newFrom(File path) throws IOException { return newFrom(path, true, true); } public static Session newFrom(File path, boolean createTransport, boolean createOSC) throws IOException { final AudioFile af = AudioFile.openAsRead( path ); final AudioFileDescr afd = af.getDescr(); Session doc; AudioTrail at = null; try { af.readMarkers(); at = AudioTrail.newFrom( af ); doc = new Session( afd, createOSC ); doc.setAudioTrail( null, at ); if( createTransport ) doc.createTransport(); return doc; } catch (IOException e1) { if (at != null) { at.dispose(); } else { af.cleanUp(); } throw e1; } } public static Session newFrom(File[] paths) throws IOException { return newFrom(paths, true, true); } public static Session newFrom(File[] paths, boolean createTransport, boolean createOSC) throws IOException { final AudioFile[] afs = new AudioFile[ paths.length ]; final AudioFileDescr[] afds = new AudioFileDescr[ paths.length ]; AudioTrail at = null; Session doc; try { for( int i = 0; i < paths.length; i++ ) { afs[ i ] = AudioFile.openAsRead( paths[ i ]); afds[ i ] = afs[ i ].getDescr(); if( i > 0 ) { if( (afds[ i ].length != afds[ 0 ].length) || (afds[ i ].rate != afds[ 0 ].rate) || (afds[ i ].bitsPerSample != afds[ 0 ].bitsPerSample) || (afds[ i ].sampleFormat != afds[ 0 ].sampleFormat) ) { throw new IOException( getResourceString( "errHeadersNotMatching" )); } } afs[ i ].readMarkers(); } at = AudioTrail.newFrom( afs ); doc = new Session( afds, createOSC ); doc.setAudioTrail( null, at ); if( createTransport ) doc.createTransport(); return doc; } catch( IOException e1 ) { if( at != null ) { at.dispose(); } else { for( int i = 0; i < paths.length; i++ ) { if( afs[ i ] != null ) afs[ i ].cleanUp(); } } throw e1; } } protected static String getResourceString(String key) { return AbstractApplication.getApplication().getResourceString(key); } public BlendContext createBlendContext(long maxLeft, long maxRight, boolean hasSelectedAudio) { if (!hasSelectedAudio || ((maxLeft == 0L) && (maxRight == 0L))) { return null; } else { return blending.createBlendContext(maxLeft, maxRight); } } protected void discardEditsAndClipboard() { undo.discardAllEdits(); ClipboardTrackList.disposeAll(this); } // ------------- OSCRouter interface ------------- public String oscGetPathComponent() { return null; // special schoko in doc handler } public void oscRoute(RoutedOSCMessage rom) { osc.oscRoute(rom); } public void oscAddRouter(OSCRouter subRouter) { if (osc != null) osc.oscAddRouter(subRouter); } public void oscRemoveRouter(OSCRouter subRouter) { if (osc != null) osc.oscRemoveRouter(subRouter); } public void oscCmd_close(RoutedOSCMessage rom) { if (frame == null) { OSCRoot.failed(rom.msg, getResourceString("errWindowNotFound")); } final ProcessingThread proc; final boolean force; try { force = rom.msg.getArgCount() > 1 && ((Number) rom.msg.getArg(1)).intValue() != 0; proc = closeDocument(force, new Flag(false)); if (proc != null) start(proc); } catch( IndexOutOfBoundsException e1 ) { OSCRoot.failedArgCount( rom ); } catch( ClassCastException e1 ) { OSCRoot.failedArgType( rom, 1 ); } } public void oscCmd_activate(RoutedOSCMessage rom) { if (frame == null) { OSCRoot.failed(rom.msg, getResourceString("errWindowNotFound")); } frame.setVisible(true); frame.toFront(); } public void oscCmd_cut(RoutedOSCMessage rom) { actionCut.perform(); } public void oscCmd_copy(RoutedOSCMessage rom) { actionCopy.perform(); } public void oscCmd_paste(RoutedOSCMessage rom) { actionPaste.perform(); } public void oscCmd_delete(RoutedOSCMessage rom) { actionDelete.perform(); } /** * "insertSilence", <numFrames> */ public void oscCmd_insertSilence(RoutedOSCMessage rom) { final long pos, numFrames; final ProcessingThread proc; int argIdx = 1; // if( !bird.attemptShared( DOOR_TIME, 250 )) return; try { pos = timeline.getPosition(); numFrames = Math.max( 0, Math.min( timeline.getLength() - pos, ((Number) rom.msg.getArg( argIdx )).longValue() )); } catch( IndexOutOfBoundsException e1 ) { OSCRoot.failedArgCount( rom ); return; } catch( ClassCastException e1 ) { OSCRoot.failedArgType( rom, argIdx ); return; } // finally { // bird.releaseExclusive( Session.DOOR_TIME ); // } proc = actionSilence.initiate( pos, numFrames ); if( proc != null ) start( proc ); } public void oscCmd_trim(RoutedOSCMessage rom) { actionTrim.perform(); } /** * "editMode", <modeName> */ public void oscCmd_editMode(RoutedOSCMessage rom) { final String mode; int argIdx = 1; try { mode = rom.msg.getArg( argIdx ).toString(); for( int i = 0; i < EDITMODES.length; i++ ) { if( EDITMODES[ i ].equals( mode )) { setEditMode( i ); break; } } } catch( IndexOutOfBoundsException e1 ) { OSCRoot.failedArgCount( rom ); } catch( ClassCastException e1 ) { OSCRoot.failedArgType( rom, argIdx ); } } /** * Replaces the currently selected span with * the contents of a given audio file, applying * blending if activated. * * "replace", <fileName>[, <fileOffset> ] * * @param rom ... */ public void oscCmd_replace(RoutedOSCMessage rom) { final RenderPlugIn plugIn; final String fileName; final long startFrame; AudioFile af = null; int argIdx = 1; FilterDialog filterDlg; try { fileName = rom.msg.getArg( argIdx ).toString(); argIdx++; if( argIdx < rom.msg.getArgCount() ) { startFrame = ((Number) rom.msg.getArg( argIdx )).longValue(); } else { startFrame = 0; } af = AudioFile.openAsRead( new File( fileName )); af.seekFrame( startFrame ); plugIn = new Replace( af ); } catch( IndexOutOfBoundsException e1 ) { OSCRoot.failedArgCount( rom ); return; } catch( ClassCastException e1 ) { OSCRoot.failedArgType( rom, argIdx ); return; } catch( IOException e1 ) { if( af != null ) af.cleanUp(); OSCRoot.failed( rom, e1 ); return; } filterDlg = (FilterDialog) AbstractApplication.getApplication().getComponent( Main.COMP_FILTER ); if( filterDlg == null ) { filterDlg = new FilterDialog(); } filterDlg.process( plugIn, this, false, false ); // actionProcessAgain.setPlugIn( filterDlg.getPlugIn() ); } public Object oscQuery_id() { return getNodeID(); } public Object oscQuery_dirty() { return isDirty() ? 1 : 0; } public Object oscQuery_editMode() { return EDITMODES[getEditMode()]; } public Object oscQuery_name() { return getName(); } public Object oscQuery_file() { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < afds.length; i++) { if (i > 0) sb.append(File.pathSeparator); sb.append(afds[i].file.getAbsolutePath()); } return sb.toString(); } // ---------------- Document interface ---------------- public de.sciss.app.Application getApplication() { return AbstractApplication.getApplication(); } public de.sciss.app.UndoManager getUndoManager() { return undo; } public void dispose() { //System.err.println( "Session.dispose(). hashCode = "+hashCode()+"; frame hash = "+(frame == null ? 0 : frame.hashCode()) ); discardEditsAndClipboard(); if( osc != null ) { osc.remove(); // osc = null; } if( transport != null ) { transport.quit(); transport.dispose(); transport = null; } if( frame != null ) { // frame.setVisible( false ); frame.dispose(); frame = null; } if( at != null ) { at.dispose(); // mte.clear( null ); at = null; } } public boolean isDirty() { return dirty; } public void setDirty( boolean dirty ) { if( !this.dirty == dirty ) { this.dirty = dirty; updateTitle(); } } // ------------------ internal classes ------------------ private class ActionSave implements ProcessingThread.Client { protected ActionSave() { /* empty */ } /** * Initiate the save process. * Transport is stopped before, if it was running. * On success, undo history is purged and * <code>setModified</code> and <code>updateTitle</code> * are called, and the file is added to * the Open-Recent menu. Note that returned * process has not yet been started, as to allow * other objects to add listeners. So it's the * job of the caller to invoke the processing thread's * <code>start</code> method. */ protected ProcessingThread initiate(String procName, Span span, AudioFileDescr[] descrs, int[] channelMap, boolean saveMarkers, boolean asCopy) { final ProcessingThread proc; getTransport().stop(); if( !checkProcess() ) return null; // pt = new ProcessingThread( this, getFrame(), bird, name, args, Session.DOOR_ALL ); proc = new ProcessingThread( this, getFrame(), procName ); proc.putClientArg( "afds", descrs ); proc.putClientArg( "doc", Session.this ); proc.putClientArg( "asCopy", asCopy); proc.putClientArg( "chanMap", channelMap ); proc.putClientArg( "markers", saveMarkers); proc.putClientArg( "span", span == null ? new Span( 0, timeline.getLength() ) : span ); return proc; } /** * - wenn das audio file format marker unterstuetzt, kopiere die marker in das erste file * - fuer jedes file: wenn ein file gleichen namens existiert, erzeuge zunaechst ein temporaeres file * - oeffne alle files zum schreiben (AudioFile.openAsWrite) * - schreibe alle files (at.flatten) * - liefere ein array aller geschriebener files in client arg "afs" */ public int processRun( ProcessingThread context ) throws IOException { final AudioFileDescr[] clientAFDs = (AudioFileDescr[]) context.getClientArg( "afds" ); final int numFiles = clientAFDs.length; final Session doc = (Session) context.getClientArg( "doc" ); final boolean saveMarkers = (Boolean) context.getClientArg("markers"); final Span span = (Span) context.getClientArg( "span" ); final int[] channelMap = (int[]) context.getClientArg( "chanMap" ); final AudioTrail audioTrail = doc.getAudioTrail(); // final File[] tempFs = new File[ numFiles ]; // final boolean[] renamed = new boolean[ numFiles ]; final AudioFile[] afs = new AudioFile[ numFiles ]; AudioFileDescr afdTemp; File tempF; context.putClientArg( "afs", afs ); if( saveMarkers ) { if( clientAFDs[ 0 ].isPropertySupported( AudioFileDescr.KEY_MARKERS )) { doc.markers.copyToAudioFile( clientAFDs[ 0 ], span ); // XXX } else if( !doc.markers.isEmpty() ) { System.err.println( "WARNING: markers are not saved in this file format!!!" ); } } else { // WARNING: we must clear KEY_MARKERS, it might contain copied data! clientAFDs[ 0 ].setProperty( AudioFileDescr.KEY_MARKERS, null ); } for( int i = 0; i < numFiles; i++ ) { if( clientAFDs[ i ].file.exists() ) { // tempFs[ i ] = File.createTempFile( "eis", null, afds[ i ].file.getParentFile() ); // tempFs[ i ].delete(); tempF = File.createTempFile( "eis", null, clientAFDs[ i ].file.getParentFile() ); afdTemp = new AudioFileDescr( clientAFDs[ i ]); // afdTemp.file = tempFs[ i ]; afdTemp.file = tempF; // renamed[ i ] = true; afs[ i ] = AudioFile.openAsWrite( afdTemp ); } else { afs[ i ] = AudioFile.openAsWrite( clientAFDs[ i ]); } } audioTrail.flatten( afs, span, channelMap ); return DONE; } // run public void processFinished( ProcessingThread context ) { final AudioFileDescr[] clientAFDs = (AudioFileDescr[]) context.getClientArg( "afds" ); final AudioFile[] afs = (AudioFile[]) context.getClientArg( "afs" ); final Session doc = (Session) context.getClientArg( "doc" ); final boolean asCopy = (Boolean) context.getClientArg("asCopy"); File tempF; if( context.getReturnCode() == DONE ) { // ------------------------------- DONE ------------------------------- if( asCopy ) { // ............................... asCopy ............................... for( int i = 0; i < afs.length; i++ ) { try { afs[ i ].close(); } catch( IOException e1 ) { System.err.println( "File '" + afs[ i ].getFile().getName() + "' could not be closed ("+ e1.getClass().getName() + " : " + e1.getLocalizedMessage() + ")" ); } if( !clientAFDs[ i ].file.equals( afs[ i ].getFile() )) { if( clientAFDs[ i ].file.delete() ) { if( !afs[ i ].getFile().renameTo( clientAFDs[ i ].file )) { System.err.println( "Newly saved file '" + afs[ i ].getFile().getName() + "' "+ "could not be renamed!" ); } } else { System.err.println( "Previous file '" +clientAFDs[ i ].file.getAbsolutePath() + "' "+ "could not be deleted! Newly saved file is '" + afs[ i ].getFile().getName() + "'!" ); } } } } else { // ............................... replace ............................... // doc.getUndoManager().discardAllEdits(); doc.discardEditsAndClipboard(); // at.clear( null ); try { at.closeAll(); } catch( IOException e1 ) { System.err.println( "Previous audio files could not be closed ("+ e1.getClass().getName() + " : " + e1.getLocalizedMessage() + ")" ); } for( int i = 0; i < afs.length; i++ ) { final File f; try { afs[ i ].close(); } catch( IOException e1 ) { System.err.println( "File '" + afs[ i ].getFile().getName() + "' could not be closed ("+ e1.getClass().getName() + " : " + e1.getLocalizedMessage() + ")" ); } if( clientAFDs[ i ].file.equals( afs[ i ].getFile() )) { f = afs[ i ].getFile(); } else { if( clientAFDs[ i ].file.delete() ) { if( afs[ i ].getFile().renameTo( clientAFDs[ i ].file )) { f = clientAFDs[ i ].file; } else { System.err.println( "New current working file '" + afs[ i ].getFile().getName() + "' "+ "could not be renamed!" ); f = afs[ i ].getFile(); } } else tryRename: { try { tempF = File.createTempFile( "eis", null, clientAFDs[ i ].file.getParentFile() ); } catch( IOException e1 ) { System.err.println( "Previous file '" + clientAFDs[ i ].file.getAbsolutePath() + "' could neither be " + "deleted nor renamed. New current working file is '" + afs[ i ].getFile().getName() + "'!" ); f = afs[ i ].getFile(); break tryRename; } if( clientAFDs[ i ].file.renameTo( tempF )) { System.err.println( "Previous file '" + clientAFDs[ i ].file.getAbsolutePath() + "' could not be " + "deleted. It was renamed to '" + tempF.getName() + "'!" ); if( afs[ i ].getFile().renameTo( clientAFDs[ i ].file )) { f = clientAFDs[ i ].file; } else { System.err.println( "New current working file '" + afs[ i ].getFile().getName() + "' "+ "could not be renamed!" ); f = afs[ i ].getFile(); } } else { System.err.println( "Previous file '" + clientAFDs[ i ].file.getAbsolutePath() + "' could neither be " + "deleted nor renamed. New current working file is '" + afs[ i ].getFile().getName() + "'!" ); f = afs[ i ].getFile(); } } } try { afs[ i ] = AudioFile.openAsRead( f ); } catch( IOException e1 ) { System.err.println( "File '" + f.getName() + "' could not be opened ("+ e1.getClass().getName() + " : " + e1.getLocalizedMessage() + ")" ); } clientAFDs[ i ] = afs[ i ].getDescr(); } try { at.exchange( afs ); } catch( IOException e1 ) { System.err.println( "Audio files could not be exchanged ("+ e1.getClass().getName() + " : " + e1.getLocalizedMessage() + ")" ); } doc.setDescr( clientAFDs ); } } else { // ------------------------------- FAILED or CANCELLED ------------------------------- if( afs != null ) { for (AudioFile af : afs) { if (af != null) { af.cleanUp(); if (!af.getFile().delete()) { System.err.println("The file '" + af.getFile().getAbsolutePath() + "' " + "(created during saving) could not be deleted!"); } } } } } } public void processCancel( ProcessingThread context ) { /* ignored */ } } @SuppressWarnings("serial") private class ActionCut extends MenuAction { protected ActionCut() { /* empty */ } public void actionPerformed( ActionEvent e ) { perform(); } protected void perform() { final ProcessingThread proc; // = null; if( actionCopy.perform() ) { // if( !bird.attemptShared( Session.DOOR_TIME | Session.DOOR_MTE )) return; // try { proc = procDelete( getValue( NAME ).toString(), timeline.getSelectionSpan(), getEditMode() ); // } // finally { // bird.releaseShared( Session.DOOR_TIME | Session.DOOR_MTE ); // } if( proc != null ) start( proc ); } } } @SuppressWarnings("serial") private class ActionCopy extends MenuAction { protected ActionCopy() { /* empty */ } public void actionPerformed( ActionEvent e ) { perform(); } protected ClipboardTrackList getSelectionAsTrackList() { final Span span; // if( !bird.attemptShared( Session.DOOR_TIME | Session.DOOR_TRACKS, 250 )) return null; // try { span = timeline.getSelectionSpan(); if( span.isEmpty() ) return null; return new ClipboardTrackList( Session.this ); // } // finally { // bird.releaseShared( Session.DOOR_TIME | Session.DOOR_TRACKS ); // } } protected boolean perform() { boolean success = false; final ClipboardTrackList tl = getSelectionAsTrackList(); if (tl == null) return false; try { AbstractApplication.getApplication().getClipboard().setContents(tl, tl); success = true; } catch (IllegalStateException e1) { System.err.println(getResourceString("errClipboard")); } return success; } } @SuppressWarnings("serial") private class ActionPaste extends MenuAction implements ProcessingThread.Client { protected ActionPaste() { /* empty */ } public void actionPerformed( ActionEvent e ) { perform(); } protected void perform() { perform( getValue( NAME ).toString(), getEditMode() ); } private void perform( String procName, int mode ) { final Transferable t; final ClipboardTrackList tl; try { t = AbstractApplication.getApplication().getClipboard().getContents( this ); if( t == null ) return; if( !t.isDataFlavorSupported( ClipboardTrackList.trackListFlavor )) return; tl = (ClipboardTrackList) t.getTransferData( ClipboardTrackList.trackListFlavor ); } catch( IOException e11 ) { System.err.println( e11.getLocalizedMessage() ); return; } catch( UnsupportedFlavorException e11 ) { System.err.println( e11.getLocalizedMessage() ); return; } catch( IllegalStateException e11 ) { System.err.println( getResourceString( "errClipboard" )); return; } if( !checkProcess() ) return; final ProcessingThread proc = initiate( tl, timeline.getPosition(), procName, mode ); // XXX sync if( proc != null ) { start( proc ); } } protected ProcessingThread initiate( ClipboardTrackList tl, long insertPos, String procName, int mode ) { if( !checkProcess() ) return null; if( (insertPos < 0) || (insertPos > timeline.getLength()) ) throw new IllegalArgumentException( String.valueOf( insertPos )); final ProcessingThread proc; final Span oldSelSpan, insertSpan, copySpan, cutTimelineSpan; final AbstractCompoundEdit edit; final Flag hasSelectedAudio; final List<Track.Info> tis; final boolean expTimeline, cutTimeline; final long docLength, pasteLength, preMaxLen, postMaxLen; final BlendContext bcPre, bcPost; hasSelectedAudio = new Flag( false ); tis = Track.getInfos( selectedTracks.getAll(), tracks.getAll() ); if( !AudioTracks.checkSyncedAudio( tis, mode == EDIT_INSERT, null, hasSelectedAudio )) return null; expTimeline = (mode == EDIT_INSERT) && hasSelectedAudio.isSet(); docLength = timeline.getLength(); pasteLength = expTimeline ? tl.getSpan().getLength() : Math.min( tl.getSpan().getLength(), docLength - insertPos ); if( pasteLength == 0 ) return null; if( mode == EDIT_INSERT ) { /* * before paste: * * maxRight / post maxLeft / pre * * | | | * | | | * | | | * | A | B | * +-----------------+--------------+ * | * insertPos * * after paste: * * | | B #$$$$# A | | * | | ##$$$$## | | * | | ###$$$$### | | * | A |####$$$$####| B | * +-----------------+------------+--------------+ * | * insertPos */ // note: now the discrepancy between postMaxLen and preMaxLen is // limited to 100%, so pasting at the very end or beginning of // a doc will not produce a single sided xfade any more // (answering bug 1922862) if( insertPos < (docLength - insertPos) ) { postMaxLen = Math.min( insertPos, pasteLength >> 1 ); // preMaxLen = Math.min( docLength - insertPos, pasteLength - postMaxLen ); preMaxLen = Math.min( postMaxLen << 1, Math.min( docLength - insertPos, pasteLength - postMaxLen )); //System.out.println( "A" ); } else { preMaxLen = Math.min( docLength - insertPos, pasteLength >> 1 ); postMaxLen = Math.min( preMaxLen << 1, Math.min( insertPos, pasteLength - preMaxLen )); //System.out.println( "B" ); } } else { preMaxLen = pasteLength >> 1; // note: pasteLength already clipped to be <= docLength - insertPos ! postMaxLen = pasteLength - preMaxLen; //System.out.println( "C" ); } bcPre = createBlendContext( preMaxLen, 0, hasSelectedAudio.isSet() ); bcPost = createBlendContext( postMaxLen, 0, hasSelectedAudio.isSet() ); //System.out.println( "D ; preMaxLen = " + preMaxLen + "; postMaxLen = " + postMaxLen + "; bcPre.getLeftLen() = " + (bcPre == null ? null : String.valueOf( bcPre.getLeftLen())) + "; bcPre.getRightLen() = " + (bcPre == null ? null : String.valueOf( bcPre.getRightLen() )) + "; bcPost.getLeftLen() = " + (bcPost == null ? null : String.valueOf( bcPost.getLeftLen() )) + "; bcPost.getRightLen() = " + (bcPost == null ? null : String.valueOf( bcPost.getRightLen() ))); // if( bcPre != null ) System.out.println( "bcPre : " + bcPre.getLen() + ", " + bcPre.getLeftLen() + ", "+ bcPre.getRightLen() ); // if( bcPost != null ) System.out.println( "bcPost : " + bcPost.getLen() + ", " + bcPost.getLeftLen() + ", "+ bcPost.getRightLen() ); insertSpan = new Span( insertPos, insertPos + pasteLength ); copySpan = new Span( tl.getSpan().start, tl.getSpan().start + pasteLength ); cutTimeline = (mode == EDIT_INSERT) && !hasSelectedAudio.isSet(); cutTimelineSpan = cutTimeline ? new Span( docLength, docLength + pasteLength ) : null; edit = new BasicCompoundEdit( procName ); oldSelSpan = timeline.getSelectionSpan(); if( !oldSelSpan.isEmpty() ) { // deselect edit.addPerform( TimelineVisualEdit.select( this, Session.this, new Span() )); } proc = new ProcessingThread( this, getFrame(), procName ); proc.putClientArg( "tl", tl ); proc.putClientArg( "pos", insertPos); proc.putClientArg( "mode", mode); proc.putClientArg( "tis", tis ); proc.putClientArg( "pasteLen", pasteLength); proc.putClientArg( "exp", expTimeline); proc.putClientArg( "bcPre", bcPre ); proc.putClientArg( "bcPost", bcPost ); proc.putClientArg( "insertSpan", insertSpan ); proc.putClientArg( "copySpan", copySpan ); proc.putClientArg( "cut", cutTimeline); proc.putClientArg( "cutSpan", cutTimelineSpan ); proc.putClientArg( "edit", edit ); return proc; } // --------- ProcessingThread.Client interface --------- /** * This method is called by ProcessingThread */ public int processRun(ProcessingThread context) throws IOException { final ClipboardTrackList tl = (ClipboardTrackList) context.getClientArg( "tl" ); final long insertPos = (Long) context.getClientArg("pos"); final int mode = (Integer) context.getClientArg("mode"); final List<?> tis = (List<?>) context.getClientArg( "tis" ); final AbstractCompoundEdit edit = (AbstractCompoundEdit) context.getClientArg( "edit" ); final BlendContext bcPre = (BlendContext) context.getClientArg( "bcPre" ); final BlendContext bcPost = (BlendContext) context.getClientArg( "bcPost" ); final Span insertSpan = (Span) context.getClientArg( "insertSpan" ); final Span copySpan = (Span) context.getClientArg( "copySpan" ); final boolean cutTimeline = (Boolean) context.getClientArg("cut"); final Span cutTimelineSpan = (Span) context.getClientArg( "cutSpan" ); final long delta = insertPos - tl.getSpan().start; Trail srcTrail; AudioTrail audioTrail; boolean[] trackMap; boolean isAudio, pasteAudio; for (Object ti0 : tis) { Track.Info ti = (Track.Info) ti0; if (ti.selected) { // ----------------- selected tracks ----------------- try { ti.trail.editBegin(edit); isAudio = ti.trail instanceof AudioTrail; srcTrail = tl.getSubTrail(ti.trail.getClass()); pasteAudio = isAudio && (srcTrail != null) && (((AudioTrail) srcTrail).getChannelNum() > 0); if (mode == EDIT_INSERT) { ti.trail.editInsert(this, insertSpan, edit); if (cutTimeline) ti.trail.editRemove(this, cutTimelineSpan, edit); // } else if( (mode == EDIT_OVERWRITE) && (pasteAudio || !isAudio) ) { // Audio needs to be cleared even in Mix mode! } else if (pasteAudio || ((mode == EDIT_OVERWRITE) && !isAudio)) { // Audio needs to be cleared even in Mix mode! ti.trail.editClear(this, insertSpan, edit); } if (pasteAudio) { audioTrail = (AudioTrail) ti.trail; trackMap = tl.getTrackMap(ti.trail.getClass()); int[] trackMap2 = new int[audioTrail.getChannelNum()]; for (int j = 0, k = 0; j < trackMap2.length; j++) { if (ti.trackMap[j]) { // target track selected for (; (k < trackMap.length) && !trackMap[k]; k++) ; if (k < trackMap.length) { // source track exiting trackMap2[j] = k++; } else if (tl.getTrackNum(ti.trail.getClass()) > 0) { // ran out of source tracks, fold over (simple mono -> stereo par exemple) for (k = 0; !trackMap[k]; k++) ; trackMap2[j] = k++; } else { trackMap2[j] = -1; // there aren't any clipboard tracks .... } } else { // target track not selected trackMap2[j] = -1; } } if (!audioTrail.copyRangeFrom((AudioTrail) srcTrail, copySpan, insertPos, mode, this, edit, trackMap2, bcPre, bcPost)) return CANCELLED; } else if ((ti.numTracks == 1) && (tl.getTrackNum(ti.trail.getClass()) == 1)) { ti.trail.editAddAll(this, srcTrail.getCutRange( copySpan, true, srcTrail.getDefaultTouchMode(), delta), edit); } } finally { ti.trail.editEnd(edit); } } } return DONE; } public void processFinished(ProcessingThread context) { final ProcessingThread.Client doneAction = (ProcessingThread.Client) context.getClientArg( "doneAction" ); final AbstractCompoundEdit edit = (AbstractCompoundEdit) context.getClientArg( "edit" ); final boolean expTimeline = (Boolean) context.getClientArg("exp"); final long pasteLength = (Long) context.getClientArg("pasteLen"); final Span insertSpan = (Span) context.getClientArg( "insertSpan" ); if( (context.getReturnCode() == DONE) ) { if( expTimeline && (pasteLength != 0) ) { // adjust timeline edit.addPerform( new EditSetTimelineLength( this, Session.this, timeline.getLength() + pasteLength )); if( timeline.getVisibleSpan().isEmpty() ) { edit.addPerform( TimelineVisualEdit.scroll( this, Session.this, insertSpan )); } } if( !insertSpan.isEmpty() ) { edit.addPerform( TimelineVisualEdit.select( this, Session.this, insertSpan )); edit.addPerform( TimelineVisualEdit.position( this, Session.this, insertSpan.stop )); } edit.perform(); edit.end(); getUndoManager().addEdit( edit ); } else { edit.cancel(); } // if( doneAction != null ) doneAction.processFinished( context, doc ); if( doneAction != null ) doneAction.processFinished( context ); } // mte will check pt.shouldCancel() itself public void processCancel( ProcessingThread context ) { /* ignored */ } } // class actionPasteClass /** * TODO: when a cutted region spans entire view, * selecting undo results in empty visible span */ @SuppressWarnings("serial") private class ActionDelete extends MenuAction implements ProcessingThread.Client { protected ActionDelete() { /* empty */ } public void actionPerformed( ActionEvent e ) { perform(); } protected void perform() { final Span span = timeline.getSelectionSpan(); // XXX sync if( span.isEmpty() ) return; final ProcessingThread proc = initiate( getValue( NAME ).toString(), span, getEditMode() ); if( proc != null ) start( proc ); } // XXX sync protected ProcessingThread initiate( String procName, Span span, int mode ) { if( !checkProcess() ) return null; final BlendContext bc; final long cutLength, docLength, newDocLength, maxLen; final Flag hasSelectedAudio; final List<Track.Info> tis; final AbstractCompoundEdit edit; final boolean cutTimeline; final Span cutTimelineSpan, selSpan; Span visiSpan; hasSelectedAudio = new Flag( false ); tis = Track.getInfos( selectedTracks.getAll(), tracks.getAll() ); if( !AudioTracks.checkSyncedAudio( tis, mode == EDIT_INSERT, null, hasSelectedAudio )) return null; docLength = timeline.getLength(); cutLength = span.getLength(); if( mode == EDIT_INSERT ) { /* * before delete: * * |,,,,,,,,,,,,,,,,,|$$$$$$$#######|............| * |,,,,,,,,,,,,,,,,,|$$$$$$$#######|............| * |,,,,,,,,,,,,,,,,,|$$$$$$$#######|............| * |,,,,,,,,A,,,,,,,,|$$B1$$$###B2##|......C.....| * +-----------------+--------------+------------+ * | span | * * after delete: * left right * |,,,,,,,,,,,,, | | * |,,,,,,,,,,,,,,, | | * |,,,,,,,,,,,,,,,,,| | * |,,,,,,,,,,,,,,,,,|$$ | * |,,,,,,,,,,,,,,,,,|$$$$ | * |,,,,,,,,A,,,,,,,,|$B2$$$ | * +-----------------+------------+ * | * plus * | | ........| * | | ..........| * | |............| * | ##|............| * | ####|............| * | ###B2#|......C.....| * +-----------------+------------+ * | * span.start */ maxLen = Math.min( cutLength, Math.min( span.start, docLength - span.stop ) << 1 ); bc = createBlendContext( maxLen >> 1, (maxLen + 1) >> 1, hasSelectedAudio.isSet() ); } else { /* * after delete: * blend- blend- * Len Len * |,,,,,,,,,,,,,,,,,|$ #|............| * |,,,,,,,,,,,,,,,,,|$$ ##|............| * |,,,,,,,,,,,,,,,,,|$$$ ###|............| * |,,,,,,,,A,,,,,,,,|$B1$ #B2#|......C.....| * +-----------------+--------------+------------+ * | span | */ maxLen = cutLength >> 1; bc = createBlendContext( maxLen, 0, hasSelectedAudio.isSet() ); } // bc = createBlendContext( Math.min( cutLength, span.start ), Math.min( cutLength, docLength - span.stop ), hasSelectedAudio ); edit = new BasicCompoundEdit( procName ); // if( bc != null ) System.out.println( "bc : " + bc.getLen() + ", " + bc.getLeftLen() + ", "+ bc.getRightLen() ); cutTimeline = (mode == EDIT_INSERT) && hasSelectedAudio.isSet(); newDocLength = cutTimeline ? docLength - cutLength : docLength; cutTimelineSpan = cutTimeline ? new Span( newDocLength, docLength ) : null; selSpan = timeline.getSelectionSpan(); if( (mode == EDIT_INSERT) && !selSpan.isEmpty() ) { edit.addPerform( TimelineVisualEdit.position( this, Session.this, span.start )); edit.addPerform( TimelineVisualEdit.select( this, Session.this, new Span() )); } if( cutTimeline ) { visiSpan = timeline.getVisibleSpan(); if( visiSpan.stop > span.start ) { if( visiSpan.stop > newDocLength ) { visiSpan = new Span( Math.max( 0, newDocLength - visiSpan.getLength() ), newDocLength ); TimelineVisualEdit tve = TimelineVisualEdit.scroll( this, Session.this, visiSpan ); edit.addPerform( tve ); } // else visiSpan untouched } edit.addPerform( new EditSetTimelineLength( this, Session.this, newDocLength )); } final ProcessingThread proc = new ProcessingThread( this, getFrame(), procName ); proc.putClientArg( "span", span ); proc.putClientArg( "mode", mode); proc.putClientArg( "tis", tis ); proc.putClientArg( "edit", edit ); proc.putClientArg( "bc", bc ); proc.putClientArg( "cut", cutTimeline); proc.putClientArg( "cutSpan", cutTimelineSpan ); return proc; } // --------- ProcessingThread.Client interface --------- /** * This method is called by ProcessingThread */ public int processRun( ProcessingThread context ) throws IOException { final Span span = (Span) context.getClientArg( "span" ); final int mode = (Integer) context.getClientArg("mode"); final List<?> tis = (List<?>) context.getClientArg( "tis" ); final AbstractCompoundEdit edit = (AbstractCompoundEdit) context.getClientArg( "edit" ); final BlendContext bc = (BlendContext) context.getClientArg( "bc" ); final long left = bc == null ? 0L : bc.getLeftLen(); final long right = bc == null ? 0L : bc.getRightLen(); final boolean cutTimeline = (Boolean) context.getClientArg("cut"); final Span cutTimelineSpan = (Span) context.getClientArg( "cutSpan" ); AudioTrail audioTrail; Track.Info ti; boolean isAudio; for (Object ti1 : tis) { ti = (Track.Info) ti1; try { ti.trail.editBegin(edit); isAudio = ti.trail instanceof AudioTrail; if (ti.selected) { if (mode == EDIT_INSERT) { if (isAudio) { if (bc == null) { ti.trail.editRemove(this, span, edit); } else { ti.trail.editRemove(this, new Span(span.start - left, span.stop + right), edit); ti.trail.editInsert(this, new Span(span.start - left, span.start + right), edit); } audioTrail = (AudioTrail) ti.trail; audioTrail.clearRange(span, EDIT_INSERT, this, edit, ti.trackMap, bc); } else { ti.trail.editRemove(this, span, edit); } } else { ti.trail.editClear(this, span, edit); if (isAudio) { audioTrail = (AudioTrail) ti.trail; audioTrail.clearRange(span, EDIT_OVERWRITE, this, edit, ti.trackMap, bc); } } } else if (cutTimeline) { ti.trail.editRemove(this, cutTimelineSpan, edit); } } finally { ti.trail.editEnd(edit); } } return DONE; } // run public void processFinished( ProcessingThread context ) { final AbstractCompoundEdit edit = (AbstractCompoundEdit) context.getClientArg( "edit" ); if( context.getReturnCode() == DONE ) { edit.perform(); edit.end(); getUndoManager().addEdit( edit ); } else { edit.cancel(); } } // mte will check pt.shouldCancel() itself public void processCancel( ProcessingThread context ) { /* ignore */ } } // class actionDeleteClass @SuppressWarnings("serial") private class ActionTrim extends MenuAction { protected ActionTrim() { /* empty */ } // performs inplace (no runnable processing) coz it's always fast public void actionPerformed( ActionEvent e ) { perform(); } protected void perform() { final Span selSpan, deleteBefore, deleteAfter; final BasicCompoundEdit edit; final List<Track.Info> tis; Track.Info ti; boolean success = false; edit = new BasicCompoundEdit(getValue(NAME).toString()); try { selSpan = timeline.getSelectionSpan(); // if( selSpan.isEmpty() ) return; tis = Track.getInfos( selectedTracks.getAll(), tracks.getAll() ); deleteBefore = new Span( 0, selSpan.start ); deleteAfter = new Span( selSpan.stop, timeline.getLength() ); // deselect edit.addPerform( TimelineVisualEdit.select( this, Session.this, new Span() )); edit.addPerform( TimelineVisualEdit.position( this, Session.this, 0 )); if( !deleteAfter.isEmpty() || !deleteBefore.isEmpty() ) { for (Object ti1 : tis) { ti = (Track.Info) ti1; ti.trail.editBegin(edit); try { if (!deleteAfter.isEmpty()) ti.trail.editRemove(this, deleteAfter, edit); if (!deleteBefore.isEmpty()) ti.trail.editRemove(this, deleteBefore, edit); } finally { ti.trail.editEnd(edit); } } } edit.addPerform( new EditSetTimelineLength( this, Session.this, selSpan.getLength() )); edit.addPerform( TimelineVisualEdit.select( this, Session.this, selSpan.shift( -selSpan.start ))); edit.perform(); edit.end(); getUndoManager().addEdit( edit ); success = true; } finally { if( !success ) edit.cancel(); } } } // class actionTrimClass /** * TODO: when edit mode != EDIT_INSERT, audio tracks are cleared which should be bypassed and vice versa * TODO: waveform display not automatically updated when edit mode != EDIT_INSERT */ @SuppressWarnings("serial") private class ActionSilence extends MenuAction implements ProcessingThread.Client { private Param value = null; private ParamSpace space = null; protected ActionSilence() { /* empty */ } public void actionPerformed( ActionEvent e ) { perform(); } private void perform() { final SpringPanel msgPane; final int result; final ParamField ggDuration; final Param durationSmps; final DefaultUnitTranslator timeTrans; msgPane = new SpringPanel( 4, 2, 4, 2 ); timeTrans = new DefaultUnitTranslator(); ggDuration = new ParamField( timeTrans ); ggDuration.addSpace( ParamSpace.spcTimeHHMMSS ); ggDuration.addSpace( ParamSpace.spcTimeSmps ); ggDuration.addSpace( ParamSpace.spcTimeMillis ); ggDuration.addSpace( ParamSpace.spcTimePercentF ); msgPane.gridAdd( ggDuration, 0, 0 ); msgPane.makeCompactGrid(); GUIUtil.setInitialDialogFocus( ggDuration ); timeTrans.setLengthAndRate( timeline.getLength(), timeline.getRate() ); if( value == null ) { ggDuration.setValue( new Param( 1.0, ParamSpace.TIME | ParamSpace.SECS )); } else { ggDuration.setSpace( space ); ggDuration.setValue( value ); } final JOptionPane op = new JOptionPane( msgPane, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION ); // result = JOptionPane.showOptionDialog( getFrame() == null ? null : getFrame().getWindow(), msgPane, getValue( NAME ).toString(), // JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null ); result = BasicWindowHandler.showDialog( op, getFrame() == null ? null : getFrame().getWindow(), getValue( NAME ).toString() ); if( result == JOptionPane.OK_OPTION ) { value = ggDuration.getValue(); space = ggDuration.getSpace(); durationSmps = timeTrans.translate( value, ParamSpace.spcTimeSmps ); if( durationSmps.val > 0.0 ) { final ProcessingThread proc; proc = initiate( timeline.getPosition(), (long) durationSmps.val ); if( proc != null ) start( proc ); } } } public ProcessingThread initiate( long pos, long numFrames ) { if( !checkProcess() || (numFrames == 0) ) return null; if( numFrames < 0 ) throw new IllegalArgumentException( String.valueOf( numFrames )); if( (pos < 0) || (pos > timeline.getLength()) ) throw new IllegalArgumentException( String.valueOf( pos )); final ProcessingThread proc; final AbstractCompoundEdit edit; final Span oldSelSpan, insertSpan; proc = new ProcessingThread( this, getFrame(), getValue( NAME ).toString() ); edit = new BasicCompoundEdit( proc.getName() ); oldSelSpan = timeline.getSelectionSpan(); insertSpan = new Span( pos, pos + numFrames ); if( !oldSelSpan.isEmpty() ) { // deselect edit.addPerform( TimelineVisualEdit.select( this, Session.this, new Span() )); } proc.putClientArg( "tis", Track.getInfos( selectedTracks.getAll(), tracks.getAll() )); proc.putClientArg( "edit", edit ); proc.putClientArg( "span", insertSpan ); return proc; } /** * This method is called by ProcessingThread */ public int processRun( ProcessingThread context ) throws IOException { final List<?> tis = (List<?>) context.getClientArg("tis"); final AbstractCompoundEdit edit = (AbstractCompoundEdit) context.getClientArg("edit"); final Span insertSpan = (Span) context.getClientArg("span"); AudioTrail audioTrail; for (Object ti0 : tis) { Track.Info ti = (Track.Info) ti0; ti.trail.editBegin(edit); try { ti.trail.editInsert(this, insertSpan, edit); if (ti.trail instanceof AudioTrail) { audioTrail = (AudioTrail) ti.trail; audioTrail.editAdd(this, audioTrail.allocSilent(insertSpan), edit); } } finally { ti.trail.editEnd(edit); } } return DONE; } public void processFinished( ProcessingThread context ) { final AbstractCompoundEdit edit = (AbstractCompoundEdit) context.getClientArg( "edit" ); final Span insertSpan = (Span) context.getClientArg( "span" ); if( context.getReturnCode() == DONE ) { if( !insertSpan.isEmpty() ) { // adjust timeline edit.addPerform( new EditSetTimelineLength( this, Session.this, timeline.getLength() + insertSpan.getLength() )); if( timeline.getVisibleSpan().isEmpty() ) { edit.addPerform( TimelineVisualEdit.scroll( this, Session.this, insertSpan )); } } if( !insertSpan.isEmpty() ) { edit.addPerform( TimelineVisualEdit.select( this, Session.this, insertSpan )); edit.addPerform( TimelineVisualEdit.position( this, Session.this, insertSpan.stop )); } edit.perform(); edit.end(); getUndoManager().addEdit( edit ); } else { edit.cancel(); } } // mte will check pt.shouldCancel() itself public void processCancel( ProcessingThread context ) { /* ignore */ } } // class actionSilenceClass }