/* * CDVerifyDlg.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: * 28-May-05 created */ 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.io.AudioFile; import de.sciss.io.AudioFileDescr; import javax.swing.*; import java.awt.*; import java.io.File; import java.io.IOException; /** * Processing module for verifying the content * of a burned audio CD, by subtracting the original * and the re-imported sound file (automatically * finding the gap at the beginning produced by burning the CD). * * @warning this will fail to work if the original sound starts * with a purely periodic sound or DC signal */ public class CDVerifyDlg extends ModulePanel { // -------- private variables -------- // Properties (defaults) private static final int PR_ORIGINFILE = 0; // pr.text private static final int PR_COPYINFILE = 1; // private static final int PR_OUTPUTFILE = 2; // private static final int PR_OUTPUTTYPE = 0; // pr.intg private static final int PR_MAXGAP = 0; // pr.para private static final int PR_BITTOGGLE = 0; // pr.bool private static final int PR_QUICKABORT = 1; private static final String PRN_ORIGINFILE = "OrigInFile"; private static final String PRN_COPYINFILE = "CopyInFile"; private static final String PRN_OUTPUTFILE = "OutputFile"; // private static final String PRN_OUTPUTTYPE = "OutputType"; private static final String PRN_MAXGAP = "MaxGap"; private static final String PRN_BITTOGGLE = "BitToggle"; private static final String PRN_QUICKABORT = "QuickAbort"; private static final String prText[] = { "", "", "" }; private static final String prTextName[] = { PRN_ORIGINFILE, PRN_COPYINFILE, PRN_OUTPUTFILE }; // private static final int prIntg[] = { 0 }; // private static final String prIntgName[] = { PRN_OUTPUTTYPE }; private static final Param prPara[] = { null }; private static final String prParaName[] = { PRN_MAXGAP }; private static final boolean prBool[] = { true, false }; private static final String prBoolName[] = { PRN_BITTOGGLE, PRN_QUICKABORT }; private static final int GG_ORIGINFILE = GG_OFF_PATHFIELD + PR_ORIGINFILE; private static final int GG_COPYINFILE = GG_OFF_PATHFIELD + PR_COPYINFILE; private static final int GG_MAXGAP = GG_OFF_PARAMFIELD + PR_MAXGAP; private static final int GG_BITTOGGLE = GG_OFF_CHECKBOX + PR_BITTOGGLE; private static final int GG_QUICKABORT = GG_OFF_CHECKBOX + PR_QUICKABORT; private static PropertyArray static_pr = null; private static Presets static_presets = null; private static final String ERR_TOOSHORT = "Input file too short!"; private static final String ERR_SYNCFAILED = "Failed to synchronize inputs!"; private static final String ERR_DIFFERENTCHAN = "Input files have different number of channels!"; // -------- public methods -------- /** * !! setVisible() bleibt dem Aufrufer ueberlassen */ public CDVerifyDlg() { super( "CD Verification" ); init2(); } protected void buildGUI() { // einmalig PropertyArray initialisieren 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_MAXGAP ] = new Param( 20.0, Param.ABS_MS ); static_pr.paraName = prParaName; // static_pr.superPr = DocumentFrame.static_pr; } // default preset if( static_presets == null ) { static_presets = new Presets( getClass(), static_pr.toProperties( true )); } presets = static_presets; pr = (PropertyArray) static_pr.clone(); // -------- build GUI -------- GridBagConstraints con; PathField ggInputFile1, ggInputFile2; JCheckBox ggBitToggle, ggQuickAbort; ParamField ggGap; gui = new GUISupport(); con = gui.getGridBagConstraints(); con.insets = new Insets( 1, 2, 1, 2 ); // -------- Input-Gadgets -------- con.fill = GridBagConstraints.BOTH; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel( new GroupLabel( "Waveform I/O", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); ggInputFile1 = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD, "Select original input file" ); ggInputFile1.handleTypes( GenericFile.TYPES_SOUND ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Original input file", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggInputFile1, GG_ORIGINFILE, null ); ggInputFile2 = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD, "Select re-imported (burned) input file" ); ggInputFile2.handleTypes( GenericFile.TYPES_SOUND ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Re-imported (copy) input file", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggInputFile2, GG_COPYINFILE, null ); // -------- Settings-Gadgets -------- gui.addLabel( new GroupLabel( "Verfication Settings", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); ggGap = new ParamField( Constants.spaces[ Constants.absMsSpace ]); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Max. Start Gap", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggGap, GG_MAXGAP, null ); ggBitToggle = new JCheckBox( "Ignore LSB toggle" ); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addCheckbox( ggBitToggle, GG_BITTOGGLE, null ); con.weightx = 0.1; con.gridwidth = 2; gui.addLabel( new JLabel() ); ggQuickAbort = new JCheckBox( "Quick Abort" ); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addCheckbox( ggQuickAbort, GG_QUICKABORT, null ); initGUI( this, FLAGS_PRESETS | FLAGS_PROGBAR, gui ); } /** * Transfer values from prop-array to GUI */ public void fillGUI() { super.fillGUI(); super.fillGUI( gui ); } /** * Transfer values from GUI to prop-array */ public void fillPropertyArray() { super.fillPropertyArray(); super.fillPropertyArray( gui ); } // -------- Processor Interface -------- protected void process() { long progOff, progLen; long origOff, copyOff; int maxGap; AudioFile origInF = null; AudioFile copyInF = null; AudioFile outF = null; AudioFileDescr afdOrig, afdCopy; int len, cancelCount, cancelDelta; float[][] inBuf, compBuf; float thresh; long deviationOff, deviationCount, framesRemaining; float f1, deviationMax; topLevel: try { // ---- open input, output; init ---- origInF = AudioFile.openAsRead( new File( pr.text[ PR_ORIGINFILE ])); copyInF = AudioFile.openAsRead( new File( pr.text[ PR_COPYINFILE ])); afdOrig = origInF.getDescr(); afdCopy = copyInF.getDescr(); if( afdOrig.channels != afdCopy.channels ) throw new IOException( ERR_DIFFERENTCHAN ); maxGap = (int) (AudioFileDescr.millisToSamples( afdOrig, pr.para[ PR_MAXGAP ].value) + 0.5); if( maxGap > afdOrig.length - 8192 ) throw new IOException( ERR_TOOSHORT ); inBuf = new float[ afdOrig.channels ][ 8192 ]; compBuf = new float[ afdOrig.channels ][ 8192 + maxGap ]; origOff = 0; copyOff = 0; origInF.seekFrame( origOff ); len = 0; cancelDelta = 0; thresh = pr.bool[ PR_BITTOGGLE ] ? 6.103517e-05f : 0.0f; progOff = 0; progLen = afdCopy.length; do { origOff += len; copyOff += len; len = (int) Math.min( 8192, Math.min( afdCopy.length - copyOff - maxGap, afdOrig.length - origOff )); if( len <= 0 ) throw new IOException( ERR_SYNCFAILED ); origInF.readFrames( inBuf, 0, len ); copyInF.seekFrame( copyOff ); copyInF.readFrames( compBuf, 0, len + maxGap ); cancelCount = 0; deltaLp: for( int delta = 0; delta < maxGap; delta++ ) { for( int ch = 0; ch < inBuf.length; ch++ ) { for( int i = 0, j = delta; i < len; i++, j++ ) { // if( inBuf[ ch ][ i ] != compBuf[ ch ][ j ]) { if( Math.abs( inBuf[ ch ][ i ] - compBuf[ ch ][ j ]) > thresh ) continue deltaLp; } } cancelCount++; cancelDelta = delta; } if( cancelCount == 0 && copyOff > maxGap ) throw new IOException( ERR_SYNCFAILED ); // no sync withing 'maxgap' window // .... progress .... progOff += len; setProgression( (float) progOff / (float) progLen ); } while( cancelCount != 1 && threadRunning ); // repeat during ambigious sync withing 'maxgap' window // .... check running .... if( !threadRunning ) break topLevel; System.err.println( "synced with "+cancelDelta+" frames offset!" ); deviationCount = 0; deviationMax = 0.0f; deviationOff = 0; copyOff += cancelDelta; framesRemaining = Math.min( afdCopy.length - copyOff, afdOrig.length - origOff ); progLen = progOff + framesRemaining; origInF.seekFrame( origOff ); copyInF.seekFrame( copyOff ); verifyLp: while( threadRunning && framesRemaining > 0 ) { len = (int) Math.min( 8192, framesRemaining ); origInF.readFrames( inBuf, 0, len ); copyInF.readFrames( compBuf, 0, len ); for( int ch = 0; ch < inBuf.length; ch++ ) { for( int i = 0; i < len; i++ ) { f1 = Math.abs( inBuf[ ch ][ i ] - compBuf[ ch ][ i ]); if( f1 > thresh ) { deviationCount++; if( f1 > deviationMax ) { deviationMax = f1; deviationOff = copyInF.getFramePosition() - len + i; } if( pr.bool[ PR_QUICKABORT ]) break verifyLp; } } } framesRemaining -= len; // .... progress .... progOff += len; setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; if( deviationCount > 0 ) { setError( new Exception( deviationCount + " samples were not verified (max amp " + (Math.log( deviationMax ) / Constants.ln10 * 20) + " dBFS at frame offset " + deviationOff + ")!" )); } else { System.err.println( "Successfully verified" ); setProgression( 1.0f ); } } catch( IOException e1 ) { setError( e1 ); } finally { if( origInF != null ) origInF.cleanUp(); if( copyInF != null ) copyInF.cleanUp(); if( outF != null ) outF.cleanUp(); } } // process() } // class CDVerifyDlg