/* * NeedleholeDlg.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: * 06-Feb-05 added standard deviation + minimum filter + improved speed * 17-Mar-05 added center clipping */ package de.sciss.fscape.gui; import de.sciss.fscape.io.FloatFile; 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.io.AudioFile; import de.sciss.io.AudioFileDescr; import de.sciss.io.IOUtil; 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 moving window * based filtering of a sound. */ public class NeedleholeDlg extends ModulePanel { // -------- private variables -------- // Properties (defaults) private static final int PR_INPUTFILE = 0; // pr.text private static final int PR_OUTPUTFILE = 1; 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_FILTER = 3; private static final int PR_GAIN = 0; // pr.para private static final int PR_LENGTH = 1; private static final int PR_THRESH = 2; private static final int PR_SUBDRY = 0; // pr.bool private static final String PRN_INPUTFILE = "InputFile"; 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_FILTER = "Filter"; private static final String PRN_LENGTH = "Length"; private static final String PRN_THRESH = "Thresh"; private static final String PRN_SUBDRY = "SubDry"; private static final int FILTER_MEDIAN = 0; private static final int FILTER_STDDEV = 1; private static final int FILTER_MINIMUM = 2; private static final int FILTER_CENTER = 3; private static final String prText[] = { "", "" }; private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE }; private static final int prIntg[] = { 0, 0, GAIN_UNITY, FILTER_MEDIAN }; private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_GAINTYPE, PRN_FILTER }; private static final Param prPara[] = { null, new Param( 50.0, Param.ABS_MS ), new Param( -18.0, Param.DECIBEL_AMP )}; private static final String prParaName[] = { PRN_GAIN, PRN_LENGTH, PRN_THRESH }; private static final boolean prBool[] = { false }; private static final String prBoolName[] = { PRN_SUBDRY }; private static final int GG_INPUTFILE = GG_OFF_PATHFIELD + PR_INPUTFILE; 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_FILTER = GG_OFF_CHOICE + PR_FILTER; private static final int GG_GAINTYPE = GG_OFF_CHOICE + PR_GAINTYPE; private static final int GG_GAIN = GG_OFF_PARAMFIELD + PR_GAIN; private static final int GG_LENGTH = GG_OFF_PARAMFIELD + PR_LENGTH; private static final int GG_THRESH = GG_OFF_PARAMFIELD + PR_THRESH; private static final int GG_SUBDRY = GG_OFF_CHECKBOX + PR_SUBDRY; private static PropertyArray static_pr = null; private static Presets static_presets = null; // -------- public methods -------- /** * !! setVisible() bleibt dem Aufrufer ueberlassen */ public NeedleholeDlg() { super( "Needlehole Cherry Blossom" ); 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.paraName = prParaName; // 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 ggInputFile, ggOutputFile; JComboBox ggFilter; JCheckBox ggSubDry; ParamField ggLength, ggThresh; PathField[] ggInputs; Component[] ggGain; ParamSpace[] spcThresh; 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_FILTER: pr.intg[ ID - GG_OFF_CHOICE ] = ((JComboBox) e.getSource()).getSelectedIndex(); reflectPropertyChanges(); break; } } }; // -------- Input-Gadgets -------- con.fill = GridBagConstraints.BOTH; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel( new GroupLabel( "Waveform I/O", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); ggInputFile = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD, "Select input file" ); ggInputFile.handleTypes( GenericFile.TYPES_SOUND ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Input file", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggInputFile, GG_INPUTFILE, 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 ] = ggInputFile; ggOutputFile.deriveFrom( ggInputs, "$D0$F0Bloss$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 ); 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, null ); con.weightx = 0.5; con.gridwidth = GridBagConstraints.REMAINDER; gui.addChoice( (JComboBox) ggGain[ 1 ], GG_GAINTYPE, il ); // -------- Settings -------- gui.addLabel( new GroupLabel( "Peer-to-Peer Needle Sharing", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); ggLength = new ParamField( Constants.spaces[ Constants.absMsSpace ]); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Window length", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggLength, GG_LENGTH, null ); ggFilter = new JComboBox(); ggFilter.addItem( "Median" ); ggFilter.addItem( "Standard Deviation" ); ggFilter.addItem( "Minimum" ); ggFilter.addItem( "Center Clipping" ); con.weightx = 0.1; gui.addLabel( new JLabel( "Filter", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addChoice( ggFilter, GG_FILTER, il ); spcThresh = new ParamSpace[] { Constants.spaces[ Constants.ratioAmpSpace ], Constants.spaces[ Constants.decibelAmpSpace ]}; ggThresh = new ParamField( spcThresh ); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Clip thresh", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addParamField( ggThresh, GG_THRESH, null ); ggSubDry = new JCheckBox( "Subtract dry signal" ); con.weightx = 0.4; // con.gridwidth = 1; gui.addCheckbox( ggSubDry, GG_SUBDRY, il ); 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, j, ch, len; long progOff, progLen; float f1; // io AudioFile inF = null; AudioFile outF = null; AudioFileDescr inStream = null; AudioFileDescr outStream = null; FloatFile[] floatF = null; File[] tempFile = null; int inChanNum; float[][] inBuf; float[][] outBuf; float[] convBuf1, convBuf2; RunningWindowFilter filter; // boolean finished; // Synthesize float gain = 1.0f; // gain abs amp Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz // Smp Init int inLength; int framesRead, framesWritten; int inBufSize, winSize, winSizeH, offStart, outBufSize, transLen; float maxAmp = 0.0f; PathField ggOutput; topLevel: try { // ---- open input, output; init ---- // input inF = AudioFile.openAsRead( new File( pr.text[ PR_INPUTFILE ])); inStream = inF.getDescr(); inChanNum = inStream.channels; inLength = (int) inStream.length; // this helps to prevent errors from empty files! if( inLength * inChanNum < 1 ) throw new EOFException( ERR_EMPTY ); // .... check running .... if( !threadRunning ) break topLevel; progOff = 0; progLen = (long) inLength*3; // output ggOutput = (PathField) gui.getItemObj( GG_OUTPUTFILE ); if( ggOutput == null ) throw new IOException( ERR_MISSINGPROP ); outStream = new AudioFileDescr( inStream ); ggOutput.fillStream( outStream ); outF = AudioFile.openAsWrite( outStream ); // .... check running .... if( !threadRunning ) break topLevel; // ---- filter init ---- winSize = Math.max( 1, (int) (AudioFileDescr.millisToSamples( inStream, pr.para[ PR_LENGTH ].value) + 0.5) ); winSizeH = winSize >> 1; outBufSize = Math.max( 8192, winSize ); inBufSize = outBufSize + winSize; // winSize << 1 inBuf = new float[ inChanNum ][ inBufSize ]; outBuf = new float[ inChanNum ][ outBufSize ]; switch( pr.intg[ PR_FILTER ]) { case FILTER_MEDIAN: filter = new MedianFilter( winSize, inChanNum ); break; case FILTER_STDDEV: filter = new StdDevFilter( winSize, inChanNum ); break; case FILTER_MINIMUM: filter = new MinimumFilter( winSize, inChanNum ); break; case FILTER_CENTER: filter = new CenterClippingFilter( winSize, inChanNum ); break; default: assert false : pr.intg[ PR_FILTER ]; filter = null; break; } // normalization requires temp files if( pr.intg[ PR_GAINTYPE ] == GAIN_UNITY ) { tempFile = new File[ inChanNum ]; floatF = new FloatFile[ inChanNum ]; for( ch = 0; ch < inChanNum; ch++ ) { // first zero them because an exception might be thrown tempFile[ ch ] = null; floatF[ ch ] = null; } for( ch = 0; ch < inChanNum; ch++ ) { tempFile[ ch ] = IOUtil.createTempFile(); floatF[ ch ] = new FloatFile( tempFile[ ch ], GenericFile.MODE_OUTPUT ); } progLen += inLength; } if( pr.intg[ PR_GAINTYPE ] == GAIN_ABSOLUTE ) { gain = (float) ((Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ampRef, null )).value); } // .... check running .... if( !threadRunning ) break topLevel; // ----==================== kulchur ====================---- framesWritten = 0; framesRead = 0; // offStart = winSizeH; offStart = 0; // finished = false; while( threadRunning && framesWritten < inLength ) { // ==================== read input chunk ==================== len = Math.min( inLength - framesRead, inBufSize - offStart ); inF.readFrames( inBuf, offStart, len ); framesRead += len; progOff += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); // .... check running .... if( !threadRunning ) break topLevel; // zero-padding last chunk if( offStart + len < inBufSize ) { //System.out.println( "zero "+(inBufSize-chunkLength)+" frames" ); for( ch = 0; ch < inChanNum; ch++ ) { convBuf1 = inBuf[ ch ]; for( i = offStart + len; i < inBufSize; i++ ) { convBuf1[ i ] = 0.0f; } } } transLen = Math.min( inBufSize - winSize, inLength - framesWritten ); // ==================== needle sharing ==================== filter.process( inBuf, outBuf, 0, 0, transLen ); if( pr.bool[ PR_SUBDRY ]) { for( ch = 0; ch < inChanNum; ch++ ) { convBuf1 = outBuf[ ch ]; convBuf2 = inBuf[ ch ]; for( i = winSizeH, j = 0; j < transLen; i++, j++ ) { convBuf1[j] -= convBuf2[i]; } } } // .... progress .... progOff += transLen; setProgression( (float) progOff / (float) progLen ); // ==================== write output ==================== if( floatF != null ) { for( ch = 0; ch < inChanNum; ch++ ) { convBuf1 = outBuf[ ch ]; floatF[ ch ].writeFloats( convBuf1, 0, transLen ); for( i = 0; i < transLen; i++ ) { f1 = Math.abs( convBuf1[i] ); if( f1 > maxAmp ) { maxAmp = f1; } } } } else { // adjust gain for( ch = 0; ch < inChanNum; ch++ ) { convBuf1 = outBuf[ ch ]; for( j = 0; j < transLen; j++ ) { convBuf1[ j ] *= gain; f1 = Math.abs( convBuf1[j] ); if( f1 > maxAmp ) { maxAmp = f1; } } } outF.writeFrames( outBuf, 0, transLen ); } framesWritten += transLen; // ---- resample : dun ---------------------------------------------------------------------- // shift buffers for( ch = 0; ch < inChanNum; ch++ ) { System.arraycopy( inBuf[ ch ], inBufSize - winSize, inBuf[ ch ], 0, winSize ); } offStart = winSize; // .... progress .... progOff += transLen; setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; // ---- clean up, normalize ---- inF.close(); inF = null; inStream = null; if( pr.intg[ PR_GAINTYPE ] == GAIN_UNITY ) { gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, new Param( 1.0 / maxAmp, Param.ABS_AMP ), null )).value; normalizeAudioFile( floatF, outF, outBuf, gain, 1.0f ); for( ch = 0; ch < inChanNum; ch++ ) { floatF[ ch ].cleanUp(); floatF[ ch ] = null; tempFile[ ch ].delete(); tempFile[ ch ] = null; } maxAmp *= gain; } outF.close(); outF = null; // ---- Finish ---- // inform about clipping/ low level handleClipping( maxAmp ); } catch( IOException e1 ) { setError( e1 ); } catch( OutOfMemoryError e2 ) { inStream = null; outStream = null; inBuf = null; outBuf = null; convBuf1 = null; convBuf2 = null; System.gc(); setError( new Exception( ERR_MEMORY )); } // ---- cleanup (topLevel) ---- if( inF != null ) { inF.cleanUp(); } if( outF != null ) { outF.cleanUp(); } if( floatF != null ) { for( ch = 0; ch < floatF.length; ch++ ) { if( floatF[ ch ] != null ) floatF[ ch ].cleanUp(); if( tempFile[ ch ] != null ) tempFile[ ch ].delete(); } } } // process() // -------- private methods -------- protected void reflectPropertyChanges() { super.reflectPropertyChanges(); Component c; c = gui.getItemObj( GG_THRESH ); if( c != null ) { c.setEnabled( pr.intg[ PR_FILTER ] == FILTER_CENTER ); } } // -------- Window Filter -------- abstract class RunningWindowFilter { public abstract void process( float[][] inBuf, float[][] outBuf, int inOff, int outOff, int len ) throws IOException; } private class StdDevFilter extends RunningWindowFilter { final int winSize; final int channels; final double[][] dcMem; final int winSizeM1; public StdDevFilter( int winSize, int channels ) { this.winSize = winSize; this.channels = channels; winSizeM1 = winSize - 1; dcMem = new double[channels][2]; } public void process( float[][] inBuf, float[][] outBuf, int inOff, int outOff, int len ) throws IOException { int ch, i, j, k, m, n; float[] convBuf2, convBuf3; double[] convBuf4; double d1, d2, mu, mus, omus, sum; for( ch = 0; ch < channels; ch++ ) { convBuf4 = dcMem[ ch ]; convBuf2 = inBuf[ch]; convBuf3 = outBuf[ch]; // calc first full window sum mus = 0.0; for( i = 0, m = inOff; i < winSizeM1; i++, m++ ) { mus += convBuf2[ m ]; // sum all but last one in window } omus = 0.0; for( j = 0, m = inOff, n = outOff; j < len; j++, m++, n++ ) { // shift by one : remove obsolete sample // and add new last window sample mus = mus - omus + convBuf2[ m + winSizeM1 ]; mu = mus / winSize; // mean now sum = 0.0; for( i = 0, k = m; i < winSize; i++, k++ ) { d1 = convBuf2[ k ] - mu; sum += d1 * d1; // variance } d1 = Math.sqrt( sum ); // standard deviation // ---- remove DC ---- d2 = d1 - convBuf4[ 0 ] + 0.99 * convBuf4[ 1 ]; convBuf3[ n ] = (float) d2; convBuf4[ 0 ] = d1; convBuf4[ 1 ] = d2; omus = convBuf2[ m ]; } } // for channels } // process } // class StdDevFilter private class MinimumFilter extends RunningWindowFilter { final int winSize; final int channels; final int winSizeM1; public MinimumFilter( int winSize, int channels ) { this.winSize = winSize; this.channels = channels; winSizeM1 = winSize - 1; } public void process( float[][] inBuf, float[][] outBuf, int inOff, int outOff, int len ) throws IOException { int ch, i, j, k, m, n, minidx; float[] convBuf2, convBuf3; float f1, f2, min; for( ch = 0; ch < channels; ch++ ) { convBuf2 = inBuf[ch]; convBuf3 = outBuf[ch]; minidx = -1; min = 0.0f; for( j = 0, m = inOff, n = outOff; j < len; j++, m++, n++ ) { if( minidx < m ) { // need to find again f1 = Math.abs( convBuf2[ m ]); minidx = m; for( i = 1, k = m + 1; i < winSize; i++, k++ ) { f2 = Math.abs( convBuf2[ k ]); if( f2 < f1 ) { f1 = f2; minidx = k; } } min = convBuf2[ minidx ]; } else { f1 = convBuf2[ m + winSizeM1 ]; if( Math.abs( f1 ) < Math.abs( min )) { min = f1; minidx = m + winSizeM1; } } convBuf3[ n ] = min; minidx--; } } // for channels } // process } // class MinimumFilter private class MedianFilter extends RunningWindowFilter { final int winSize, medianOff, winSizeM; final int channels; final float[][] buf; final int[][] idxBuf; protected MedianFilter( int winSize, int channels ) { this.winSize = winSize; this.channels = channels; buf = new float[channels][winSize]; idxBuf = new int[channels][winSize]; medianOff = winSize >> 1; winSizeM = winSize - 1; } public void process( float[][] inBuf, float[][] outBuf, int inOff, int outOff, int len ) throws IOException { // Util.clear( buf ); int ch, i, j, k, m, n; float[] convBuf1, convBuf2, convBuf3; int[] convBuf4; float f1; for( ch = 0; ch < channels; ch++ ) { convBuf1 = buf[ch]; convBuf2 = inBuf[ch]; convBuf3 = outBuf[ch]; convBuf4 = idxBuf[ch]; m = inOff; n = outOff; convBuf1[0] = convBuf2[m++]; convBuf4[0] = 0; // --- calculate the initial median by sorting inBuf content of length 'winSize --- // XXX this is a really slow sorting algorithm and should be replaced by a fast one // e.g. by exchanging the j-loop by a step-algorithm (stepping right into // i/2 and if f1 < convBuf1[i/2] steppping to i/4 else i*3/4 etc. for( i = 1; i < winSize; i++ ) { f1 = convBuf2[m++]; for( j = 0; j < i; j++ ) { if( f1 < convBuf1[j] ) { System.arraycopy( convBuf1, j, convBuf1, j + 1, i - j ); for( k = 0; k < i; k++ ) { if( convBuf4[k] >= j ) convBuf4[k]++; } break; } } convBuf1[j] = f1; convBuf4[i] = j; } // now the median is approx. (for winSize >> 1) the sample in convBuf1[winSize/2] //System.err.println( "A---unsorted---" ); //for( int p = 0; p < winSize; p++ ) { // System.err.println( p + " : "+convBuf2[inOff+p] ); //} //System.err.println( " --sorted---" ); //for( int p = 0; p < winSize; p++ ) { // System.err.println( p + " : "+convBuf1[p] ); //} // XXX this is a really slow sorting algorithm and should be replaced by a fast one // e.g. by exchanging the j-loop by a step-algorithm (stepping right into // i/2 and if f1 < convBuf1[i/2] steppping to i/4 else i*3/4 etc. // ; also the two arraycopies could be collapsed into one or two shorter ones for( i = 0; i < len; i++ ) { convBuf3[n++] = convBuf1[medianOff]; j = convBuf4[i%winSize]; // index of the element to be removed (i.e. shifted left out of the win) System.arraycopy( convBuf1, j + 1, convBuf1, j, winSizeM - j ); for( k = 0; k < winSize; k++ ) { if( convBuf4[k] > j ) convBuf4[k]--; } f1 = convBuf2[m++]; for( j = 0; j < winSizeM; j++ ) { if( f1 < convBuf1[j] ) { System.arraycopy( convBuf1, j, convBuf1, j + 1, winSizeM - j ); for( k = 0; k < winSize; k++ ) { if( convBuf4[k] >= j ) convBuf4[k]++; } break; } } // j = index of the element to be inserted (i.e. coming from the right side of the win) convBuf1[j] = f1; convBuf4[i%winSize] = j; } //System.err.println( "B---unsorted---" ); //for( int p = 0; p < winSize; p++ ) { // System.err.println( p + " : "+convBuf2[inOff+len+p] ); //} //System.err.println( " ---sorted---" ); //for( int p = 0; p < winSize; p++ ) { // System.err.println( p + " : "+convBuf1[p] ); //} } // for channels } // process } // class MedianFilter // Center Clipping for a variable threshold // which is determined by a running histogram // and a percentage threshold value // // this only works if a) process() is // called on successive chunks; b) samples don't exceed +12 dBFS private class CenterClippingFilter extends RunningWindowFilter { // final int winSize; final int channels; final int winSizeM1; final int[][] histogram; final int threshSum; boolean init = false; public CenterClippingFilter( int winSize, int channels ) { // this.winSize = winSize; this.channels = channels; winSizeM1 = winSize - 1; histogram = new int[ channels ][ 16384 ]; threshSum = (int) (Param.transform( pr.para[ PR_THRESH ], Param.ABS_AMP, new Param( 1.0, Param.ABS_AMP ), null ).value * winSize + 0.5); } public void process( float[][] inBuf, float[][] outBuf, int inOff, int outOff, int len ) throws IOException { float[] convBuf2, convBuf3; int[] convBuf4; int histoIdx, histoSum; float f1, clip; for( int ch = 0; ch < channels; ch++ ) { convBuf4 = histogram[ ch ]; convBuf2 = inBuf[ ch ]; convBuf3 = outBuf[ ch ]; // calc first maximum // max = 0.0f; // for( int i = 0, m = inOff; i < len; i++, m++ ) { // f1 = Math.abs( convBuf2[ m ]); // if( f1 > max ) max = f1; // } // then calc initial histo // for( int i = 0; i < 8192; i++ ) { // convBuf4[ i ] = 0; // } if( !init ) { for( int i = 0, j = inOff; i < winSizeM1; i++, j++ ) { f1 = convBuf2[ j ]; histoIdx = (int) (Math.sqrt( Math.min( 1.0f, Math.abs( f1 / 4))) * 16383.5); // histoIdx = 8191 - (int) (Math.log( Math.max( 4.656613e-10, Math.min( 1.0f, Math.abs( f1 / 4)))) * -381.2437); convBuf4[ histoIdx ]++; } } for( int j = 0, m = inOff, n = outOff; j < len; j++, m++, n++ ) { // shift by one : remove obsolete sample // and add new last window sample f1 = convBuf2[ m + winSizeM1 ]; histoIdx = (int) (Math.sqrt( Math.min( 1.0f, Math.abs( f1 / 4))) * 16383.5); // histoIdx = 8191 - (int) (Math.log( Math.max( 4.656613e-10, Math.min( 1.0f, Math.abs( f1 / 4)))) * -381.2437); convBuf4[ histoIdx ]++; // find thresh for( histoIdx = 0, histoSum = 0; histoIdx < 8192 && histoSum < threshSum; histoIdx++ ) { histoSum += convBuf4[ histoIdx ]; } clip = (float) histoIdx / 16383; clip = clip*clip*4; // clip = (float) (Math.exp( (histoIdx - 8191) / 381.2437 ) * 4); f1 = convBuf2[ m ]; if( f1 >= 0.0f ) { convBuf3[ n ] = Math.max( 0.0f, f1 - clip ); } else { convBuf3[ n ] = Math.min( 0.0f, f1 + clip ); } f1 = convBuf2[ m ]; // now obsolete histoIdx = (int) (Math.sqrt( Math.min( 1.0f, Math.abs( f1 / 4))) * 16383.5); // histoIdx = 8191 - (int) (Math.log( Math.max( 4.656613e-10, Math.min( 1.0f, Math.abs( f1 / 4)))) * -381.2437); convBuf4[ histoIdx ]--; } } // for channels init = true; } // process } // class CenterClippingFilter }