/* * ChebychevDlg.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: * 21-May-05 finally implemented frequency and time interpolation * 31-Aug-05 uses new temp file style, added drop-envelope-tracking option */ 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.Constants; import de.sciss.fscape.util.Filter; import de.sciss.fscape.util.Param; import de.sciss.fscape.util.ParamSpace; import de.sciss.fscape.util.Util; import de.sciss.io.AudioFile; import de.sciss.io.AudioFileDescr; 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; /** * Processing module for multi-band waveshaping using * chebychev polynomials to describe the amount of * each overtone. */ public class ChebychevDlg 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_SPECTSLIDER1 = 2; private static final int PR_SPECTSLIDER2 = 3; private static final int PR_SPECTSLIDER3 = 4; private static final int PR_SPECTSLIDER4 = 5; private static final int PR_SPECTSLIDER5 = 6; private static final int PR_SPECTSLIDER6 = 7; private static final int PR_SPECTSLIDER7 = 8; private static final int PR_SPECTSLIDER8 = 9; private static final int PR_SPECTSLIDER9 = 10; 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_FILTERLEN = 3; private static final int PR_GAIN = 0; // pr.para private static final int PR_LOFREQ = 1; private static final int PR_MIDFREQ = 2; private static final int PR_HIFREQ = 3; private static final int PR_MIDTIME = 4; private static final int PR_DRYMIX = 5; private static final int PR_WETMIX = 6; private static final int PR_ROLLOFF = 7; private static final int PR_BANDSPEROCT = 8; // private static final int PR_DROPENV = 0; // pr.bool // private static final int FLT_SHORT = 0; // private static final int FLT_MEDIUM = 1; private static final int FLT_LONG = 2; // private static final int FLT_VERYLONG = 3; 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_SPECTSLIDER1 = "Shape1"; private static final String PRN_SPECTSLIDER2 = "Shape2"; private static final String PRN_SPECTSLIDER3 = "Shape3"; private static final String PRN_SPECTSLIDER4 = "Shape4"; private static final String PRN_SPECTSLIDER5 = "Shape5"; private static final String PRN_SPECTSLIDER6 = "Shape6"; private static final String PRN_SPECTSLIDER7 = "Shape7"; private static final String PRN_SPECTSLIDER8 = "Shape8"; private static final String PRN_SPECTSLIDER9 = "Shape9"; private static final String PRN_OUTPUTRES = "OutputReso"; private static final String PRN_FILTERLEN = "FilterLen"; private static final String PRN_LOFREQ = "LoFreq"; private static final String PRN_MIDFREQ = "MidFreq"; private static final String PRN_HIFREQ = "HiFreq"; private static final String PRN_MIDTIME = "MidTime"; private static final String PRN_DRYMIX = "DryMix"; private static final String PRN_WETMIX = "WetMix"; private static final String PRN_ROLLOFF = "RollOff"; private static final String PRN_BANDSPEROCT = "BandsPerOct"; private static final String PRN_DROPENV = "DropEnv"; private static final int SLIDER_NUM = 9; private static final int MAX_HARMON = 8; private static final String prText[] = new String[ PR_SPECTSLIDER1 + SLIDER_NUM ]; private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE, PRN_SPECTSLIDER1, PRN_SPECTSLIDER2, PRN_SPECTSLIDER3, PRN_SPECTSLIDER4, PRN_SPECTSLIDER5, PRN_SPECTSLIDER6, PRN_SPECTSLIDER7, PRN_SPECTSLIDER8, PRN_SPECTSLIDER9 }; private static final int prIntg[] = { 0, 0, 0, FLT_LONG }; private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_GAINTYPE, PRN_FILTERLEN }; private static final Param prPara[] = new Param[ 9 ]; private static final String prParaName[] = { PRN_GAIN, PRN_LOFREQ, PRN_MIDFREQ, PRN_HIFREQ, PRN_MIDTIME, PRN_DRYMIX, PRN_WETMIX, PRN_ROLLOFF, PRN_BANDSPEROCT }; private static final boolean prBool[] = { false }; private static final String prBoolName[] = { PRN_DROPENV }; 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_GAINTYPE = GG_OFF_CHOICE + PR_GAINTYPE; private static final int GG_FILTERLEN = GG_OFF_CHOICE + PR_FILTERLEN; // private static final int GG_DROPENV = GG_OFF_CHECKBOX + PR_DROPENV; private static final int GG_GAIN = GG_OFF_PARAMFIELD + PR_GAIN; private static final int GG_LOFREQ = GG_OFF_PARAMFIELD + PR_LOFREQ; private static final int GG_MIDFREQ = GG_OFF_PARAMFIELD + PR_MIDFREQ; private static final int GG_HIFREQ = GG_OFF_PARAMFIELD + PR_HIFREQ; private static final int GG_MIDTIME = GG_OFF_PARAMFIELD + PR_MIDTIME; // private static final int GG_DRYMIX = GG_OFF_PARAMFIELD + PR_DRYMIX; // private static final int GG_WETMIX = GG_OFF_PARAMFIELD + PR_WETMIX; // private static final int GG_ROLLOFF = GG_OFF_PARAMFIELD + PR_ROLLOFF; private static final int GG_BANDSPEROCT = GG_OFF_PARAMFIELD + PR_BANDSPEROCT; private static final int GG_PANEL = GG_OFF_OTHER + 0; private static final int GG_SPECTSLIDER1 = GG_OFF_OTHER + PR_SPECTSLIDER1; // thru 9 ! private static PropertyArray static_pr = null; private static Presets static_presets = null; protected JButton ggToTime, ggToFreq; // -------- public methods -------- public ChebychevDlg() { super("Chebyshev Waveshaping"); init2(); } protected void buildGUI() { if( static_pr == null ) { static_pr = new PropertyArray(); static_pr.text = prText; static_pr.textName = prTextName; static_pr.text[ PR_INPUTFILE ] = ""; static_pr.text[ PR_OUTPUTFILE ] = ""; for( int i = 0; i < SLIDER_NUM; i++ ) { static_pr.text[ PR_SPECTSLIDER1 + i ] = "1.0,0,0,0,0,0,0,0"; } static_pr.intg = prIntg; static_pr.intgName = prIntgName; static_pr.bool = prBool; static_pr.boolName = prBoolName; static_pr.para = prPara; static_pr.para[ PR_LOFREQ ] = new Param( 400.0, Param.ABS_HZ ); static_pr.para[ PR_MIDFREQ ] = new Param( 2000.0, Param.ABS_HZ ); static_pr.para[ PR_HIFREQ ] = new Param( 9000.0, Param.ABS_HZ ); static_pr.para[ PR_DRYMIX ] = new Param( 100.0, Param.FACTOR_AMP ); static_pr.para[ PR_WETMIX ] = new Param( 25.0, Param.FACTOR_AMP ); static_pr.para[ PR_ROLLOFF ] = new Param( 12.0, Param.OFFSET_SEMITONES ); static_pr.para[ PR_BANDSPEROCT ] = new Param( 36.0, Param.NONE ); static_pr.para[ PR_MIDTIME ] = new Param( 50.0, Param.FACTOR_TIME ); 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; PathField[] ggParent1; JComboBox ggFltLen; ParamField ggLoFreq, ggMidFreq, ggHiFreq, ggBandsPerOct, ggMidTime; ParamSpace[] spcHiCut, spcMidTime; Component[] ggGain; SpectSlider ggShape; JPanel shapePanel; JPanel p2; Box b; gui = new GUISupport(); con = gui.getGridBagConstraints(); con.insets = new Insets( 1, 2, 1, 2 ); ActionListener al = new ActionListener() { public void actionPerformed( ActionEvent e ) { SpectSlider ggSpect = null; int focussed; for( focussed = 0; focussed < SLIDER_NUM; focussed++ ) { ggSpect = (SpectSlider) gui.getItemObj( GG_SPECTSLIDER1 + focussed ); if( ggSpect.hasFocus() ) break; } if( focussed == SLIDER_NUM ) return; float[] bands = ggSpect.getBands(); int col = focussed % 3; int row = focussed / 3; if( e.getSource() == ggToTime ) { for( int i = col; i < SLIDER_NUM; i += 3 ) { if( i != focussed ) ((SpectSlider) gui.getItemObj( GG_SPECTSLIDER1 + i )).setBands( bands ); } } else if( e.getSource() == ggToFreq ) { for( int i = row * 3; i < (row + 1) * 3; i++ ) { if( i != focussed ) ((SpectSlider) gui.getItemObj( GG_SPECTSLIDER1 + i )).setBands( bands ); } } } }; // -------- I/O-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 ); 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 ); ggParent1 = new PathField[ 1 ]; ggParent1[ 0 ] = ggInputFile; ggOutputFile.deriveFrom( ggParent1, "$D0$F0Chby$E" ); 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, null ); // -------- Settings-Gadgets -------- gui.addLabel( new GroupLabel( "Shape Settings", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); ggLoFreq = new ParamField( Constants.spaces[ Constants.absHzSpace ]); con.weightx = 0.1; con.weighty = 0.0; con.gridwidth = 1; gui.addLabel( new JLabel( "Low Freq", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggLoFreq, GG_LOFREQ, null ); spcHiCut = new ParamSpace[ 3 ]; spcHiCut[0] = Constants.spaces[ Constants.absHzSpace ]; spcHiCut[1] = Constants.spaces[ Constants.offsetHzSpace ]; spcHiCut[2] = Constants.spaces[ Constants.offsetSemitonesSpace ]; ggMidFreq = new ParamField( spcHiCut ); ggMidFreq.setReference( ggLoFreq ); con.weightx = 0.1; gui.addLabel( new JLabel( "Mid Freq", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggMidFreq, GG_MIDFREQ, null ); ggHiFreq = new ParamField( spcHiCut ); ggHiFreq.setReference( ggLoFreq ); con.weightx = 0.1; gui.addLabel( new JLabel( "High Freq", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addParamField( ggHiFreq, GG_HIFREQ, null ); shapePanel = new JPanel( new GridLayout( 3, 3, 4, 4 )); p2 = new JPanel( new BorderLayout() ); b = Box.createVerticalBox(); for( int i = 0; i < SLIDER_NUM; i++ ) { ggShape = new SpectSlider( MAX_HARMON, 32 /* 59 */, 59 ); shapePanel.add( ggShape ); gui.registerGadget( ggShape, GG_SPECTSLIDER1 + i ); } con.weightx = 1.0; con.weighty = 1.0; p2.add( shapePanel, BorderLayout.CENTER ); ggToTime = new JButton( "\u2192all times" ); ggToTime.setFocusable( false ); ggToTime.addActionListener( al ); ggToFreq = new JButton( "\u2192all freq" ); ggToFreq.setFocusable( false ); ggToFreq.addActionListener( al ); b.add( ggToTime ); b.add( ggToFreq ); p2.add( b, BorderLayout.EAST ); gui.addGadget( p2, GG_PANEL ); spcMidTime = new ParamSpace[ 3 ]; spcMidTime[0] = Constants.spaces[ Constants.absMsSpace ]; spcMidTime[1] = Constants.spaces[ Constants.absBeatsSpace ]; spcMidTime[2] = Constants.spaces[ Constants.factorTimeSpace ]; ggMidTime = new ParamField( spcMidTime ); con.weightx = 0.1; con.weighty = 0.0; con.gridwidth = 1; gui.addLabel( new JLabel( "Mid Time", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggMidTime, GG_MIDTIME, null ); ggBandsPerOct = new ParamField( new ParamSpace( 1.0, 256.0, 1.0, Param.NONE )); con.weightx = 0.1; gui.addLabel( new JLabel( "Bands per Oct.", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggBandsPerOct, GG_BANDSPEROCT, null ); ggFltLen = new JComboBox(); ggFltLen.addItem( "Short" ); ggFltLen.addItem( "Medium" ); ggFltLen.addItem( "Long" ); ggFltLen.addItem( "Very long" ); con.weightx = 0.1; gui.addLabel( new JLabel( "Filter length", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addChoice( ggFltLen, GG_FILTERLEN, null ); // ggDropEnv = new JCheckBox(); // con.weightx = 0.1; // gui.addLabel( new JLabel( "Drop Envelope Tracking", SwingConstants.RIGHT )); // con.weightx = 0.4; // con.gridwidth = GridBagConstraints.REMAINDER; // gui.addCheckbox( ggDropEnv, GG_DROPENV, il ); initGUI( this, FLAGS_PRESETS | FLAGS_PROGBAR, gui ); } public void fillGUI() { super.fillGUI(); super.fillGUI(gui); SpectSlider ggSpect; for (int i = 0; i < SLIDER_NUM; i++) { ggSpect = (SpectSlider) gui.getItemObj(GG_SPECTSLIDER1 + i); if (ggSpect != null) { ggSpect.setBands(SpectSlider.valueOf(pr.text[PR_SPECTSLIDER1 + i])); } } } public void fillPropertyArray() { super.fillPropertyArray(); super.fillPropertyArray(gui); SpectSlider ggSpect; for (int i = 0; i < SLIDER_NUM; i++) { ggSpect = (SpectSlider) gui.getItemObj(GG_SPECTSLIDER1 + i); if (ggSpect != null) { pr.text[PR_SPECTSLIDER1 + i] = ggSpect.toString(); } } } // -------- Processor Interface -------- /* * How it works: input is low and highpass filtered. low/high are processed separately as * "dry parts", the difference from the original signal is fed to the shaper stage: it is * split up into a user defined number of bands per octave (cosine-modulated sinc FIR filters * with variable taps) -> peak amp. for each band is tracked; gain is normalized, chebychev * polynomial distortion is applied and original gain restored. * * idea taken from fernandez (dafx/cost), multiband waveshaping */ // XXX TODO: SpectSlider / ... protected void process() { int i, j, k, m, len, off, ch, chunkLength, chunkLength2, band; long progOff, progLen; double d1, d2, loFreq, hiFreq, midFreq, freqFactor; float f1, f2, f3, weight; boolean beforeMidTime; // io AudioFile inF = null; AudioFile outF = null; AudioFileDescr inStream; AudioFileDescr outStream; AudioFile tempF = null; // buffers float[] lowpass, highpass, hpFFTBuf, lpFFTBuf, dwFFTBuf, convFFTBuf; float[][] shpFFTBuf, lpOverBuf, hpOverBuf, sliceFFTBuf; float[][][] convOverBuf; float[] convBuf1, convBuf2, convBuf3; float[] dwWin, shpLowWin, shpHighWin; float[][] inBuf, outBuf; int inChanNum, inLength, inputLen, outSkip; int framesRead, framesWritten; Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz float gain = 1.0f; // gain abs amp float maxAmp = 0.0f; PathField ggOutput; int dwNumPeriods, dwHalfWinSize, dwFltLength, dwFFTLength, dwOverLen; int shpNumPeriods, shpMinHalfWin, shpMaxHalfWin, shpHighHalfWin, shpLowHalfWin; int shpMinFFTLen, shpMaxFFTLen, shpFFTLen, shpFFTLenIdx, numShpLen, shpFFTCounter; int inputConv = 0, convNum = 0, convOverLen = 0, convShift; float[] crossFreqs, cosineFreqs; int[] shpFFTLength, shpFFTCount, shpHalfWinSize, shpConvNum; double freqNorm, freqBase, cosineNorm, cosineBase, cosineBaseSqr; float nyquist; int numBands; int midTime; // in samples // drove my cheby to the leby ChebyTracker[][] chebys; ChebyTracker chebyTrk; float[] chbPeakBuf; // see ChebyTracker class description! int chbPeakBufLen, chbPeakBufStart, chbPeakBufOff, chbPeakValid, chbPeakScope; float chbPeakValue; boolean chbPeakBufCont; float[] chbHarmonWeight1, chbHarmonWeight2, chbHarmonWeight3, chbHarmonWeight4, chbHarmonWeight5, chbHarmonWeight6, chbHarmonWeight7, chbHarmonWeight8, chbHarmonWeight9; int chbHarmonNum; float chbT0, chbT1, chbT2, chbTn; float dryGain; final boolean dropEnv = false; // no marche pas topLevel: try { // ---- open input, output ---- // 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; // 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; // ---- calculate filters ---- loFreq = Param.transform( pr.para[ PR_LOFREQ ], Param.ABS_HZ, null, null ).value; midFreq = Param.transform( pr.para[ PR_MIDFREQ ], Param.ABS_HZ, pr.para[ PR_LOFREQ ], null ).value; hiFreq = Math.min( inStream.rate * 0.2475, Param.transform( pr.para[ PR_HIFREQ ], Param.ABS_HZ, pr.para[ PR_LOFREQ ], null ).value); freqFactor = Math.pow( 2.0, 1.0 / pr.para[ PR_BANDSPEROCT ].value); nyquist = (float) (inStream.rate * 0.49); // well, almost ;) numBands = (int) (Math.log( hiFreq / loFreq ) / Math.log( freqFactor )); crossFreqs = new float[ numBands+1 ]; cosineFreqs = new float[ numBands+1 ]; shpHalfWinSize = new int[ numBands ]; d1 = loFreq; d2 = d1 * freqFactor; midTime = (int) (AudioFileDescr.millisToSamples( inStream, Param.transform( pr.para[ PR_MIDTIME ], Param.ABS_MS, new Param( AudioFileDescr.samplesToMillis( inStream, inLength ), Param.ABS_MS ), null ).value) + 0.5); midTime = Math.max( 1, Math.min( midTime, inLength - 1 )); // ---- calc bandpass freqs ---- for( i = 0; i <= numBands; i++ ) { crossFreqs[i] = (float) d1; cosineFreqs[i] = (float) (Math.sqrt( d2 * d1 ) - d1); d1 = d2; d2 = d1 * freqFactor; } hiFreq = crossFreqs[numBands]; dwNumPeriods = 6; // << pr.intg[ PR_FILTERLEN ]; dwHalfWinSize = Math.max( 1, (int) ((double) dwNumPeriods * inStream.rate / loFreq + 0.5) ); freqNorm = Constants.PI2 / inStream.rate; cosineNorm = 4.0 / (Math.PI*Math.PI); dwFltLength = dwHalfWinSize + dwHalfWinSize; j = dwFltLength + dwFltLength - 1; for( dwFFTLength = 2; dwFFTLength < j; dwFFTLength <<= 1 ) ; inputLen = dwFFTLength - dwFltLength + 1; dwOverLen = dwFFTLength - inputLen; // ---- calc highpass filter ---- // use a narrow window here to decrease time smearing!! (large window calculated in lowpass section) k = Math.max( 1, (int) ((double) dwNumPeriods * inStream.rate / hiFreq + 0.5) ); dwWin = Filter.createFullWindow( k << 1, Filter.WIN_BLACKMAN ); freqBase = freqNorm * (inStream.rate/2); highpass = new float[ dwFFTLength + 2 ]; Util.clear( highpass ); highpass[ dwHalfWinSize ] = (float) freqBase; freqBase = freqNorm * hiFreq * 0.8; // / rollOff; cosineBase = freqNorm * hiFreq * 0.04; // (1.0 - 1.0 / rollOff); cosineBaseSqr = cosineBase * cosineBase; for( j = 1; j < k; j++ ) { d1 = (Math.sin( freqBase * j ) / (double) j); // raised cosine modulation d2 = cosineNorm * cosineBaseSqr * j * j; d1 *= (Math.cos( cosineBase * j ) / (1.0 - d2)); highpass[ dwHalfWinSize+j ]-= (float) d1; highpass[ dwHalfWinSize-j ]-= (float) d1; } highpass[ dwHalfWinSize ] -= (float) freqBase; Util.mult( dwWin, 0, highpass, dwHalfWinSize - k, dwWin.length ); Fourier.realTransform( highpass, dwFFTLength, Fourier.FORWARD ); Util.mult( highpass, 0, highpass.length, (float) (1.0 / Math.PI) ); // for( i = 0; i < highpass.length; i += 2 ) { // highpass[ i ] = (float) Math.sqrt( highpass[ i ]*highpass[ i ]+highpass[ i+1 ]*highpass[ i+1 ] ); // highpass[ i+1 ] = highpass[ i ]; // } // highpass[ (int) (hiFreq / inStream.smpRate * 2 * highpass.length) ] = -1.0f; // Debug.view( highpass, "highpass" ); // ---- calc lowpass filter ---- dwWin = Filter.createFullWindow( dwFltLength, Filter.WIN_BLACKMAN ); freqBase = freqNorm * (inStream.rate/2); lowpass = new float[ dwFFTLength + 2 ]; Util.clear( lowpass ); lowpass[ dwHalfWinSize ] = (float) (Math.PI - freqBase); freqBase = freqNorm * loFreq * 1.2; cosineBase = freqNorm * loFreq * 0.04; cosineBaseSqr = cosineBase * cosineBase; for( j = 1; j < dwHalfWinSize; j++ ) { d1 = (Math.sin( freqBase * j ) / (double) j); // raised cosine modulation d2 = cosineNorm * cosineBaseSqr * j * j; d1 *= (Math.cos( cosineBase * j ) / (1.0 - d2)); lowpass[ dwHalfWinSize+j ] = (float) d1; lowpass[ dwHalfWinSize-j ] = (float) d1; } lowpass[ dwHalfWinSize ] += (float) freqBase; // windowing Util.mult( dwWin, 0, lowpass, 0, dwFltLength ); Fourier.realTransform( lowpass, dwFFTLength, Fourier.FORWARD ); Util.mult( lowpass, 0, lowpass.length, (float) (1.0 / Math.PI) ); // for( i = 0; i < lowpass.length; i += 2 ) { // lowpass[ i ] = (float) Math.sqrt( lowpass[ i ]*lowpass[ i ]+lowpass[ i+1 ]*lowpass[ i+1 ] ); // lowpass[ i+1 ] = lowpass[ i ]; // } // lowpass[ (int) (loFreq / inStream.smpRate * 2 * lowpass.length) ] = -1.0f; // Debug.view( lowpass, "lowpass" ); // LP = +1.0 fc -1.0 Zero // HP = +1.0 pi/2 -1.0 fc // BP = +1.0 fc2 -1.0 fc1 // ---- calculate impulse response of the bandpasses + init tracker ---- shpNumPeriods = 3 << pr.intg[ PR_FILTERLEN ]; shpMaxHalfWin = Math.max( 1, (int) ((double) shpNumPeriods * inStream.rate / crossFreqs[0] + 0.5) ); shpMinHalfWin = Math.max( 1, (int) ((double) shpNumPeriods * inStream.rate / crossFreqs[numBands-1] + 0.5) ); i = shpMinHalfWin + shpMinHalfWin; j = i + i - 1; for( shpMinFFTLen = 2; shpMinFFTLen < j; shpMinFFTLen <<= 1 ) ; // FFTlength for shortest BP i = shpMaxHalfWin + shpMaxHalfWin; j = i + i - 1; for( shpMaxFFTLen = 2; shpMaxFFTLen < j; shpMaxFFTLen <<= 1 ) ; // FFTlength for longest BP for( numShpLen = 1, i = shpMinFFTLen; i < shpMaxFFTLen; i <<= 1, numShpLen ++ ) ; // how many different FFT lengths? shpFFTLength = new int[ numShpLen ]; // each of the different lengths shpFFTCount = new int[ numShpLen ]; // how many successive FFT of each FFT length shpConvNum = new int[ numShpLen ]; shpFFTBuf = new float[ numBands ][]; convFFTBuf = new float[ shpMaxFFTLen + 2 ]; convOverBuf = new float[ inChanNum ][ numBands ][]; shpFFTLen = -1; // shpMaxFFTLen; shpFFTLenIdx = -1; shpFFTLength[0] = shpFFTLen; shpFFTCount[0] = 0; i = shpMaxFFTLen - (shpMaxHalfWin << 1) + 1; shpConvNum[0] = (inputLen + i - 1) / i; shpHighHalfWin = shpMaxHalfWin; shpHighWin = Filter.createFullWindow( shpHighHalfWin << 1, Filter.WIN_BLACKMAN ); chebys = new ChebyTracker[ inChanNum ][ numBands ]; chbHarmonWeight1 = SpectSlider.valueOf( pr.text[ PR_SPECTSLIDER1 ]); chbHarmonWeight2 = SpectSlider.valueOf( pr.text[ PR_SPECTSLIDER2 ]); chbHarmonWeight3 = SpectSlider.valueOf( pr.text[ PR_SPECTSLIDER3 ]); chbHarmonWeight4 = SpectSlider.valueOf( pr.text[ PR_SPECTSLIDER4 ]); chbHarmonWeight5 = SpectSlider.valueOf( pr.text[ PR_SPECTSLIDER5 ]); chbHarmonWeight6 = SpectSlider.valueOf( pr.text[ PR_SPECTSLIDER6 ]); chbHarmonWeight7 = SpectSlider.valueOf( pr.text[ PR_SPECTSLIDER7 ]); chbHarmonWeight8 = SpectSlider.valueOf( pr.text[ PR_SPECTSLIDER8 ]); chbHarmonWeight9 = SpectSlider.valueOf( pr.text[ PR_SPECTSLIDER9 ]); for( band = 0; band < numBands; band++ ) { shpLowHalfWin = shpHighHalfWin; shpLowWin = shpHighWin; shpHalfWinSize[band]=shpLowHalfWin; shpHighHalfWin = Math.max( 1, (int) ((double) shpNumPeriods * inStream.rate / crossFreqs[band+1] + 0.5) ); shpHighWin = Filter.createFullWindow( shpHighHalfWin << 1, Filter.WIN_BLACKMAN ); j = ((shpLowHalfWin + shpLowHalfWin) << 1) - 1; for( k = 2; k < j; k <<= 1 ) ; // current FFTlength convBuf1 = new float[ k + 2 ]; shpFFTBuf[band] = convBuf1; // System.out.println( "Band "+band+" (freq "+crossFreqs[band]+" ... "+crossFreqs[band+1]+") => halfwin "+shpLowHalfWin+"/"+shpHighHalfWin ); if( k != shpFFTLen ) { // System.out.println( "new FFT size "+k ); shpFFTLen = k; shpFFTLenIdx++; shpFFTLength[ shpFFTLenIdx ] = shpFFTLen; j = shpFFTLen - (shpLowHalfWin << 1) + 1; shpConvNum[ shpFFTLenIdx ] = (inputLen + j - 1) / j; } shpFFTCount[ shpFFTLenIdx ]++; // overlap buffer inputConv = shpFFTLen - (shpLowHalfWin << 1) + 1; convOverLen = shpFFTLen - inputConv; for( ch = 0; ch < inChanNum; ch++ ) { convOverBuf[ ch ][ band ] = new float[ convOverLen ]; } // first part of filter if( band < numBands-1 ) { // damn overhead for highest band (we don't want an extra slope, we have the hp-slope already) freqBase = freqNorm * crossFreqs[ band+1 ]; cosineBase = freqNorm * cosineFreqs[ band+1 ]; cosineBaseSqr = cosineBase * cosineBase; for( j = 1; j < shpHighHalfWin; j++ ) { // sinc-filter d1 = (Math.sin( freqBase * j ) / (double) j); // raised cosine modulation d2 = cosineNorm * cosineBaseSqr * j * j; d1 *= (Math.cos( cosineBase * j ) / (1.0 - d2)); convBuf1[ shpLowHalfWin+j ] = (float) d1 * shpHighWin[ shpHighHalfWin+j ]; convBuf1[ shpLowHalfWin-j ] = (float) d1 * shpHighWin[ shpHighHalfWin-j ]; } convBuf1[ shpLowHalfWin ] = (float) freqBase * shpHighWin[ shpHighHalfWin ]; } else { Util.clear( convBuf1 ); convBuf1[ shpLowHalfWin ] = (float) Math.PI * shpHighWin[ shpHighHalfWin ]; } // second part of filter if( band > 0 ) { // damn overhead for lowest band (we don't want an extra slope, we have the lp-slope already) freqBase = freqNorm * crossFreqs[ band ]; cosineBase = freqNorm * cosineFreqs[ band ]; cosineBaseSqr = cosineBase * cosineBase; for( j = 1; j < shpLowHalfWin; j++ ) { d1 = (Math.sin( freqBase * j ) / (double) j); // raised cosine modulation d2 = cosineNorm * cosineBaseSqr * j * j; d1 *= (Math.cos( cosineBase * j ) / (1.0 - d2)); convBuf1[ shpLowHalfWin+j ] -= (float) d1 * shpLowWin[ shpLowHalfWin+j ]; convBuf1[ shpLowHalfWin-j ] -= (float) d1 * shpLowWin[ shpLowHalfWin-j ]; } convBuf1[ shpLowHalfWin ] -= (float) freqBase * shpLowWin[ shpLowHalfWin ]; // zero padding for( j = (shpLowHalfWin << 1); j < shpFFTLen; j++ ) { convBuf1[ j ] = 0.0f; } } // no else ;) // all filters are pre-transformed to fourier domain and normalized Fourier.realTransform( convBuf1, shpFFTLen, Fourier.FORWARD ); Util.mult( convBuf1, 0, convBuf1.length, (float) (1.0 / Math.PI) ); // ---- tracker init ---- chebyTrk = new ChebyTracker(); f1 = (float) inStream.rate / crossFreqs[ band ]; // period [smp] chbPeakScope = Math.max( 64, (int) (f1 * 1.5f + 0.5f) ); // scope 1.5 periods chbPeakBufLen = /* dropEnv ? 1 :*/ (chbPeakScope << 1); chebyTrk.peakBufLen = chbPeakBufLen; chebyTrk.peakScope = chbPeakScope; chebyTrk.peakBuf = new float[ chbPeakBufLen ]; Util.clear( chebyTrk.peakBuf ); chebyTrk.peakBufStart= 0; chebyTrk.peakBufOff = chbPeakScope - 1; chebyTrk.peakValid = 0; // no valid peak chebyTrk.harmonWeight1 = new float[ MAX_HARMON ]; chebyTrk.harmonWeight2 = new float[ MAX_HARMON ]; chebyTrk.harmonWeight3 = new float[ MAX_HARMON ]; if( dropEnv ) { f1 = (float) band / (numBands - 1); chebyTrk.peakBuf[ 0 ] = 1.0e-4f; // 1.0f; // / (((1.0f - f1) * lowBoost) + f1 * highBoost); } for( i = 0; i < MAX_HARMON; i++ ) { if( crossFreqs[ band+1 ] < nyquist ) { if( crossFreqs[ band+1 ] < midFreq ) { // -------- below mid freq -------- weight = (float) ((crossFreqs[ band+1 ] - loFreq) / (midFreq - loFreq)); f1 = chbHarmonWeight1[ i ]; f2 = chbHarmonWeight2[ i ]; f1 = f1 * (1.0f - weight) + f2 * weight; f3 = f1; chebyTrk.harmonWeight1[ i ] = f1*f1*f1; f1 = chbHarmonWeight4[ i ]; f2 = chbHarmonWeight5[ i ]; f1 = f1 * (1.0f - weight) + f2 * weight; f3 = Math.max( f3, f1 ); chebyTrk.harmonWeight2[ i ] = f1*f1*f1; f1 = chbHarmonWeight7[ i ]; f2 = chbHarmonWeight8[ i ]; f1 = f1 * (1.0f - weight) + f2 * weight; f3 = Math.max( f3, f1 ); chebyTrk.harmonWeight3[ i ] = f1*f1*f1; } else { // -------- above mid freq -------- weight = (float) ((hiFreq - crossFreqs[ band+1 ]) / (hiFreq - midFreq)); f1 = chbHarmonWeight2[ i ]; f2 = chbHarmonWeight3[ i ]; f1 = f1 * weight + f2 * (1.0f - weight); f3 = f1; chebyTrk.harmonWeight1[ i ] = f1*f1*f1; f1 = chbHarmonWeight5[ i ]; f2 = chbHarmonWeight6[ i ]; f1 = f1 * weight + f2 * (1.0f - weight); f3 = Math.max( f3, f1 ); chebyTrk.harmonWeight2[ i ] = f1*f1*f1; f1 = chbHarmonWeight8[ i ]; f2 = chbHarmonWeight9[ i ]; f1 = f1 * weight + f2 * (1.0f - weight); f3 = Math.max( f3, f1 ); chebyTrk.harmonWeight3[ i ] = f1*f1*f1; } if( f3 > 0f ) chebyTrk.harmonNum = i + 1; } else { chebyTrk.harmonWeight1[ i ] = 0f; chebyTrk.harmonWeight2[ i ] = 0f; chebyTrk.harmonWeight3[ i ] = 0f; } } chebys[0][band] = chebyTrk; for( ch = 1; ch < inChanNum; ch++ ) { chebys[ch][band]= (ChebyTracker) chebyTrk.clone(); } } // for bands numShpLen = shpFFTLenIdx + 1; // muss korrigiert werden, weil u.U. eine FFT-Laenge "uebersprungen" wurde!! /* float[] tmp = new float[ shpMaxFFTLen + 2]; float[] tmp2 = new float[ shpMaxFFTLen + 2]; for( band = 0; band < numBands; band++ ) { System.arraycopy( shpFFTBuf[band], 0, tmp2, 0, shpFFTBuf[band].length ); Fourier.realTransform( tmp2, shpFFTBuf[band].length - 2, Fourier.INVERSE ); Util.add( tmp2, 0, tmp, shpMaxFFTLen/2 - shpHalfWinSize[band], shpHalfWinSize[band]*2); } Debug.view( tmp, "Summed BPs" ); Fourier.realTransform( tmp, shpMaxFFTLen, Fourier.FORWARD ); for( i = 0; i < tmp.length; i+= 2 ) { tmp[ i ] = (float) Math.sqrt( tmp[ i ]*tmp[ i ]+tmp[ i+1 ]*tmp[ i+1 ] ); tmp[ i+1 ] = tmp[ i ]; } Debug.view( tmp, "Summed BPs FFT" ); */ // ---- further inits ---- outBuf = new float[ inChanNum ][ shpMaxHalfWin + inputLen ]; dwFFTBuf = new float[ dwFFTLength + 2 ]; lpFFTBuf = new float[ dwFFTLength + 2 ]; hpFFTBuf = new float[ dwFFTLength + 2 ]; lpOverBuf = new float[ inChanNum ][ dwOverLen ]; hpOverBuf = new float[ inChanNum ][ dwOverLen ]; inBuf = new float[ inChanNum ][ inputLen + dwHalfWinSize ]; sliceFFTBuf = new float[ shpConvNum[ numShpLen - 1 ]][ shpMaxFFTLen + 2 ]; // largets convNum x largest FFT size Util.clear( inBuf ); // System.out.println( "inputLen "+inputLen+"; dwFltLength "+dwFltLength+"; shpMaxFFTLen "+shpMaxFFTLen+"; shpMinFFTLen "+shpMinFFTLen+"; sliceFFTBuf dim "+(shpConvNum[ numShpLen - 1 ])+" x "+shpMaxFFTLen ); progOff = 0; progLen = (long) inLength * (2 + inChanNum + inChanNum); m = 0; // java compiler complains if not initialized dryGain = chebys[ 0 ][ 0 ].harmonWeight1[ 0 ]; // XXX // normalization requires temp files if( pr.intg[ PR_GAINTYPE ] == GAIN_UNITY ) { tempF = createTempFile( inStream ); progLen += (long) inLength; } else { // account for gain loss RealFFT => CmplxIFFT gain = (float) ((Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ampRef, null )).value); } // .... check running .... if( !threadRunning ) break topLevel; // ----==================== the real stuff ====================---- framesRead = 0; framesWritten = 0; outSkip = dwHalfWinSize + shpMaxHalfWin; chbHarmonWeight3 = new float[ MAX_HARMON ]; // time interpolated while( threadRunning && (framesWritten < inLength) ) { chunkLength = Math.min( inputLen, inLength - framesRead ); chunkLength2 = Math.min( inputLen, inLength - framesWritten ); // ---- read input chunk ---- for( off = 0; threadRunning && (off < chunkLength); ) { len = Math.min( 8192, chunkLength - off ); inF.readFrames( inBuf, off + dwHalfWinSize, len ); // inBuf delayed by dwHalfWinSize framesRead += len; progOff += len; off += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; beforeMidTime = framesRead <= midTime; if( beforeMidTime ) { weight = (float) framesRead / (float) midTime; } else { weight = (float) (framesRead - midTime) / (float) (inLength - midTime); } // ---- channels loop ----------------------------------------------------------------------- for( ch = 0; threadRunning && (ch < inChanNum); ch++ ) { // ---- dry/wet pre-filtering ---- convBuf1 = outBuf[ ch ]; convBuf2 = inBuf[ ch ]; System.arraycopy( convBuf2, dwHalfWinSize, dwFFTBuf, 0, chunkLength ); // inBuf copy without delay for( i = chunkLength; i < dwFFTLength; i++ ) { dwFFTBuf[ i ] = 0.0f; } Fourier.realTransform( dwFFTBuf, dwFFTLength, Fourier.FORWARD ); // input chunk -> FFT Fourier.complexMult( lowpass, 0, dwFFTBuf, 0, lpFFTBuf, 0, lowpass.length ); // convolve with LP Fourier.realTransform( lpFFTBuf, dwFFTLength, Fourier.INVERSE ); // back to time domain (delayed by dwHalfWinSize) Util.add( lpOverBuf[ ch ], 0, lpFFTBuf, 0, dwOverLen ); // add old overlap System.arraycopy( lpFFTBuf, inputLen, lpOverBuf[ ch ], 0, dwOverLen ); // save new overlap Fourier.complexMult( highpass, 0, dwFFTBuf, 0, hpFFTBuf, 0, highpass.length ); // convolve with HP Fourier.realTransform( hpFFTBuf, dwFFTLength, Fourier.INVERSE ); // back to time domain (delayed by dwHalfWinSize) Util.add( hpOverBuf[ ch ], 0, hpFFTBuf, 0, dwOverLen ); // add old overlap System.arraycopy( hpFFTBuf, inputLen, hpOverBuf[ ch ], 0, dwOverLen ); // save new overlap // now the signal to be fed into the waveshaper is input (delayed by dwHalfWinSize) // - lowpass - highpass Util.sub( lpFFTBuf, 0, convBuf2, 0, inputLen ); Util.sub( hpFFTBuf, 0, convBuf2, 0, inputLen ); System.arraycopy( lpFFTBuf, 0, convBuf1, shpMaxHalfWin, inputLen ); // dry lp/hp to output Util.add( hpFFTBuf, 0, convBuf1, shpMaxHalfWin, inputLen ); Util.mult( convBuf1, shpMaxHalfWin, inputLen, dryGain ); // ---- bands loop ---------------------------------------------------------------------- shpFFTLenIdx = -1; shpFFTCounter = 0; for( band = 0; threadRunning && (band < numBands); band++ ) { shpLowHalfWin = shpHalfWinSize[ band ]; if( shpFFTCounter == 0) { shpFFTLenIdx++; shpFFTCounter = shpFFTCount[ shpFFTLenIdx ]; shpFFTLen = shpFFTLength[ shpFFTLenIdx ]; inputConv = shpFFTLen - (shpLowHalfWin << 1) + 1; // max. input chunk for convolution convNum = shpConvNum[ shpFFTLenIdx ]; // number of conv's per inputLen for( i = 0, off = 0; i < convNum; i++ ) { // FFT all the sliced input chunk pieces len = Math.min( inputConv, inputLen - off ); convBuf3 = sliceFFTBuf[ i ]; System.arraycopy( convBuf2, off, convBuf3, 0, len ); for( j = len; j < shpFFTLen; j++ ) { convBuf3[ j ] = 0.0f; } Fourier.realTransform( convBuf3, shpFFTLen, Fourier.FORWARD ); off += len; } } shpFFTCounter--; convOverLen = (shpLowHalfWin << 1) - 1; // =< inputConv ! convShift = shpMaxHalfWin - shpLowHalfWin; // align all filters with uniform dly of shpMaxHalfWin // cheby init (read) chebyTrk = chebys[ ch ][ band ]; chbPeakBuf = chebyTrk.peakBuf; chbPeakBufLen = chebyTrk.peakBufLen; chbPeakBufStart = chebyTrk.peakBufStart; chbPeakBufOff = chebyTrk.peakBufOff; chbPeakValid = chebyTrk.peakValid; chbPeakValue = chebyTrk.peakValue; chbPeakScope = chebyTrk.peakScope; chbPeakBufCont = chbPeakBufStart < chbPeakBufOff; // true = continuous; false = wrapped search chbHarmonNum = chebyTrk.harmonNum; if( beforeMidTime ) { chbHarmonWeight1 = chebyTrk.harmonWeight1; chbHarmonWeight2 = chebyTrk.harmonWeight2; } else { chbHarmonWeight1 = chebyTrk.harmonWeight2; chbHarmonWeight2 = chebyTrk.harmonWeight3; } for( i = 0; i < MAX_HARMON; i++ ) { chbHarmonWeight3[ i ] = chbHarmonWeight1[ i ] * (1.0f - weight) + chbHarmonWeight2[ i ] * weight; } // convolve slices, inverse transform, overlap-add for( i = 0, off = 0; i < convNum; i++ ) { // System.out.println( "conv "+i+"; off "+off+"; shpFFTBuf[ band ].length "+shpFFTBuf[ band ].length ); Fourier.complexMult( shpFFTBuf[ band ], 0, sliceFFTBuf[ i ], 0, convFFTBuf, 0, shpFFTBuf[ band ].length ); Fourier.realTransform( convFFTBuf, shpFFTLen, Fourier.INVERSE ); // back to time domain (delayed by shpLowHalfWin) len = Math.min( inputConv, inputLen - off ); Util.add( convOverBuf[ ch ][ band ], 0, convFFTBuf, 0, convOverLen ); // add old overlap System.arraycopy( convFFTBuf, len, convOverBuf[ ch ][ band ], 0, convOverLen ); // save new overlap // ======== Cheby Core ================================================= // peak tracking + cheby shaping if( dropEnv ) { chbPeakValue = chbPeakBuf[ 0 ]; } for( j = 0; j < len; j++ ) { // all samples in a slice f1 = convFFTBuf[ j ]; if( !dropEnv ) { f2 = Math.abs( f1 ); chbPeakBuf[ chbPeakBufOff ] = f2; if( chbPeakValid > 0 ) { if( f2 >= chbPeakValue ) { chbPeakValue = f2; chbPeakValid = chbPeakScope; } chbPeakValid--; } else { chbPeakValue = -1f; // any next value will be a first peak guess if( chbPeakBufCont ) { for( k = chbPeakBufStart; k <= chbPeakBufOff; k++ ) { if( chbPeakBuf[ k ] >= chbPeakValue ) { chbPeakValue = chbPeakBuf[ k ]; m = k; } } chbPeakValid = m - chbPeakBufStart; } else { for( k = chbPeakBufStart; k < chbPeakBufLen; k++ ) { if( chbPeakBuf[ k ] >= chbPeakValue ) { chbPeakValue = chbPeakBuf[ k ]; m = k; } } for( k = 0; k <= chbPeakBufOff; k++ ) { if( chbPeakBuf[ k ] >= chbPeakValue ) { chbPeakValue = chbPeakBuf[ k ]; m = k; } } chbPeakValid = m - chbPeakBufStart; if( chbPeakValid < 0 ) chbPeakValid += chbPeakBufLen; } } // circular buffer track keeping chbPeakBufOff++; chbPeakBufStart++; if( chbPeakBufOff == chbPeakBufLen ) { chbPeakBufOff = 0; chbPeakBufCont = false; } else if( chbPeakBufStart == chbPeakBufLen ) { chbPeakBufStart = 0; chbPeakBufCont = true; } } // if( !dropEnv ) // ---- at this point 'chbPeakValue' is a good guess for the amplitude // calc weighted cheby polynoms (T0 = 1, T1 = x, Tn= 2xTn-1 - Tn-2) if( chbPeakValue > 0.00001f ) { // -100 dB thresh XXX chbT1 = 1.0f; // first polynom chbT0 = f1 / chbPeakValue; // second polynom (x normalized) f2 = 2 * chbT0; // (2x term in recursion) chbTn = chbT0 * chbHarmonWeight3[0]; // f3 will be summed harmonics for( k = 1; k < chbHarmonNum; k++ ) { chbT2 = chbT1; chbT1 = chbT0; chbT0 = f2 * chbT1 - chbT2; chbTn += chbT0 * chbHarmonWeight3[k]; } if( chbPeakValue > 0.0001f ) { // -80 dB thresh XXX convFFTBuf[j] = chbTn * chbPeakValue; } else { // f2 = (chbPeakValue - 0.0001f) / 0.00009f; // map to 0...1 f2 = chbPeakValue / 0.0001f; // map to 0...1 convFFTBuf[j] = chbTn * f2 * chbPeakValue + // weighted sum cheby/harmon1 f1 * chbHarmonWeight3[0] * (1.0f - f2); } } else { convFFTBuf[j] *= chbHarmonWeight3[0]; } } // for slice samples // ===================================================================== Util.add( convFFTBuf, 0, convBuf1, off + convShift, len ); off += len; } // for slices // cheby finish (write) chebyTrk.peakBufStart = chbPeakBufStart; chebyTrk.peakBufOff = chbPeakBufOff; chebyTrk.peakValid = chbPeakValid; chebyTrk.peakValue = chbPeakValue; } // for bands // .... check running .... if( !threadRunning ) break topLevel; // overlap handling input System.arraycopy( convBuf2, inputLen, convBuf2, 0, dwHalfWinSize ); progOff += chunkLength2 + chunkLength2; // .... progress .... setProgression( (float) progOff / (float) progLen ); } // for chanNum // .... check running .... if( !threadRunning ) break topLevel; for( off = outSkip; threadRunning && (off < chunkLength2); ) { len = Math.min( 8192, chunkLength2 - off ); if( tempF != null ) { tempF.writeFrames( outBuf, off, len ); } else { for( ch = 0; ch < inChanNum; ch++ ) { Util.mult( outBuf[ ch ], off, len, gain ); } outF.writeFrames( outBuf, off, len ); } progOff += len; off += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } for( ch = 0; ch < inChanNum; ch++ ) { convBuf1 = outBuf[ ch ]; // max measurement for( off = outSkip; off < chunkLength2; off++ ) { f1 = Math.abs( convBuf1[ off ]); if( f1 > maxAmp ) { maxAmp = f1; } } // overlap handling output System.arraycopy( convBuf1, inputLen, convBuf1, 0, shpMaxHalfWin ); // for( i = shpMaxHalfWin; i < convBuf1.length; i++ ) convBuf1[ i ] = 0.0f; } if( outSkip > 0 ) { framesWritten += Math.max( 0, chunkLength2 - outSkip ); outSkip = Math.max( 0, outSkip - chunkLength2 ); } else { framesWritten += chunkLength2; } } // until framesWritten == outLength // .... check running .... if( !threadRunning ) break topLevel; // ----==================== normalize output ====================---- 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; f1 = 1.0f; normalizeAudioFile( tempF, outF, outBuf, gain, f1 ); maxAmp *= gain; deleteTempFile( tempF ); } // .... check running .... if( !threadRunning ) break topLevel; // ---- Finish ---- outF.close(); outF = null; outStream = null; inF.close(); inF = null; inStream = null; shpFFTBuf = null; lpOverBuf = null; hpOverBuf = null; sliceFFTBuf = null; convOverBuf = null; convBuf1 = null; convBuf2 = null; convBuf3 = null; inBuf = null; outBuf = null; chebys = null; // inform about clipping/ low level handleClipping( maxAmp ); } catch( IOException e1 ) { setError( e1 ); } catch( OutOfMemoryError e2 ) { inStream = null; outStream = null; lowpass = null; highpass = null; hpFFTBuf = null; lpFFTBuf = null; dwFFTBuf = null; convFFTBuf = null; shpFFTBuf = null; lpOverBuf = null; hpOverBuf = null; sliceFFTBuf = null; convOverBuf = null; convBuf1 = null; convBuf2 = null; convBuf3 = null; inBuf = null; outBuf = null; chebys = null; System.gc(); setError( new Exception( ERR_MEMORY )); } // ---- cleanup (topLevel) ---- if( inF != null ) { inF.cleanUp(); inF = null; } if( outF != null ) { outF.cleanUp(); outF = null; } } // process() // -------- internal ChebyTracker class -------- private static class ChebyTracker { float[] peakBuf; // buffer holding the 3 recent periods of the band's (abs!) samples (circular updated!) int peakBufLen, peakBufStart, peakBufOff; // len = peakBuf.length; start...off = peak search area (increased per sample) int peakValid; // how often is the value still valid (until peak position leaves scope of 1.5 periods) float peakValue; // last valid peak value int peakScope; // how many samples are inspected for peak finding int harmonNum; // number of harmonics (including first) float[] harmonWeight1; // 0...1 beginning of performance float[] harmonWeight2; // 0...1 middle time float[] harmonWeight3; // 0...1 end of performance protected ChebyTracker() { /* empty */} protected ChebyTracker( ChebyTracker orig ) { this.peakBuf = (float[]) orig.peakBuf.clone(); this.peakBufLen = orig.peakBufLen; this.peakBufStart = orig.peakBufStart; this.peakBufOff = orig.peakBufOff; this.peakValid = orig.peakValid; this.peakValue = orig.peakValue; this.peakScope = orig.peakScope; this.harmonNum = orig.harmonNum; this.harmonWeight1 = (float[]) orig.harmonWeight1.clone(); this.harmonWeight2 = (float[]) orig.harmonWeight2.clone(); this.harmonWeight3 = (float[]) orig.harmonWeight3.clone(); } public Object clone() { return new ChebyTracker( this ); } } // class ChebyTracker }