/* * LueckenbuesserDlg.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 */ 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.Filter; import de.sciss.fscape.util.Param; import de.sciss.fscape.util.ParamSpace; import de.sciss.io.AudioFile; import de.sciss.io.AudioFileDescr; import javax.swing.*; import java.awt.*; import java.io.File; import java.io.IOException; /** * I hazily remember having seen this * algorithm somewhere using CSound. It * tracks two (preferably similar) soundfiles * and zigzags between them whenever it * finds suitable similarities or "wave crossings". */ public class LueckenbuesserDlg extends ModulePanel { // -------- private variables -------- // Properties (defaults) private static final int PR_INPUTFILE1 = 0; // pr.text private static final int PR_INPUTFILE2 = 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_PATIENCE = 0; // pr.para private static final int PR_CROSSFADE = 1; private static final int PR_EQP = 0; // pr.bool private static final String PRN_INPUTFILE1 = "InputFile1"; private static final String PRN_INPUTFILE2 = "InputFile2"; 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_EQP = "EqualPower"; private static final String PRN_PATIENCE = "Patience"; private static final String PRN_CROSSFADE = "Crossfade"; private static final String prText[] = { "", "", "" }; private static final String prTextName[] = { PRN_INPUTFILE1, PRN_INPUTFILE2, PRN_OUTPUTFILE }; private static final int prIntg[] = { 0, 0 }; private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES }; private static final Param prPara[] = { null, null }; private static final String prParaName[] = { PRN_PATIENCE, PRN_CROSSFADE }; private static final boolean prBool[] = { false }; private static final String prBoolName[] = { PRN_EQP }; private static final int GG_INPUTFILE1 = GG_OFF_PATHFIELD + PR_INPUTFILE1; private static final int GG_INPUTFILE2 = GG_OFF_PATHFIELD + PR_INPUTFILE2; 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_EQP = GG_OFF_CHECKBOX + PR_EQP; private static final int GG_PATIENCE = GG_OFF_PARAMFIELD + PR_PATIENCE; private static final int GG_CROSSFADE = GG_OFF_PARAMFIELD + PR_CROSSFADE; private static PropertyArray static_pr = null; private static Presets static_presets = null; private static final String ERR_CHANNELS = "All inputs must have the same # of channels!"; private static final String ERR_CHANNELS2 = "Inputs must have at least one channel!"; private static final String WARN_CHANNELS = "Warning, multi channel input! Only first channel is analyzed!"; // -------- public methods -------- /** * !! setVisible() bleibt dem Aufrufer ueberlassen */ public LueckenbuesserDlg() { super( "L\u00FCckenb\u00FC\u00DFer" ); 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_PATIENCE ] = new Param( 100.0, Param.ABS_MS ); static_pr.para[ PR_CROSSFADE ] = new Param( 10.0, Param.ABS_MS ); static_pr.paraName = prParaName; // static_pr.superPr = DocumentFrame.static_pr; 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 ggInputFile1, ggInputFile2, ggOutputFile; JCheckBox ggEqP; ParamField ggPatience, ggCrossFade; PathField[] ggInputs; ParamSpace spcPatience; 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( "File I/O", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); ggInputFile1 = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD, "Select input file" ); ggInputFile1.handleTypes( GenericFile.TYPES_SOUND ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Input A", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggInputFile1, GG_INPUTFILE1, null ); ggInputFile2 = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD, "Select input file" ); ggInputFile2.handleTypes( GenericFile.TYPES_SOUND ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Input B", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggInputFile2, GG_INPUTFILE2, null ); ggOutputFile = new PathField( PathField.TYPE_OUTPUTFILE + PathField.TYPE_FORMATFIELD + PathField.TYPE_RESFIELD, "Select output file" ); ggOutputFile.handleTypes( GenericFile.TYPES_SOUND ); ggInputs = new PathField[ 2 ]; ggInputs[ 0 ] = ggInputFile1; ggInputs[ 1 ] = ggInputFile2; ggOutputFile.deriveFrom( ggInputs, "$D0$B0Fuse$B1$E" ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Output file", 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 ); // gui.registerGadget( ggOutputFile.getRateGadget(), GG_OUTPUTRATE ); // -------- Settings -------- gui.addLabel( new GroupLabel( "Settings", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); spcPatience = new ParamSpace( 0.01, 10000.0, 0.01, Param.ABS_MS ); ggPatience = new ParamField( spcPatience ); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Patience", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addParamField( ggPatience, GG_PATIENCE, null ); ggCrossFade = new ParamField( spcPatience ); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Crossfade", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggCrossFade, GG_CROSSFADE, null ); ggEqP = new JCheckBox( "Equal Power" ); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addCheckbox( ggEqP, GG_EQP, 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() { int i, ch, len; long progOff, progLen; float f1; // io AudioFile outF = null; AudioFileDescr outStream = null; Info[] info = new Info[2]; int chanNum, schoko, gaga, outBufOff, outBufLen; float lastSample, switchSample; float[][] outBuf; int inBufLen = 4096; int patience, patienceReset, patienceLeft; int crossfadeLeft, crossfadeLen; float[] crossfade, crossfade2; PathField ggOutput; topLevel: try { // ---- open input, output; init ---- // input chanNum = 0; progOff = 0; progLen = 0; for( i = 0; i < 2; i++ ) { info[ i ] = new Info(); info[ i ].sf = AudioFile.openAsRead( new File( pr.text[ PR_INPUTFILE1 + i ])); info[ i ].stream= info[ i ].sf.getDescr(); info[ i ].len = (int) info[ i ].stream.length; if( i == 0 ) chanNum = info[ i ].stream.channels; else if( chanNum != info[ i ].stream.channels ) throw new IOException( ERR_CHANNELS ); info[ i ].buf = new float[ chanNum ][ inBufLen ]; progLen += info[ i ].len; } if( chanNum < 1 ) throw new IOException( ERR_CHANNELS2 ); if( chanNum != 1 ) System.out.println( WARN_CHANNELS ); // output ggOutput = (PathField) gui.getItemObj( GG_OUTPUTFILE ); if( ggOutput == null ) throw new IOException( ERR_MISSINGPROP ); outStream = new AudioFileDescr( info[ 0 ].stream ); ggOutput.fillStream( outStream ); outF = AudioFile.openAsWrite( outStream ); // .... check running .... if( !threadRunning ) break topLevel; outBufOff = 0; outBufLen = 4096; outBuf = new float[ chanNum ][ outBufLen ]; crossfadeLen= Math.max( 1, (int) (AudioFileDescr.millisToSamples( info[ 0 ].stream, pr.para[ PR_CROSSFADE ].value) + 0.5) ); patience = Math.max( 1, (int) (AudioFileDescr.millisToSamples( info[ 0 ].stream, pr.para[ PR_PATIENCE ].value) + 0.5) ); crossfade = Filter.createWindow( crossfadeLen, Filter.WIN_HANNING ); crossfade2 = new float[ crossfadeLen ]; if( pr.bool[ PR_EQP ]) { for( i = 0; i < crossfadeLen; i++ ) { crossfade2[ i ] = (float) Math.sqrt( 1.0 - crossfade[ i ] * crossfade[ i ] ); } } else { for( i = 0; i < crossfadeLen; i++ ) { crossfade2[ i ] = 1.0f - crossfade[ i ]; } } // ----==================== kulchur ====================---- schoko = 0; gaga = 1; lastSample = 0.0f; patienceLeft = patience; patienceReset = patience; crossfadeLeft = 0; halllo: while( threadRunning ) { for( i = 0; i < 2; i++ ) { if( info[ i ].bufStop == info[ i ].bufOff ) { len = Math.min( info[ i ].len - info[ i ].framesRead, inBufLen ); info[ i ].sf.readFrames( info[ i ].buf, 0, len ); info[ i ].bufOff = 0; info[ i ].bufStop = len; progOff += len; info[ i ].framesRead += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } if( info[ i ].bufStop == info[ i ].bufOff ) { schoko = (i + 1) % 2; gaga = i; break halllo; } } switchSample = info[ gaga ].buf[ 0 ][ info[ gaga ].bufOff ]; for( ; info[ schoko ].bufOff < info[ schoko ].bufStop; info[ schoko ].bufOff++ ) { if( crossfadeLeft == 0 ) { f1 = info[ schoko ].buf[ 0 ][ info[ schoko ].bufOff ]; if( (switchSample >= lastSample && switchSample <= f1) || (switchSample <= lastSample && switchSample >= f1) ) { // yeah switch schoko = gaga; gaga = 1 - schoko; lastSample = switchSample; switchSample = f1; patienceReset = patience; patienceLeft = patience; crossfadeLeft = crossfadeLen - 1; } else { lastSample = f1; patienceLeft--; } } for( ch = 0; ch < chanNum; ch++ ) { // outBuf[ ch ][ outBufOff ] = info[ schoko ].buf[ ch ][ info[ schoko ].bufOff ]; outBuf[ ch ][ outBufOff ] = info[ schoko ].buf[ ch ][ info[ schoko ].bufOff ] * crossfade[ crossfadeLeft ] + info[ gaga ].buf[ ch ][ info[ gaga ].bufOff ] * crossfade2[ crossfadeLeft ]; } if( ++outBufOff == outBufLen ) { // flush outF.writeFrames( outBuf, 0, outBufOff ); outBufOff = 0; } if( patienceLeft == 0 ) { patienceLeft = patienceReset; if( patienceReset > 1 ) patienceReset >>= 1; if( ++info[ gaga ].bufOff < info[ gaga ].bufStop ) { switchSample = info[ gaga ].buf[ 0 ][ info[ gaga ].bufOff ]; } else { continue halllo; } } if( crossfadeLeft > 0 ) { crossfadeLeft--; if( ++info[ gaga ].bufOff == info[ gaga ].bufStop ) continue halllo; } } } // while( threadRunning ) // .... check running .... if( !threadRunning ) break topLevel; outF.writeFrames( outBuf, 0, outBufOff ); // flush outBufOff = 0; // ---- clean up, normalize ---- for( i = 0; i < 2; i++ ) { info[ i ].sf.close(); info[ i ].sf = null; } outF.close(); outF = null; setProgression( 1.0f ); // ---- Finish ---- // inform about clipping/ low level // handleClipping( maxAmp ); } catch( IOException e1 ) { setError( e1 ); } // ---- cleanup (topLevel) ---- for( i = 0; i < 2; i++ ) { if( info[ i ] != null && info[ i ].sf != null ) { info[ i ].sf.cleanUp(); } } if( outF != null ) { outF.cleanUp(); } } // process() // -------- private methods -------- protected class Info { private AudioFile sf; private AudioFileDescr stream; private float[][] buf; private int bufOff = 0; private int bufStop = 0; private int framesRead = 0, len; } }