/* * ComplexConvDlg.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: * 29-May-05 uses new (audio) temp files, greatly speed up, bug fixes */ 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.spect.Fourier; import de.sciss.fscape.util.Param; import de.sciss.fscape.util.ParamSpace; import de.sciss.fscape.util.Util; import de.sciss.gui.PathEvent; import de.sciss.gui.PathListener; import de.sciss.io.AudioFile; import de.sciss.io.AudioFileDescr; import javax.swing.*; import java.awt.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.EOFException; import java.io.File; import java.io.IOException; /** * Processing module for convolution of long or complex files * using harddisk based FFT. */ public class ComplexConvDlg extends ModulePanel { // -------- private variables -------- // Properties (defaults) private static final int PR_REINPUTFILE = 0; // pr.text private static final int PR_IMINPUTFILE = 1; private static final int PR_REIMPFILE = 2; private static final int PR_IMIMPFILE = 3; private static final int PR_REOUTPUTFILE = 4; private static final int PR_IMOUTPUTFILE = 5; private static final int PR_OUTPUTTYPE = 0; // pr.intg private static final int PR_OUTPUTRES = 1; private static final int PR_GAINTYPE = 2; private static final int PR_GAIN = 0; // pr.para private static final int PR_MEMORY = 1; private static final int PR_HASIMINPUT = 0; // pr.bool private static final int PR_HASIMIMPULSE = 1; private static final int PR_HASIMOUTPUT = 2; private static final int PR_CEPSTRAL1 = 3; private static final int PR_CEPSTRAL2 = 4; private static final String PRN_REINPUTFILE = "ReInFile"; private static final String PRN_IMINPUTFILE = "ImInFile"; private static final String PRN_REIMPFILE = "ReImpFile"; private static final String PRN_IMIMPFILE = "ImImpFile"; private static final String PRN_REOUTPUTFILE = "ReOutFile"; private static final String PRN_IMOUTPUTFILE = "ImOutFile"; private static final String PRN_OUTPUTTYPE = "OutputType"; private static final String PRN_OUTPUTRES = "OutputReso"; private static final String PRN_HASIMINPUT = "HasImInput"; private static final String PRN_HASIMIMPULSE = "HasImImp"; private static final String PRN_HASIMOUTPUT = "HasImOutput"; private static final String PRN_CEPSTRAL1 = "Cepstral1"; private static final String PRN_CEPSTRAL2 = "Cepstral2"; private static final String PRN_MEMORY = "Memory"; private static final String prText[] = { "", "", "", "", "", "" }; private static final String prTextName[] = { PRN_REINPUTFILE, PRN_IMINPUTFILE, PRN_REIMPFILE, PRN_IMIMPFILE, PRN_REOUTPUTFILE, PRN_IMOUTPUTFILE }; private static final int prIntg[] = { 0, 0, GAIN_UNITY }; private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_GAINTYPE }; private static final Param prPara[] = { null, null }; private static final String prParaName[] = { PRN_GAIN, PRN_MEMORY }; private static final boolean prBool[] = { false, false, false, false, false }; private static final String prBoolName[] = { PRN_HASIMINPUT, PRN_HASIMIMPULSE, PRN_HASIMOUTPUT, PRN_CEPSTRAL1, PRN_CEPSTRAL2 }; private static final int GG_REINPUTFILE = GG_OFF_PATHFIELD + PR_REINPUTFILE; private static final int GG_IMINPUTFILE = GG_OFF_PATHFIELD + PR_IMINPUTFILE; private static final int GG_REIMPFILE = GG_OFF_PATHFIELD + PR_REIMPFILE; private static final int GG_IMIMPFILE = GG_OFF_PATHFIELD + PR_IMIMPFILE; private static final int GG_REOUTPUTFILE = GG_OFF_PATHFIELD + PR_REOUTPUTFILE; private static final int GG_IMOUTPUTFILE = GG_OFF_PATHFIELD + PR_IMOUTPUTFILE; private static final int GG_GAIN = GG_OFF_PARAMFIELD + PR_GAIN; private static final int GG_MEMORY = GG_OFF_PARAMFIELD + PR_MEMORY; 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_GAINTYPE = GG_OFF_CHOICE + PR_GAINTYPE; private static final int GG_HASIMINPUT = GG_OFF_CHECKBOX + PR_HASIMINPUT; private static final int GG_HASIMIMPULSE = GG_OFF_CHECKBOX + PR_HASIMIMPULSE; private static final int GG_HASIMOUTPUT = GG_OFF_CHECKBOX + PR_HASIMOUTPUT; // private static final int GG_CEPSTRAL1 = GG_OFF_CHECKBOX + PR_CEPSTRAL1; // private static final int GG_CEPSTRAL2 = GG_OFF_CHECKBOX + PR_CEPSTRAL2; private static PropertyArray static_pr = null; private static Presets static_presets = null; private JTextField ggInfo; protected ParamField ggMemory; protected final long[] guiInLength = new long[ 4 ]; protected final int[] inChanNum = new int[ 4 ]; // -------- public methods -------- /** * !! setVisible() bleibt dem Aufrufer ueberlassen */ public ComplexConvDlg() { super( "Complex Convolution" ); 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.para = prPara; static_pr.para[ PR_MEMORY ] = new Param( 32.0, Param.NONE ); static_pr.paraName = prParaName; static_pr.bool = prBool; static_pr.boolName = prBoolName; // static_pr.superPr = DocumentFrame.static_pr; fillDefaultAudioDescr( static_pr.intg, PR_OUTPUTTYPE, PR_OUTPUTRES ); fillDefaultGain( static_pr.para, PR_GAIN ); static_presets = new Presets( getClass(), static_pr.toProperties( true )); } presets = static_presets; pr = (PropertyArray) static_pr.clone(); // -------- build GUI -------- GridBagConstraints con; PathField ggImInputFile, ggReInputFile, ggReOutputFile, ggImOutputFile; JCheckBox ggHasImInput, ggHasImOutput; PathField[] ggParent1, ggParent2; Component[] ggGain; gui = new GUISupport(); con = gui.getGridBagConstraints(); con.insets = new Insets( 1, 2, 1, 2 ); ItemListener il = new ItemListener() { public void itemStateChanged( ItemEvent e ) { int ID = gui.getItemID( e ); switch( ID ) { case GG_HASIMINPUT: case GG_HASIMIMPULSE: pr.bool[ ID - GG_OFF_CHECKBOX ] = ((JCheckBox) e.getSource()).isSelected(); reflectPropertyChanges(); recalcSteps(); break; case GG_HASIMOUTPUT: pr.bool[ ID - GG_OFF_CHECKBOX ] = ((JCheckBox) e.getSource()).isSelected(); reflectPropertyChanges(); break; } } }; PathListener pathL = new PathListener() { public void pathChanged( PathEvent e ) { int ID = gui.getItemID( e ); int i; switch( ID ) { case GG_REINPUTFILE: i = 0; break; case GG_IMINPUTFILE: i = 1; break; case GG_REIMPFILE: i = 2; break; case GG_IMIMPFILE: i = 3; break; default: return; } try { AudioFile af = AudioFile.openAsRead( e.getPath() ); AudioFileDescr afd = af.getDescr(); af.close(); guiInLength[ i ] = afd.length; inChanNum[ i ] = afd.channels; } catch( IOException e1 ) { guiInLength[ i ] = 0; inChanNum[ i ] = 0; } recalcSteps(); } }; ParamListener paramL= new ParamListener() { public void paramChanged( ParamEvent e ) { if( e.getSource() == ggMemory ) { pr.para[ PR_MEMORY ] = e.getParam(); recalcSteps(); } } }; // -------- Input-Gadgets -------- con.fill = GridBagConstraints.BOTH; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel( new GroupLabel( "Waveform I/O", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); ggParent1 = new PathField[ 2 ]; for( int i = 0; i < 2; i++ ) { ggReInputFile = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD, "Select real part of input" ); ggReInputFile.handleTypes( GenericFile.TYPES_SOUND ); ggParent1[ i ] = ggReInputFile; con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Input "+(i+1)+" [Real]", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggReInputFile, GG_REINPUTFILE + (i<<1), pathL ); ggImInputFile = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD, "Select imaginary part of input" ); ggImInputFile.handleTypes( GenericFile.TYPES_SOUND ); ggParent2 = new PathField[ 1 ]; ggParent2[ 0 ] = ggReInputFile; ggImInputFile.deriveFrom( ggParent2, "$D0$F0i$X0" ); ggHasImInput = new JCheckBox( "Input "+(i+1)+" [Imaginary]" ); con.gridwidth = 1; con.weightx = 0.1; final int tmpAnchor = con.anchor; con.anchor = GridBagConstraints.EAST; gui.addCheckbox( ggHasImInput, GG_HASIMINPUT + (i<<1), il ); con.anchor = tmpAnchor; con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggImInputFile, GG_IMINPUTFILE + (i<<1), pathL ); // ggCepstral = new JCheckBox(); // con.gridwidth = 1; // con.weightx = 0.1; // gui.addLabel( new JLabel( "Cepstral", SwingConstants.RIGHT )); // con.gridwidth = GridBagConstraints.REMAINDER; // gui.addCheckbox( ggCepstral, GG_CEPSTRAL1 + i, il ); } ggReOutputFile = new PathField( PathField.TYPE_OUTPUTFILE + PathField.TYPE_FORMATFIELD + PathField.TYPE_RESFIELD, "Select output for real part" ); ggReOutputFile.handleTypes( GenericFile.TYPES_SOUND ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Output [Real]", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggReOutputFile, GG_REOUTPUTFILE, pathL ); gui.registerGadget( ggReOutputFile.getTypeGadget(), GG_OUTPUTTYPE ); gui.registerGadget( ggReOutputFile.getResGadget(), GG_OUTPUTRES ); ggImOutputFile = new PathField( PathField.TYPE_OUTPUTFILE, "Select output for imaginary part" ); // ggImOutputFile.handleTypes( GenericFile.TYPES_SOUND ); ggHasImOutput = new JCheckBox( "Output [Imaginary]" ); con.gridwidth = 1; con.weightx = 0.1; final int tmpAnchor = con.anchor; con.anchor = GridBagConstraints.EAST; gui.addCheckbox( ggHasImOutput, GG_HASIMOUTPUT, il ); con.anchor = tmpAnchor; con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggImOutputFile, GG_IMOUTPUTFILE, pathL ); ggParent2 = new PathField[ 1 ]; ggParent2[ 0 ] = ggReOutputFile; ggReOutputFile.deriveFrom( ggParent1, "$D0$B0Con$B1$E" ); ggImOutputFile.deriveFrom( ggParent2, "$D0$F0i$X0" ); ggGain = createGadgets( GGTYPE_GAIN ); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Gain", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( (ParamField) ggGain[ 0 ], GG_GAIN, paramL ); con.weightx = 0.5; con.gridwidth = GridBagConstraints.REMAINDER; gui.addChoice( (JComboBox) ggGain[ 1 ], GG_GAINTYPE, il ); ggMemory = new ParamField( new ParamSpace( 1.0, 2047.0, 1.0, Param.NONE )); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Mem.alloc. [MB]", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggMemory, GG_MEMORY, paramL ); ggInfo = new JTextField( 32 ); ggInfo.setEditable( false ); ggInfo.setBackground( null ); con.weightx = 0.1; gui.addLabel( new JLabel( "\u2192", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addGadget( ggInfo, GG_OFF_OTHER + 0 ); initGUI( this, FLAGS_PRESETS | FLAGS_PROGBAR, gui ); } /** * Transfer values from prop-array to GUI */ public void fillGUI() { super.fillGUI(); super.fillGUI( gui ); for( int i = PR_REINPUTFILE, j = 0; i < PR_IMIMPFILE; i++, j++ ) { try { AudioFile af = AudioFile.openAsRead( new File( pr.text[ i ])); AudioFileDescr afd = af.getDescr(); af.close(); guiInLength[ j ] = afd.length; inChanNum[ j ] = afd.channels; } catch( IOException e1 ) { guiInLength[ j ] = 0; inChanNum[ j ] = 0; } } recalcSteps(); } /** * Transfer values from GUI to prop-array */ public void fillPropertyArray() { super.fillPropertyArray(); super.fillPropertyArray( gui ); } // -------- Processor Interface -------- /** * Translation durchfuehren */ public void process() { int off, len, chunkLength; float f1; boolean imag; long progOff, progLen; float PIf = (float) Math.PI; // (Math.PI / 32); // io AudioFile[] reInF = new AudioFile[ 2 ]; AudioFile[] imInF = new AudioFile[ 2 ]; AudioFile reOutF = null; AudioFile imOutF = null; AudioFileDescr[] reInStream = new AudioFileDescr[ 2 ]; AudioFileDescr[] imInStream = new AudioFileDescr[ 2 ]; AudioFileDescr reOutStream = null; AudioFileDescr imOutStream = null; AudioFile[] tempF = new AudioFile[ 4 ]; // 0,1 = inputs complex-interlvd; 2=reOut, 3=imOut AudioFile tempFe; float[][] inBuf; float[][] fftBuf; float[] convBuf1, convBuf2; PathField ggOutput; // Synthesize Param ampRef; float gain; int outChanNum; long outLength; long[] inLength = new long[ 2 ]; // int[] inChanNum = new int[ 2 ]; long framesRead, framesWritten; int fullFFTsize, convLen, gainLen; float maxAmp = 0.0f; boolean autoConv; // both inputs the same => speed up Descriptor d; topLevel: try { autoConv = (pr.text[ PR_REINPUTFILE ].equals( pr.text[ PR_REIMPFILE ])) && (pr.bool[ PR_HASIMINPUT ] == pr.bool[ PR_HASIMIMPULSE ]) && (!pr.bool[ PR_HASIMINPUT ] || (pr.text[ PR_IMINPUTFILE ].equals( pr.text[ PR_IMIMPFILE ]))) && (pr.bool[ PR_CEPSTRAL1 ] == pr.bool[ PR_CEPSTRAL2 ]); // ---- open inputs ---- for( int i = 0, j = 0; i < (autoConv ? 1 : 2); i++, j += 2 ) { reInF[i] = AudioFile.openAsRead( new File( pr.text[ PR_REINPUTFILE + j ])); reInStream[i] = reInF[i].getDescr(); inChanNum[i] = reInStream[i].channels; inLength[i] = reInStream[i].length; if( pr.bool[ PR_HASIMINPUT + i ]) { imInF[i] = AudioFile.openAsRead( new File( pr.text[ PR_IMINPUTFILE + j ])); imInStream[i]= imInF[i].getDescr(); if( imInStream[i].channels != inChanNum[i] ) throw new IOException( ERR_COMPLEX ); inLength[i] = Math.min( inLength[i], imInStream[i].length ); } // this helps to prevent errors from empty files! if( (inLength[i] * inChanNum[i]) < 1 ) throw new EOFException( ERR_EMPTY ); } // .... check running .... if( !threadRunning ) break topLevel; if( autoConv ) { inChanNum[1] = inChanNum[0]; inLength[1] = inLength[0]; reInStream[1] = reInStream[0]; imInStream[1] = imInStream[0]; } outChanNum = Math.max( inChanNum[0], inChanNum[1] ); // ---- calc lengths ---- d = initDescriptor( inLength, outChanNum ); // ---- open output ---- ggOutput = (PathField) gui.getItemObj( GG_REOUTPUTFILE ); if( ggOutput == null ) throw new IOException( ERR_MISSINGPROP ); reOutStream = new AudioFileDescr( reInStream[0] ); ggOutput.fillStream( reOutStream ); reOutStream.channels = outChanNum; reOutF = AudioFile.openAsWrite( reOutStream ); if( pr.bool[ PR_HASIMOUTPUT ]) { imOutStream = new AudioFileDescr( reInStream[0] ); ggOutput.fillStream( imOutStream ); imOutStream.channels = outChanNum; imOutStream.file = new File( pr.text[ PR_IMOUTPUTFILE ]); imOutF = AudioFile.openAsWrite( imOutStream ); } // .... check running .... if( !threadRunning ) break topLevel; convLen = d.inputLen[0] + d.inputLen[1] - 1; outLength = inLength[0] + inLength[1] - 1; fullFFTsize = d.complex ? (d.fftSize << 1) : d.fftSize; fftBuf = new float[ outChanNum ][ fullFFTsize + 2 ]; inBuf = new float[ outChanNum ][ 8192 ]; // System.out.println( "inLen0 "+inLength[0]+"/step"+inputLen[0]+"; inLen1 "+inLength[1]+"/step"+inputLen[1]+" ; out "+outLength ); //System.out.println( "cnvLen "+convLen+"; fftSize "+d.fftSize+" (full "+fullFFTsize+"); steps0 "+d.steps[0]+"/steps1 "+d.steps[1]+"/tot "+d.totalSteps ); // ---- Progress Length ---- progOff = 0; progLen = pr.bool[ PR_HASIMOUTPUT ] ? (outLength << 1) : outLength; // normalize for( int i = 0; i < (autoConv ? 1 : 2); i++ ) { // per channel: read input, transform, save progLen += (inLength[i] + (long) fullFFTsize * d.steps[i] * 2); // progLen += inChanNum[i] * ((long) inLength[i] + (long) fullFFTsize * steps[i] * 2); } // for( int ch = 0; ch < outChanNum; ch++ ) { long lo1, lo2 = 0; for( int step0 = 0; step0 < d.steps[0]; step0++ ) { for( int step1 = 0; step1 < d.steps[1]; step1++ ) { progLen += 3*fullFFTsize; // load, multiply, transform lo1 = step0 * (long) d.inputLen[0] + step1 * (long) d.inputLen[1]; progLen += Math.min( lo2 - lo1, convLen ); // overlap-add chunkLength = (int) Math.min( convLen, outLength - lo1 ); progLen += chunkLength; lo2 = Math.max( lo2, lo1 + chunkLength ); } } // } // ---- create temp files ---- for( int i = 0; i < 2; i++ ) { // alloc tempF[i] = createTempFile( reInStream[i] ); } for( int i = 2; i < (d.complex ? 4 : 3); i++ ) { tempF[i] = createTempFile( outChanNum, reInStream[0].rate ); } // ============================== create fft files ============================== for( int i = 0; threadRunning && (i < (autoConv ? 1 : 2)); i++ ) { // for both input files reInF[i].seekFrame( 0 ); imag = pr.bool[ PR_HASIMINPUT + i ] || pr.bool[ PR_CEPSTRAL1 + i ]; if( imInF[i] != null ) { imInF[i].seekFrame( 0 ); } for( framesRead = 0; threadRunning && (framesRead < inLength[i]); ) { chunkLength = (int) Math.min( d.inputLen[i], inLength[i] - framesRead ); // ---- read input chunk ---- for( off = 0; threadRunning && (off < chunkLength); ) { len = Math.min( 8192, chunkLength - off ); reInF[i].readFrames( inBuf, 0, len ); if( imag ) { for( int ch = 0; ch < inChanNum[i]; ch++ ) { convBuf1 = inBuf[ch]; convBuf2 = fftBuf[ch]; for( int j = (off << 1), k = 0; k < len; k++, j += 2 ) { convBuf2[ j ] = convBuf1[ k ]; } if( imInF[i] != null ) { imInF[i].readFrames( inBuf, 0, len ); for( int j = (off << 1) + 1, k = 0; k < len; k++, j += 2 ) { convBuf2[ j ] = convBuf1[ k ]; } } } } else { for( int ch = 0; ch < inChanNum[i]; ch++ ) { System.arraycopy( inBuf[ch], 0, fftBuf[ch], off, len ); } } framesRead += len; progOff += len; off += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; for( int ch = 0; ch < inChanNum[i]; ch++ ) { convBuf2 = fftBuf[ch]; // zero padding if( imag ) { for( int j = chunkLength << 1; j < fullFFTsize; ) { convBuf2[ j++ ] = 0.0f; } } else { for( int j = chunkLength; j < d.fftSize; ) { convBuf2[ j++ ] = 0.0f; } } // ---- transform forward ---- if( imag ) { if( pr.bool[ PR_CEPSTRAL1 + i ]) { // take log pre-fft for cepstral filtering if( pr.bool[ PR_HASIMINPUT + i ]) { Fourier.rect2Polar( convBuf2, 0, convBuf2, 0, fullFFTsize ); // Im(ln z)=phi +k2 pi for( int j = 0; j < fullFFTsize; j+=2 ) { // Re(ln z)=ln r f1 = convBuf2[ j ]; if( f1 > 1.266416555e-14f ) { convBuf2[ j ] = (float) Math.log( f1 ); // (float) (Math.log( f1 ) / 32); // fftBuf[ j++ ] /= 32; } else { convBuf2[ j ] = -32f; // -1.0f; // c. ln( 2^-47 ) / 32 // fftBuf[ j++ ] /= 32; } } } else { // only real input, i.e. phi=0 deg or 180 deg for( int j = 0; j < fullFFTsize; ) { f1 = convBuf2[ j ]; if( f1 > 1.266416555e-14f ) { convBuf2[ j++ ] = (float) Math.log( f1 ); // (float) (Math.log( f1 ) / 32); convBuf2[ j++ ] = 0.0f; } else if( f1 < -1.266416555e-14f ) { convBuf2[ j++ ] = (float) Math.log( -f1 ); // (float) Math.log( -f1 ) / 32; convBuf2[ j++ ] = PIf; } else { convBuf2[ j++ ] = -32f; // -1.0f; convBuf2[ j++ ] = 0.0f; } } } } Fourier.complexTransform( convBuf2, d.fftSize, Fourier.FORWARD ); } else { Fourier.realTransform( convBuf2, d.fftSize, Fourier.FORWARD ); if( d.complex ) { // construct complex conjugate negative spectrum for( int k = d.fftSize, j = d.fftSize; k > 0; k -= 2, j += 2 ) { convBuf2[ j ] = convBuf2[ k ]; convBuf2[ j+1 ] = -convBuf2[ k+1 ]; } } } } // for input channels chunkLength = fullFFTsize; progOff += chunkLength; // .... progress .... setProgression( (float) progOff / (float) progLen ); // .... check running .... if( !threadRunning ) break topLevel; // ---- write float chunk ---- for( off = 0; threadRunning && (off < chunkLength); ) { len = Math.min( 8192, chunkLength - off ); // tempF[i].writeFloats( fftBuf, off, len ); tempF[i].writeFrames( fftBuf, off, len ); progOff += len; off += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; } // for input-frames reInF[i].close(); reInF[i] = null; if( imInF[i] != null ) { imInF[i].close(); imInF[i] = null; } } // for input0/1 // ============================== create output files ============================== framesWritten = 0; for( int step0 = 0; threadRunning && (step0 < d.steps[0]); step0++ ) { for( int step1 = 0; threadRunning && (step1 < d.steps[1]); step1++ ) { // tempFe = tempF[ 0 ][ ch % inChanNum[0] ]; tempFe = tempF[ 0 ]; // tempFe.seekFloat( step0 * fullFFTsize ); tempFe.seekFrame( (long) step0 * (long) fullFFTsize ); chunkLength = fullFFTsize; // ---- read fft1 chunk ---- for( off = 0; threadRunning && (off < chunkLength); ) { len = Math.min( 8192, chunkLength - off ); // tempFe.readFloats( fftBuf, off, len ); tempFe.readFrames( fftBuf, off, len ); progOff += len; off += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; // tempFe = tempF[ autoConv ? 0 : 1 ][ ch % inChanNum[1] ]; tempFe = tempF[ autoConv ? 0 : 1 ]; // tempFe.seekFloat( step1 * fullFFTsize ); tempFe.seekFrame( (long) step1 * (long) fullFFTsize ); // ---- read + multiply fft2 chunk ---- for( off = 0; threadRunning && (off < chunkLength); ) { len = Math.min( 8192, chunkLength - off ); tempFe.readFrames( inBuf, 0, len ); for( int ch = outChanNum - 1; ch >= 0; ch-- ) { Fourier.complexMult( inBuf[ ch % inChanNum[1] ], 0, fftBuf[ ch % inChanNum[0] ], off, fftBuf[ ch ], off, len ); } progOff += len; off += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; for( int ch = 0; ch < outChanNum; ch++ ) { convBuf2 = fftBuf[ ch ]; // ---- transform backward ---- if( d.complex ) { Fourier.complexTransform( convBuf2, d.fftSize, Fourier.INVERSE ); for( int i = 0; i < 2; i++ ) { // take post-ifft exp for cepstral filtering if( pr.bool[ PR_CEPSTRAL1 + i ]) { for( int j = 0; j < fullFFTsize; j+=2 ) { // |exp z|=exp(Re(z)) convBuf2[ j ] = (float) Math.exp( convBuf2[ j ]); // (float) Math.exp( fftBuf[ j ] * 32 ); } Fourier.polar2Rect( convBuf2, 0, convBuf2, 0, fullFFTsize ); // arg(exp z)= Im z } } } else { Fourier.realTransform( convBuf2, d.fftSize, Fourier.INVERSE ); } } // for output channels chunkLength = fullFFTsize; progOff += chunkLength; // .... progress .... setProgression( (float) progOff / (float) progLen ); // .... check running .... if( !threadRunning ) break topLevel; long tempOff = (long) step0 * (long) d.inputLen[0] + (long) step1 * (long) d.inputLen[1]; // tempF[ 2 ][ ch ].seekFloat( i ); tempF[ 2 ].seekFrame( tempOff ); if( d.complex ) { // tempF[ 3 ][ ch ].seekFloat( i ); tempF[ 3 ].seekFrame( tempOff ); } chunkLength = (int) Math.min( framesWritten - tempOff, convLen ); // ---- add old overlap ---- for( off = 0; threadRunning && (off < chunkLength); ) { len = Math.min( 8192, chunkLength - off ); // tempF[ 2 ][ ch ].readFloats( convBuf1, 0, len ); tempF[ 2 ].readFrames( inBuf, 0, len ); if( d.complex ) { for( int ch = 0; ch < outChanNum; ch++ ) { convBuf1 = inBuf[ ch ]; convBuf2 = fftBuf[ ch ]; for( int j = 0, k = (off << 1); j < len; j++, k += 2 ) { convBuf2[ k ] += convBuf1[ j ]; } } // tempF[ 3 ][ ch ].readFloats( convBuf1, 0, len ); tempF[ 3 ].readFrames( inBuf, 0, len ); for( int ch = 0; ch < outChanNum; ch++ ) { convBuf1 = inBuf[ ch ]; convBuf2 = fftBuf[ ch ]; for( int j = 0, k = (off << 1) + 1; j < len; j++, k += 2 ) { convBuf2[ k ] += convBuf1[ j ]; } } } else { for( int ch = 0; ch < outChanNum; ch++ ) { Util.add( inBuf[ ch ], 0, fftBuf[ ch ], off, len ); } } progOff += len; off += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; // tempF[ 2 ][ ch ].seekFloat( i ); tempF[ 2 ].seekFrame( tempOff ); if( d.complex ) { // tempF[ 3 ][ ch ].seekFloat( i ); tempF[ 3 ].seekFrame( tempOff ); } chunkLength = (int) Math.min( convLen, outLength - tempOff ); long tempOff2 = tempOff + chunkLength; // (first as a stop-offset) if( step1 < (d.steps[1] - 1) ) { // don't measure parts that will be updated in the future! tempOff2 = Math.min( tempOff2, tempOff + d.inputLen[1] ); // min in this step0 } if( step0 < (d.steps[0] - 1) ) { tempOff2 = Math.min( tempOff2, (long) (step0 + 1) * (long) d.inputLen[0] ); // total min } gainLen = (int) (tempOff2 - tempOff); // (now offset turned to length) // ---- write output chunk ---- for( off = 0; threadRunning && (off < chunkLength); ) { len = Math.min( 8192, chunkLength - off ); if( d.complex ) { // ------- write cmplx for( int ch = 0; ch < outChanNum; ch++ ) { convBuf1 = inBuf[ ch ]; convBuf2 = fftBuf[ ch ]; for( int j = 0, k = (off << 1) + 1; j < len; j++, k += 2 ) { convBuf1[ j ] = convBuf2[ k ]; } } // tempF[ 3 ][ ch ].writeFloats( convBuf1, 0, len ); tempF[ 3 ].writeFrames( inBuf, 0, len ); // measure gain if( pr.bool[ PR_HASIMOUTPUT ]) { for( int ch = 0; ch < outChanNum; ch++ ) { convBuf1 = inBuf[ ch ]; for( int j = Math.min( len, gainLen ); j > 0; ) { f1 = Math.abs( convBuf1[ --j ]); if( f1 > maxAmp ) { maxAmp = f1; } } } } for( int ch = 0; ch < outChanNum; ch++ ) { convBuf1 = inBuf[ ch ]; convBuf2 = fftBuf[ ch ]; for( int j = 0, k = (off << 1); j < len; j++, k += 2 ) { convBuf1[ j ] = convBuf2[ k ]; } } // tempF[ 2 ][ ch ].writeFloats( convBuf1, 0, len ); tempF[ 2 ].writeFrames( inBuf, 0, len ); // measure gain for( int ch = 0; ch < outChanNum; ch++ ) { convBuf1 = inBuf[ ch ]; for( int j = Math.min( len, gainLen ); j > 0; ) { f1 = Math.abs( convBuf1[ --j ]); if( f1 > maxAmp ) { maxAmp = f1; } } } } else { // ------- write real // tempF[ 2 ][ ch ].writeFloats( fftBuf, off, len ); tempF[ 2 ].writeFrames( fftBuf, off, len ); // measure gain for( int ch = 0; ch < outChanNum; ch++ ) { convBuf2 = fftBuf[ ch ]; for( int j = off, k = Math.min( len, gainLen ) + off; j < k; ) { f1 = Math.abs( convBuf2[ j++ ]); if( f1 > maxAmp ) { maxAmp = f1; } } } } progOff += len; off += len; gainLen -= len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; framesWritten = Math.max( framesWritten, tempOff + chunkLength ); } // for step1 } // for step0 // ============================== normalize output ============================== // System.out.println( "progOff "+progOff+"; len "+progLen ); // ---- delete obsolete temp files ---- for( int i = 0; i < 2; i++ ) { if( tempF[i] != null ) deleteTempFile( tempF[ i ]); } // .... check running .... if( !threadRunning ) break topLevel; // ---- norm ---- if( pr.intg[ PR_GAINTYPE ] == GAIN_UNITY ) { ampRef = new Param( 1.0 / (double) maxAmp, Param.ABS_AMP ); } else { ampRef = new Param( 1.0, Param.ABS_AMP ); } gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ampRef, null )).value; f1 = (imOutF != null) ? ((1.0f + getProgression()) / 2) : 1.0f; normalizeAudioFile( tempF[2], reOutF, inBuf, gain, f1 ); if( imOutF != null ) { normalizeAudioFile( tempF[3], imOutF, inBuf, gain, 1.0f ); } maxAmp *= gain; // ---- delete obsolete temp files ---- for( int i = 2; i < 4; i++ ) { if( tempF[i] != null ) deleteTempFile( tempF[ i ]); } // .... check running .... if( !threadRunning ) break topLevel; reOutF.close(); reOutF = null; if( imOutF != null ) { imOutF.close(); imOutF = null; } // ---- Finish ---- // inform about clipping/ low level handleClipping( maxAmp ); } catch( IOException e1 ) { setError( e1 ); } catch( OutOfMemoryError e2 ) { convBuf1= null; fftBuf = null; inBuf = null; System.gc(); setError( new Exception( ERR_MEMORY )); } finally { // ---- cleanup (topLevel) ---- for( int i = 0; i < 2; i++ ) { if( reInF[i] != null ) reInF[i].cleanUp(); if( imInF[i] != null ) imInF[i].cleanUp(); } if( reOutF != null ) reOutF.cleanUp(); if( imOutF != null ) imOutF.cleanUp(); } } // process() protected void recalcSteps() { int outChanNum = Math.max( inChanNum[ 0 ], inChanNum[ 2 ]); long[] minLength = new long[] { pr.bool[ PR_HASIMINPUT ] ? Math.min( guiInLength[ 0 ], guiInLength[ 1 ]) : guiInLength[ 0 ], pr.bool[ PR_HASIMIMPULSE ] ? Math.min( guiInLength[ 2 ], guiInLength[ 3 ]) : guiInLength[ 2 ], }; if( outChanNum > 0 && minLength[ 0 ] > 0 && minLength[ 1 ] > 0 ) { final Descriptor d = initDescriptor( minLength, outChanNum ); ggInfo.setText( "process divided into "+d.totalSteps+" steps." ); } else { ggInfo.setText( null ); } } private Descriptor initDescriptor( long[] inLength, int outChanNum ) { boolean autoConv; int j, minSize; long lo1, lo2, lo4; Descriptor d = new Descriptor(); autoConv = (pr.text[ PR_REINPUTFILE ].equals( pr.text[ PR_REIMPFILE ])) && (pr.bool[ PR_HASIMINPUT ] == pr.bool[ PR_HASIMIMPULSE ]) && (!pr.bool[ PR_HASIMINPUT ] || (pr.text[ PR_IMINPUTFILE ].equals( pr.text[ PR_IMIMPFILE ]))) && (pr.bool[ PR_CEPSTRAL1 ] == pr.bool[ PR_CEPSTRAL2 ]); d.complex = pr.bool[ PR_HASIMINPUT ] || pr.bool[ PR_HASIMIMPULSE ] || pr.bool[ PR_HASIMOUTPUT ] || pr.bool[ PR_CEPSTRAL1 ] || pr.bool[ PR_CEPSTRAL2 ]; minSize = (int) Math.min( 0x3FFFFFFF, ((((long) pr.para[ PR_MEMORY].value * 1024 * 1024) >> (d.complex ? 5 : 4)) * 3 / outChanNum)); // int minSize = (int) Math.min( 0x3FFFFFFF, ((Runtime.getRuntime().freeMemory() >> (complex ? 5 : 4)) * 3 / outChanNum)); for( d.fftSize = 32; d.fftSize <= minSize; d.fftSize <<= 1 ) ; if( !autoConv ) { // ----- different inputs ----- lo2 = inLength[0]; lo4 = (inLength[0] * inLength[1] + 1) * d.fftSize * d.fftSize; // worst case // calc best performance long bestCost = Long.MAX_VALUE; int bestFFTsize = 0; long bestInpLen = 0; do { // lo3 = lo4; // lo5 = lo2; d.fftSize >>= 1; j = (int) Math.min( inLength[0], d.fftSize + 1 - Math.min( 32, inLength[1] )); lo1 = inLength[0] * inLength[1] + 1; // worst case lo2 = inLength[0]; for( int i = (int) Math.min( 32, inLength[0] ); i < j; i += 512 ) { d.inputLen[0] = i; d.inputLen[1] = (int) Math.min( inLength[1], d.fftSize + 1 - i ); d.steps[0] = (int) ((inLength[0] + d.inputLen[0] - 1) / d.inputLen[0]); d.steps[1] = (int) ((inLength[1] + d.inputLen[1] - 1) / d.inputLen[1]); d.totalSteps = ((long) d.steps[0] * (long) d.steps[1]) + (long) d.steps[0] + (long) d.steps[1]; //System.err.println( "with i = "+i+" : d.inputLen[0] "+d.inputLen[0]+"; d.inputLen[1] "+d.inputLen[1]+"; d.steps[0] "+d.steps[0]+"; d.steps[1] "+d.steps[1]+"; d.totalSteps "+d.totalSteps+"; d.fftSize "+d.fftSize ); if( d.totalSteps < lo1 ) { lo2 = i; // minimum step-num so far lo1 = d.totalSteps; //System.err.println( " --> lo2 = "+i+"; lo1 = "+lo1 ); } } lo4 = (long) (lo1 * d.fftSize * Math.log( d.fftSize )); //System.err.println( "= cost "+lo4 ); if( lo4 < bestCost ) { bestCost = lo4; bestFFTsize = d.fftSize; bestInpLen = lo2; } } while( d.fftSize > 32 ); d.fftSize = bestFFTsize; d.inputLen[0] = (int) bestInpLen; d.inputLen[1] = (int) Math.min( inLength[1], d.fftSize + 1 - d.inputLen[0] ); } else { // ----- same inputs ----- lo1 = inLength[0] << 2; while( d.fftSize >= lo1 ) d.fftSize >>= 1; d.inputLen[0] = (int) Math.min( inLength[0], d.fftSize >> 1 ); d.inputLen[1] = d.inputLen[0]; } d.steps[0] = (int) ((inLength[0] + d.inputLen[0] - 1) / d.inputLen[0]); d.steps[1] = (int) ((inLength[1] + d.inputLen[1] - 1) / d.inputLen[1]); //System.err.println( "d.inputLen[0] "+d.inputLen[0]+"; d.inputLen[1] "+d.inputLen[1]+"; d.steps[0] "+d.steps[0]+"; d.steps[1] "+d.steps[1]+"; d.fftSize "+d.fftSize ); d.totalSteps = (long) d.steps[0] * (long) d.steps[1]; return d; } protected void reflectPropertyChanges() { super.reflectPropertyChanges(); Component c; boolean b; c = gui.getItemObj( GG_IMINPUTFILE ); if( c != null ) { c.setEnabled( pr.bool[ PR_HASIMINPUT ]); } c = gui.getItemObj( GG_IMIMPFILE ); if( c != null ) { c.setEnabled( pr.bool[ PR_HASIMIMPULSE ]); } b = pr.bool[ PR_HASIMINPUT ] || pr.bool[ PR_HASIMIMPULSE ]; c = gui.getItemObj( GG_HASIMOUTPUT ); if( c != null ) { c.setEnabled( b ); } c = gui.getItemObj( GG_IMOUTPUTFILE ); if( c != null ) { c.setEnabled( b && pr.bool[ PR_HASIMOUTPUT ]); } } protected static class Descriptor { int fftSize; long totalSteps; boolean complex; int[] inputLen = new int[ 2 ]; int[] steps = new int[ 2 ]; } }