/* * RecycleDlg.java * (FScape) * * Copyright (c) 2001-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 * * * Changelog: * 03-Mar-05 added 'Analyze' button + equal power cross fading option, uses indicateOutputWrite(). */ package de.sciss.fscape.gui; import de.sciss.fscape.io.GenericFile; import de.sciss.fscape.prop.Presets; import de.sciss.fscape.prop.PropertyArray; import de.sciss.fscape.session.ModulePanel; import de.sciss.fscape.util.Constants; import de.sciss.fscape.util.Param; import de.sciss.fscape.util.ParamSpace; import de.sciss.fscape.util.TimeFormat; import de.sciss.io.AudioFile; import de.sciss.io.AudioFileDescr; import de.sciss.io.IOUtil; import de.sciss.io.Marker; import de.sciss.io.Region; import de.sciss.io.Span; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.EOFException; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Locale; /** * Processing module that looks at a source file and a compacted * file and puts out all the stuff which wasn't used in the compacted file. * An optional cross-fade is performed between adjacent chunks to avoid * clicks. If for example the original file is 20 seconds long and the * compacted version consists of the material 5.5...15.5 seconds and padding * is set to 500 milliseconds the recycled output will consist of the chunks * 0...6 seconds and 15...20 seconds (re original file). if cross-fade is * set to 1.0 sec, the output file will contain the original material 0...5 seconds, * followed by a 1 second cross-fade of material 5...6 seconds versus 14...15 seconds, * followed by an optional marker named 'Cut', followed by material 15...20 seconds * (hence total duration 11 sec). */ public class RecycleDlg extends ModulePanel { // -------- private variables -------- // Properties (defaults) private static final int PR_ORIGINFILE = 0; // pr.text private static final int PR_COMPACTFILE = 1; private static final int PR_OUTPUTFILE = 2; private static final int PR_OUTPUTTYPE = 0; // pr.intg private static final int PR_OUTPUTRES = 1; private static final int PR_MARKERS = 0; // pr.bool private static final int PR_MINMATCH = 0; // pr.para private static final int PR_MINRECYCLE = 1; private static final int PR_PADDING = 2; private static final int PR_CMPSPACING = 3; private static final int PR_CROSSFADE = 4; private static final String PRN_ORIGINFILE = "OriginFile"; private static final String PRN_COMPACTFILE = "CompactFile"; private static final String PRN_OUTPUTFILE = "OutputFile"; private static final String PRN_OUTPUTTYPE = "OutputType"; private static final String PRN_OUTPUTRES = "OutputReso"; private static final String PRN_MARKERS = "Markers"; private static final String PRN_MINMATCH = "MinMatch"; private static final String PRN_MINRECYCLE = "MinRecycle"; private static final String PRN_PADDING = "Padding"; private static final String PRN_CMPSPACING = "CmpSpacing"; private static final String PRN_CROSSFADE = "CrossFade"; private static final String prText[] = { "", "", "" }; private static final String prTextName[] = { PRN_ORIGINFILE, PRN_COMPACTFILE, PRN_OUTPUTFILE }; private static final int prIntg[] = { 0, 0 }; private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES }; private static final boolean prBool[] = { true }; private static final String prBoolName[] = { PRN_MARKERS }; private static final Param prPara[] = { null, null, null, null, null }; private static final String prParaName[] = { PRN_MINMATCH, PRN_MINRECYCLE, PRN_PADDING, PRN_CMPSPACING, PRN_CROSSFADE }; private static final int GG_ORIGINFILE = GG_OFF_PATHFIELD + PR_ORIGINFILE; private static final int GG_COMPACTFILE = GG_OFF_PATHFIELD + PR_COMPACTFILE; private static final int GG_OUTPUTFILE = GG_OFF_PATHFIELD + PR_OUTPUTFILE; private static final int GG_OUTPUTTYPE = GG_OFF_CHOICE + PR_OUTPUTTYPE; private static final int GG_OUTPUTRES = GG_OFF_CHOICE + PR_OUTPUTRES; private static final int GG_MARKERS = GG_OFF_CHECKBOX + PR_MARKERS; private static final int GG_MINMATCH = GG_OFF_PARAMFIELD + PR_MINMATCH; private static final int GG_MINRECYCLE = GG_OFF_PARAMFIELD + PR_MINRECYCLE; private static final int GG_PADDING = GG_OFF_PARAMFIELD + PR_PADDING; private static final int GG_CMPSPACING = GG_OFF_PARAMFIELD + PR_CMPSPACING; private static final int GG_CROSSFADE = GG_OFF_PARAMFIELD + PR_CROSSFADE; private static final int GG_INFOFIELD = GG_OFF_OTHER + 0; private static final int GG_ANALYZE = GG_OFF_OTHER + 1; private static PropertyArray static_pr = null; private static Presets static_presets = null; private static final String ERR_CHANNUM = "Input files must have same # of channels"; private static final String MARK_CUT = "Cut"; private boolean regionsKnown = false; // true after "Analyze"; false after new input choice private boolean threadJustAnalyze = false; // true = find peak; false = change gain private final java.util.List<Region> regionList = new ArrayList<Region>(); private final MessageFormat msgInfoField = new MessageFormat( "{0,choice,-1#[…|0#}"+ "{1,choice,0#No regions|1#One region|1<{1,number,integer} regions}"+ "{1,choice,0#|1# with total duration of {2}}"+ " to recycle{0,choice,-1#…]|0#}", Locale.US ); // -------- public methods -------- public RecycleDlg() { super("Recycle Or Die"); init2(); } protected void buildGUI() { if (static_pr == null) { static_pr = new PropertyArray(); static_pr.text = prText; static_pr.textName = prTextName; static_pr.intg = prIntg; static_pr.intgName = prIntgName; static_pr.bool = prBool; static_pr.boolName = prBoolName; static_pr.para = prPara; static_pr.para[ PR_MINMATCH ] = new Param( 100.0, Param.ABS_MS ); static_pr.para[ PR_MINRECYCLE ] = new Param( 100.0, Param.ABS_MS ); static_pr.para[ PR_PADDING ] = new Param( 1000.0, Param.ABS_MS ); static_pr.para[ PR_CMPSPACING ] = new Param( 6.0, Param.ABS_MS ); // 256 smp for Logic static_pr.para[ PR_CROSSFADE ] = new Param( 0.0, Param.ABS_MS ); static_pr.paraName = prParaName; fillDefaultAudioDescr( static_pr.intg, PR_OUTPUTTYPE, PR_OUTPUTRES ); static_presets = new Presets( getClass(), static_pr.toProperties( true )); } presets = static_presets; pr = (PropertyArray) static_pr.clone(); // -------- build GUI -------- GridBagConstraints con; PathField ggOriginFile, ggCompactFile, ggOutputFile; PathField[] ggInputs; ParamField ggMinMatch, ggMinRecycle, ggPadding, ggCmpSpacing, ggCrossFade; JCheckBox ggMarkers; ParamSpace[] spcInterval; JButton ggAnalyze; gui = new GUISupport(); con = gui.getGridBagConstraints(); con.insets = new Insets( 1, 2, 1, 2 ); ActionListener al = new ActionListener() { public void actionPerformed( ActionEvent e ) { int ID = gui.getItemID( e ); switch( ID ) { case GG_ORIGINFILE: case GG_COMPACTFILE: clearInput(); break; case GG_ANALYZE: threadJustAnalyze = true; clearInput(); start(); } } }; // -------- Input-Gadgets -------- con.fill = GridBagConstraints.BOTH; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel( new GroupLabel( "Waveform I/O", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); ggOriginFile = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD, "Select original input file" ); ggOriginFile.handleTypes( GenericFile.TYPES_SOUND ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Original Input", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggOriginFile, GG_ORIGINFILE, null ); ggCompactFile = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD, "Select compacted input file" ); ggCompactFile.handleTypes( GenericFile.TYPES_SOUND ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Compacted Input", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggCompactFile, GG_COMPACTFILE, null ); ggOutputFile = new PathField( PathField.TYPE_OUTPUTFILE + PathField.TYPE_FORMATFIELD + PathField.TYPE_RESFIELD, "Select output file" ); ggOutputFile.handleTypes( GenericFile.TYPES_SOUND ); ggInputs = new PathField[ 1 ]; ggInputs[ 0 ] = ggOriginFile; ggOutputFile.deriveFrom( ggInputs, "$D0$F0Rcyc$E" ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Recycled Output", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggOutputFile, GG_OUTPUTFILE, null ); gui.registerGadget( ggOutputFile.getTypeGadget(), GG_OUTPUTTYPE ); gui.registerGadget( ggOutputFile.getResGadget(), GG_OUTPUTRES ); ggAnalyze = new JButton( "Analyze" ); con.gridwidth = 1; con.weightx = 0.1; gui.addButton( ggAnalyze, GG_ANALYZE, al ); con.fill = GridBagConstraints.HORIZONTAL; JTextField ggInfoField = new JTextField(); ggInfoField.setEditable( false ); ggInfoField.setBackground( null ); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addTextField(ggInfoField, GG_INFOFIELD, al ); // -------- Settings-Gadgets -------- con.fill = GridBagConstraints.BOTH; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel( new GroupLabel( "Recycling Settings", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); spcInterval = new ParamSpace[ 2 ]; spcInterval[0] = Constants.spaces[ Constants.absMsSpace ]; spcInterval[1] = Constants.spaces[ Constants.absBeatsSpace ]; ggMinMatch = new ParamField( spcInterval ); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Min. Matching", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggMinMatch, GG_MINMATCH, null ); ggMarkers = new JCheckBox(); con.weightx = 0.1; gui.addLabel( new JLabel( "Write Markers", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; gui.addCheckbox( ggMarkers, GG_MARKERS, null ); ggCmpSpacing = new ParamField( spcInterval ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Compact Spacing", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggCmpSpacing, GG_CMPSPACING, null ); con.weightx = 0.1; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel( new JLabel() ); ggMinRecycle = new ParamField( spcInterval ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Min. Recycling", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggMinRecycle, GG_MINRECYCLE, null ); con.weightx = 0.1; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel( new JLabel() ); ggPadding = new ParamField( spcInterval ); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Pre/Post Padding", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggPadding, GG_PADDING, null ); con.weightx = 0.1; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel( new JLabel() ); ggCrossFade = new ParamField( spcInterval ); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Crossfade", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggCrossFade, GG_CROSSFADE, null ); con.weightx = 0.1; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel( new JLabel() ); initGUI( this, FLAGS_PRESETS | FLAGS_PROGBAR, gui ); } public void fillGUI() { super.fillGUI(); super.fillGUI(gui); clearInput(); } public void fillPropertyArray() { super.fillPropertyArray(); super.fillPropertyArray(gui); } // -------- Processor Interface -------- protected void process() { int i, j, k, chunkLength; int off, len, ch; long progOff, progLen; double d1; // io AudioFile origF = null; AudioFile cmpF = null; AudioFile outF = null; AudioFileDescr origStream, cmpStream, outStream; int chanNum; float[][] origBuf, cmpBuf, fadeBuf; float[] convBuf1, convBuf2; int origLength, cmpLength, outLength; int origFramesRead, cmpFramesRead; int minMatch, minRecycle, padding, cmpSpacing, matchBegin, matchEnd; int origBufLen, origBufOff, cmpBufLen, cmpBufOff, origBufPhys; Region currentRegion; java.util.List<Marker> markers; PathField ggOutput; boolean justAnalyse; long dispDelta, dispTime; boolean infoChange; int fadeLength, fadeInLength, fadeOutLength; topLevel: try { justAnalyse = threadJustAnalyze; // "Analyze"-JButton acts like a JCheckBox, threadJustAnalyze = false; // we have to "turn it off" ourselves // ---- open input, output; init ---- // input origF = AudioFile.openAsRead(new File(pr.text[PR_ORIGINFILE])); origStream = origF.getDescr(); chanNum = origStream.channels; origLength = (int) origStream.length; // this helps to prevent errors from empty files! if ((origLength < 1) || (chanNum < 1)) throw new EOFException(ERR_EMPTY); ggOutput = (PathField) gui.getItemObj(GG_OUTPUTFILE); if (ggOutput == null) throw new IOException(ERR_MISSINGPROP); outStream = new AudioFileDescr(origStream); ggOutput.fillStream(outStream); markers = (java.util.List<Marker>) outStream.getProperty(AudioFileDescr.KEY_MARKERS); if (markers == null && pr.bool[PR_MARKERS]) { markers = new ArrayList<Marker>(); } // ---- open output ---- if (!justAnalyse) { if (!pr.bool[PR_MARKERS]) { outF = AudioFile.openAsWrite(outStream); } else { IOUtil.createEmptyFile(new File(pr.text[PR_OUTPUTFILE])); } } // .... check running .... if (!threadRunning) break topLevel; // ---- misc init ---- d1 = Param.transform(pr.para[PR_MINMATCH], Param.ABS_MS, null, null).value; minMatch = Math.max(1, (int) (AudioFileDescr.millisToSamples(origStream, d1) + 0.5)); d1 = Param.transform(pr.para[PR_MINRECYCLE], Param.ABS_MS, null, null).value; minRecycle = Math.max(1, (int) (AudioFileDescr.millisToSamples(origStream, d1) + 0.5)); d1 = Param.transform(pr.para[PR_PADDING], Param.ABS_MS, null, null).value; padding = (int) (AudioFileDescr.millisToSamples(origStream, d1) + 0.5); d1 = Param.transform(pr.para[PR_CMPSPACING], Param.ABS_MS, null, null).value; cmpSpacing = (int) (AudioFileDescr.millisToSamples(origStream, d1) + 0.5); d1 = Param.transform(pr.para[PR_CROSSFADE], Param.ABS_MS, null, null).value; fadeLength = Math.max(1, (int) (AudioFileDescr.millisToSamples(origStream, d1) + 0.5)); origBufLen = minMatch << 1; origBuf = new float[chanNum][Math.max(8192, origBufLen)]; fadeBuf = new float[chanNum][fadeLength]; // ================ PASS 1: Region detection ================ currentRegion = null; progOff = 0; if( !regionsKnown ) { cmpF = AudioFile.openAsRead( new File( pr.text[ PR_COMPACTFILE ])); cmpStream = cmpF.getDescr(); cmpLength = (int) cmpStream.length; if( chanNum != cmpStream.channels ) throw new IOException( ERR_CHANNUM ); // this helps to prevent errors from empty files! if( cmpLength < 1 ) throw new EOFException( ERR_EMPTY ); cmpBufLen = minMatch; cmpBuf = new float[ chanNum ][ cmpBufLen ]; cmpBufOff = 0; origBufOff = 0; cmpFramesRead = 0; origFramesRead = 0; origBufPhys = 0; // physical offset matchEnd = 0; regionList.clear(); // elements are of type de.sciss.fscape.io.Region, chronologically added progLen = ((long) origLength + (long) cmpLength) << (justAnalyse ? 0 : 1); dispDelta = 10000; // first prelim. display after 10 sec. dispTime = System.currentTimeMillis() + dispDelta; infoChange = true; passOne: while (threadRunning) { // ======== 1.) Find track ======== // skip space between compacted chunks if (cmpSpacing > 0) { i = cmpBufOff - cmpSpacing; if (i > 0) { cmpBufOff = i; for (ch = 0; ch < chanNum; ch++) { System.arraycopy(cmpBuf[ch], cmpSpacing, cmpBuf[ch], 0, cmpBufOff); } } else { cmpBufOff = 0; cmpFramesRead -= i; cmpF.seekFrame(cmpFramesRead); } } // compact der laenge "minMatch" einlesen chunkLength = Math.min(cmpLength - cmpFramesRead, cmpBufLen - cmpBufOff); for (off = 0; off < chunkLength; ) { len = Math.min(8192, chunkLength - off); cmpF.readFrames(cmpBuf, cmpBufOff, len); cmpFramesRead += len; cmpBufOff += len; off += len; progOff += len; // .... progress .... setProgression((float) progOff / (float) progLen); // .... check running .... if (!threadRunning) break topLevel; } if (cmpBufOff < minMatch) break passOne; // EOF -> continue pass2 matchLp2: while (true) { chunkLength = Math.min(origLength - origFramesRead, origBufLen - origBufOff); for (off = 0; off < chunkLength; ) { len = Math.min(8192, chunkLength - off); origF.readFrames(origBuf, origBufOff, len); origFramesRead += len; origBufOff += len; off += len; progOff += len; // .... progress .... setProgression((float) progOff / (float) progLen); // .... check running .... if (!threadRunning) break topLevel; } if ((chunkLength == 0) || (origBufOff < minMatch)) break passOne; // EOF -> continue pass2 // ---- update display after increasing time deltas ---- if ((System.currentTimeMillis() > dispTime) && infoChange) { showRegions(origStream.rate, false); dispDelta = dispDelta * 3 / 2; dispTime += dispDelta; infoChange = false; } // System.out.println( "minMatch "+minMatch+"; cmpBufLen "+cmpBufLen+"; cmpBufOff "+cmpBufOff+"; origBufLen "+origBufLen+"; origBufOff "+origBufOff ); matchLp: for (i = minMatch; i <= origBufOff; i++) { for (ch = 0; ch < chanNum; ch++) { convBuf1 = origBuf[ch]; convBuf2 = cmpBuf[ch]; for (j = minMatch, k = i; j > 0; ) { if (convBuf1[--k] != convBuf2[--j]) continue matchLp; } } // when we get here origBuf matches cmpBuf with offset k matchBegin = origBufPhys + i - minMatch; origBufOff -= i; origBufPhys += i; cmpBufOff = 0; for (ch = 0; ch < chanNum; ch++) { // shift buffer content to offset 0 System.arraycopy(origBuf[ch], i, origBuf[ch], 0, origBufOff); } break matchLp2; } i = origBufOff - minMatch; origBufOff -= i; origBufPhys+= i; for (ch = 0; ch < chanNum; ch++) { // shift buffer content to offset 0 System.arraycopy(origBuf[ch], i, origBuf[ch], 0, origBufOff); } } // while( true ) // ======== 2.) Follow track ======== // add new region up to current match if ((matchBegin - matchEnd) >= minRecycle) { i = Math.max(0, matchEnd - padding); j = Math.min(origLength, matchBegin + padding); if ((currentRegion != null) && (currentRegion.span.getStop() >= i)) { // fuse overlapping regions currentRegion = new Region(new Span(Math.min(currentRegion.span.getStart(), i), Math.max(currentRegion.span.getStop(), j)), currentRegion.name); regionList.set(regionList.size() - 1, currentRegion); infoChange = true; } else { // add new region to end of list currentRegion = new Region(new Span(i, j), null); regionList.add(currentRegion); infoChange = true; } } // ---- update display after increasing time deltas ---- if ((System.currentTimeMillis() > dispTime) && infoChange) { showRegions(origStream.rate, false); dispDelta = dispDelta * 3 / 2; dispTime += dispDelta; infoChange = false; } do { chunkLength = Math.min(Math.min(cmpLength - cmpFramesRead, cmpBufLen - cmpBufOff), Math.min(origLength - origFramesRead, origBufLen - origBufOff)); if (chunkLength == 0) { // EOF -> finalize region and continue pass2 matchEnd = origBufPhys; break passOne; } for (off = 0; off < chunkLength; ) { len = Math.min(8192, chunkLength - off); cmpF.readFrames(cmpBuf, cmpBufOff, len); cmpFramesRead += len; cmpBufOff += len; progOff += len; origF.readFrames(origBuf, origBufOff, len); origFramesRead += len; origBufOff += len; off += len; progOff += len; // .... progress .... setProgression((float) progOff / (float) progLen); // .... check running .... if (!threadRunning) break topLevel; } j = chunkLength; followLp: for (ch = 0; ch < chanNum; ch++) { convBuf1 = origBuf[ch]; convBuf2 = cmpBuf[ch]; for (i = 0; i < chunkLength; i++) { if (convBuf1[i] != convBuf2[i]) { matchEnd = origBufPhys + i; j = i; break followLp; } } } origBufOff -= j; origBufPhys+= j; cmpBufOff -= j; for (ch = 0; ch < chanNum; ch++) { // shift buffer content to offset 0 System.arraycopy(origBuf[ch], j, origBuf[ch], 0, origBufOff); System.arraycopy(cmpBuf [ch], j, cmpBuf [ch], 0, cmpBufOff ); } } while (j == chunkLength); // j < chunkLength means we lost track } // passOne loop // .... check running .... if (!threadRunning) break topLevel; // add final region if ((origLength - matchEnd) >= minRecycle) { i = Math.max(0, matchEnd - padding); j = origLength; if ((currentRegion != null) && (currentRegion.span.getStop() >= i)) { // fuse overlapping regions currentRegion = new Region(new Span(Math.min(currentRegion.span.getStart(), i), Math.max(currentRegion.span.getStop(), j)), currentRegion.name); regionList.set(regionList.size() - 1, currentRegion); } else { // add new region to end of list currentRegion = new Region(new Span(i, j), null); regionList.add(currentRegion); } } cmpF.close(); cmpF = null; regionsKnown = true; if (!justAnalyse) indicateOutputWrite(); } // if( !regionsKnown ) // ================ PASS 2: Region copying ================ showRegions(origStream.rate, true); // calc output length, ggf. marker erzeugen // XXX eigentlich muessten die originalen marker noch entsprechend verschoben werden for (i = 0, outLength = 0; i < regionList.size(); i++) { currentRegion = regionList.get(i); j = (int) currentRegion.span.getLength(); if (pr.bool[PR_MARKERS]) { markers.add(new Marker(outLength, MARK_CUT)); } outLength += j; } if (!justAnalyse) { if (pr.bool[PR_MARKERS]) { outStream.setProperty(AudioFileDescr.KEY_MARKERS, markers); outF = AudioFile.openAsWrite(outStream); } progOff = progOff == 0 ? 0 : (long) outLength; progLen = progOff + outLength; fadeOutLength = 0; for (i = 0; i < regionList.size(); i++) { currentRegion = regionList.get(i); chunkLength = (int) currentRegion.span.getLength(); fadeInLength = fadeOutLength; origF.seekFrame(currentRegion.span.getStart() - fadeInLength); for (off = 0; off < fadeInLength; ) { len = Math.min(8192, fadeInLength - off); origF.readFrames(origBuf, 0, len); for (ch = 0; ch < chanNum; ch++) { convBuf1 = fadeBuf[ch]; convBuf2 = origBuf[ch]; for (j = 0, k = off; j < len; j++, k++) { convBuf1[k] += convBuf2[j] * (float) Math.sqrt((double) k / (double) fadeInLength); } } outF.writeFrames(fadeBuf, off, len); off += len; progOff += len; // .... progress .... setProgression((float) progOff / (float) progLen); // .... check running .... if (!threadRunning) break topLevel; } if (i + 1 < regionList.size()) { fadeOutLength = (int) Math.min(fadeLength, Math.min(origLength - currentRegion.span.getStop(), regionList.get(i + 1).span.getStart())); chunkLength -= fadeOutLength; } else { fadeOutLength = 0; } for (off = 0; off < chunkLength; ) { len = Math.min(8192, chunkLength - off); origF.readFrames(origBuf, 0, len); outF.writeFrames(origBuf, 0, len); off += len; progOff += len; // .... progress .... setProgression((float) progOff / (float) progLen); // .... check running .... if (!threadRunning) break topLevel; } if (fadeOutLength > 0) { origF.readFrames(fadeBuf, 0, fadeOutLength); for (ch = 0; ch < chanNum; ch++) { convBuf1 = fadeBuf[ch]; for (j = 0; j < fadeOutLength; j++) { convBuf1[j] *= (float) Math.sqrt(1.0 - ((double) j / (double) fadeOutLength)); } } } } // passTwo loop // .... check running .... if (!threadRunning) break topLevel; outF.close(); outF = null; } origF.close(); origF = null; setProgression(1.0f); // in case regionList.size() == 0 } catch (IOException e1) { setError(e1); } catch (OutOfMemoryError e2) { origStream = null; cmpStream = null; outStream = null; origBuf = null; cmpBuf = null; fadeBuf = null; convBuf1 = null; convBuf2 = null; System.gc(); setError(new Exception(ERR_MEMORY)); } // ---- cleanup (topLevel) ---- if (outF != null) { outF.cleanUp(); } if (origF != null) { origF.cleanUp(); } if (cmpF != null) { cmpF.cleanUp(); } } // process() // -------- private methods -------- /* * Show number of length of recycle regions * * @param complete `false` if calculation has not yet completed * * @return total length of all regions in frames */ private void showRegions(double sampleRate, boolean complete) { Integer cmpChoice; Object[] msgArgs; int i, numRegions; long duration; String strTime; numRegions = regionList.size(); if (numRegions > 0) { for (i = 0, duration = 0; i < regionList.size(); i++) { duration += regionList.get(i).span.getLength(); } strTime = new TimeFormat(0, null, null, 3, Locale.US).formatTime(new Double(duration / sampleRate)); } else { strTime = null; } cmpChoice = new Integer(complete ? 1 : -1); msgArgs = new Object[] { cmpChoice, new Integer(regionList.size()), strTime }; gui.stringToJTextField(msgInfoField.format(msgArgs), GG_INFOFIELD); } /* * Set new input file */ protected void clearInput() { regionsKnown = false; regionList.clear(); gui.stringToJTextField("", GG_INFOFIELD); } }