/*
* IchneumonDlg.java
* (FScape)
*
* Copyright (c) 2001-2016 Hanns Holger Rutz. All rights reserved.
*
* This software is published under the GNU General Public License v3+
*
*
* For further information, please contact Hanns Holger Rutz at
* contact@sciss.de
*/
package de.sciss.fscape.gui;
import de.sciss.fscape.io.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.Filter;
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 de.sciss.io.IOUtil;
import javax.swing.*;
import java.awt.*;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
/**
* Processing module that tracks zero
* crossings and applies variable resampling
* to wave sections around these crossings.
*/
public class IchneumonDlg
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_QUALITY = 3;
private static final int PR_GAIN = 0; // pr.para
private static final int PR_MINRSMP = 1;
private static final int PR_MAXRSMP = 2;
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_QUALITY = "Quality";
private static final String PRN_MINRSMP = "MinRsmp";
private static final String PRN_MAXRSMP = "MaxRsmp";
private static final int QUAL_MEDIUM = 0; // PR_QUALITY
private static final int QUAL_GOOD = 1;
private static final int QUAL_VERYGOOD = 2;
private static final String[] QUAL_NAMES = { "Medium", "Good", "Very good" };
private static final String prText[] = { "", "" };
private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE };
private static final int prIntg[] = { 0, 0, GAIN_UNITY, QUAL_GOOD };
private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_GAINTYPE, PRN_QUALITY };
private static final Param prPara[] = { null, null, null };
private static final String prParaName[] = { PRN_GAIN, PRN_MINRSMP, PRN_MAXRSMP };
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_QUALITY = GG_OFF_CHOICE + PR_QUALITY;
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_MINRSMP = GG_OFF_PARAMFIELD + PR_MINRSMP;
private static final int GG_MAXRSMP = GG_OFF_PARAMFIELD + PR_MAXRSMP;
private static PropertyArray static_pr = null;
private static Presets static_presets = null;
// -------- public methods --------
/**
* !! setVisible() bleibt dem Aufrufer ueberlassen
*/
public IchneumonDlg()
{
super( "Ichneumon" );
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_MINRSMP ] = new Param( 1.0, Param.OFFSET_SEMITONES );
static_pr.para[ PR_MAXRSMP ] = new Param( 24.0, Param.OFFSET_SEMITONES );
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;
ParamField ggMinRsmp, ggMaxRsmp;
JComboBox ggQuality;
PathField[] ggInputs;
ParamSpace[] spcRateModDepth;
Component[] ggGain;
int i;
gui = new GUISupport();
con = gui.getGridBagConstraints();
con.insets = new Insets( 1, 2, 1, 2 );
PathListener pathL = new PathListener() {
public void pathChanged( PathEvent e )
{
int ID = gui.getItemID( e );
switch( ID ) {
case GG_INPUTFILE:
setInput( ((PathField) e.getSource()).getPath().getPath() );
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, pathL );
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;
// ggInputs[ 1 ] = ggModFile;
ggOutputFile.deriveFrom( ggInputs, "$D0$F0Ich$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, pathL );
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, null );
// -------- Settings --------
gui.addLabel( new GroupLabel( "Radio Massacre", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE ));
spcRateModDepth = new ParamSpace[ 2 ];
spcRateModDepth[0] = Constants.spaces[ Constants.offsetSemitonesSpace ];
spcRateModDepth[1] = Constants.spaces[ Constants.offsetFreqSpace ];
// spcRateModDepth[2] = Constants.spaces[ Constants.offsetHzSpace ];
ggMinRsmp = new ParamField( spcRateModDepth );
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Min.rsmp", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggMinRsmp, GG_MINRSMP, null );
ggQuality = new JComboBox();
for( i = 0; i < QUAL_NAMES.length; i++ ) {
ggQuality.addItem( QUAL_NAMES[ i ]);
}
con.weightx = 0.1;
gui.addLabel( new JLabel( "Quality", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addChoice( ggQuality, GG_QUALITY, null );
ggMaxRsmp = new ParamField( spcRateModDepth );
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Max.rsmp", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggMaxRsmp, GG_MAXRSMP, null );
initGUI( this, FLAGS_PRESETS | FLAGS_PROGBAR, gui );
}
/**
* Transfer values from prop-array to GUI
*/
public void fillGUI()
{
super.fillGUI();
super.fillGUI( gui );
}
/**
* Transfer values from GUI to prop-array
*/
public void fillPropertyArray()
{
super.fillPropertyArray();
super.fillPropertyArray( gui );
}
// -------- Processor Interface --------
protected void process()
{
int ch, i, j, k;
int len, chunkLength;
long progOff, progLen;
double d1, d2, d3;
float f1, f2;
// io
AudioFile inF = null;
AudioFile outF = null;
AudioFileDescr inStream = null;
AudioFileDescr outStream = null;
FloatFile zcFloatFile = null;
File zcTempFile = null;
FloatFile[] floatF = null;
File[] tempFile = null;
float[][] inBuf = null;
float[][] outBuf = null;
float[] convBuf1;
// Smp Init
int inLength, inChanNum, outChanNum;
int framesRead, framesWritten, timeIndex;
float gain = 1.0f; // gain abs amp
Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz
float maxAmp = 0.0f;
double inRate, inPhase;
double minRsmpFactor;
double dOff, inputIncr;
int outTransLen;
int overlapSize, inputStep, inBufSize, outBufSize;
int offStart;
int fltSmpPerCrossing, fltLen, fltCrossings;
float[] flt, fltD;
double[] factor;
float[][] filter;
float fltGain, fltRolloff, fltWin;
double fltIncr;
float oldGrad, newGrad;
int[] crossHist;
int crossCount, maxGrad;
int[] zcBuf;
int maxLoc, minLoc, zeroLoc;
float maxVal, minVal;
float minFactor, maxFactor;
float[] factorLookup;
Param rateBase;
boolean finished;
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;
inRate = inStream.rate;
// this helps to prevent errors from empty files!
if( inLength * inChanNum < 1 ) throw new EOFException( ERR_EMPTY );
// .... check running ....
if( !threadRunning ) break topLevel;
outChanNum = inChanNum;
rateBase = new Param( inRate, Param.ABS_HZ );
fltSmpPerCrossing = 4096; // 2048 << quality;
switch( pr.intg[ PR_QUALITY ]) {
case QUAL_MEDIUM:
fltRolloff = 0.70f;
fltWin = 6.5f;
fltCrossings = 5;
break;
case QUAL_GOOD:
fltRolloff = 0.80f;
fltWin = 7.0f;
fltCrossings = 9;
break;
case QUAL_VERYGOOD:
fltRolloff = 0.86f;
fltWin = 7.5f;
fltCrossings = 15;
break;
default:
throw new IllegalArgumentException( String.valueOf( pr.intg[ PR_QUALITY ]));
}
fltLen = (int) ((float) (fltSmpPerCrossing * fltCrossings) / fltRolloff + 0.5f);
flt = new float[ fltLen ];
fltD = null;
fltGain = Filter.createAntiAliasFilter( flt, fltD, fltLen, fltSmpPerCrossing, fltRolloff, fltWin );
filter = new float[ 3 ][];
filter[ 0 ] = flt;
filter[ 1 ] = fltD;
filter[ 2 ] = new float[ 2 ];
filter[ 2 ][ 0 ] = fltSmpPerCrossing;
filter[ 2 ][ 1 ] = fltGain;
inputStep = 32768 / inChanNum;
inPhase = 0.0;
progOff = 0;
progLen = (long) inLength*4;
// 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;
// calc minimum rsmp.factor; used for overlap-size
minRsmpFactor = rateBase.value;
minFactor = (float) ((Param.transform( pr.para[ PR_MINRSMP ], Param.ABS_HZ, rateBase, null )).value / inRate);
maxFactor = (float) ((Param.transform( pr.para[ PR_MAXRSMP ], Param.ABS_HZ, rateBase, null )).value / inRate);
minRsmpFactor = Math.min( 1.0, Math.min( minFactor, maxFactor ));
fltIncr = fltSmpPerCrossing * minRsmpFactor;
overlapSize = (int) ((double) fltLen / fltIncr) + 1;
inBufSize = (overlapSize << 1) + inputStep;
outBufSize = (int) ((double) inputStep / minRsmpFactor) + 1; // +1 als ArrayBounds security fuer FreqMod
inBuf = new float[ inChanNum ][ inBufSize ];
outBuf = new float[ outChanNum ][ outBufSize ];
factor = new double[ outBufSize ];
// System.out.println( "minRsmpFactor "+minRsmpFactor+"; overlapSize "+overlapSize+"; minFactor "+minFactor+"; maxFactor "+maxFactor );
zcBuf = new int[ 4 ]; // pre-zc-loc / zc-loc / post-zc-loc / factor-index
zcTempFile = IOUtil.createTempFile();
zcFloatFile = new FloatFile( zcTempFile, GenericFile.MODE_OUTPUT );
// System.out.println( "fltIncr "+fltIncr+"; overlapSize "+overlapSize+"; inBufSize "+inBufSize+"; outBufSize "+outBufSize );
// normalization requires temp files
if( pr.intg[ PR_GAINTYPE ] == GAIN_UNITY ) {
tempFile = new File[ outChanNum ];
floatF = new FloatFile[ outChanNum ];
for( ch = 0; ch < outChanNum; ch++ ) { // first zero them because an exception might be thrown
tempFile[ ch ] = null;
floatF[ ch ] = null;
}
for( ch = 0; ch < outChanNum; ch++ ) {
tempFile[ ch ] = IOUtil.createTempFile();
floatF[ ch ] = new FloatFile( tempFile[ ch ], GenericFile.MODE_OUTPUT );
}
progLen += (long) inLength; // (outLength unknown)
} else {
gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ampRef, null )).value;
}
// .... check running ....
if( !threadRunning ) break topLevel;
// ----==================== da resampling ====================----
framesRead = 0;
maxLoc = 0;
minLoc = 0;
maxVal = 0.0f;
minVal = 0.0f;
zeroLoc = 0;
newGrad = 0.0f;
maxGrad = (int) inStream.rate / 50;
crossHist = new int[ maxGrad + 1 ];
factorLookup = new float[ maxGrad + 1 ];
while( threadRunning && (framesRead < inLength) ) {
for( ch = 0; ch < inChanNum; ch++ ) {
inBuf[ch][0]=inBuf[ch][inBufSize-1];
}
// ==================== read input chunk ====================
len = Math.min( inLength - framesRead, inBufSize-1 );
inF.readFrames( inBuf, 1, len );
progOff += len;
// .... progress ....
setProgression( (float) progOff / (float) progLen );
// .... check running ....
if( !threadRunning ) break topLevel;
// ---- Ichneumon ----------------------------------------------------------------------
convBuf1 = inBuf[0];
f2 = convBuf1[0];
for( i = 1; i <= len; i++ ) { // < inBufSize
f1 = f2;
f2 = convBuf1[i];
oldGrad = newGrad;
newGrad = f2 - f1;
if( (oldGrad <= 0f) && (newGrad > 0f) ) { // local min
minLoc = framesRead + i - 2;
minVal = f1;
if( maxLoc < zeroLoc ) {
k = Math.max( 0, Math.min( maxGrad, (int) ((maxVal - minVal) * 2 * (minLoc - maxLoc)) ));
crossHist[k]++;
zcBuf[0] = maxLoc;
zcBuf[1] = zeroLoc;
zcBuf[2] = minLoc + 1; // ZZZ
zcBuf[3] = k;
zcFloatFile.writeInts( zcBuf );
}
} else if( (oldGrad > 0f) && (newGrad <= 0f) ) { // local max
maxLoc = framesRead + i - 2;
maxVal = f1;
if( minLoc < zeroLoc ) {
k = Math.max( 0, Math.min( maxGrad, (int) ((maxVal - minVal) * 2 * (maxLoc - minLoc)) ));
crossHist[k]++;
zcBuf[0] = minLoc;
zcBuf[1] = zeroLoc;
zcBuf[2] = maxLoc + 1; // ZZZ
zcBuf[3] = k;
zcFloatFile.writeInts( zcBuf );
}
}
if( (f1 > 0f && f2 <= 0f) || (f1 <= 0f && f2 > 0f) ) { // zero crossing
zeroLoc = framesRead + i - 1;
// System.out.println( "zc "+zeroLoc );
}
}
framesRead += len;
}
// .... check running ....
if( !threadRunning ) break topLevel;
// factor lookup berechnen
// es wird ein Histogramm der zc-Werte (Produkt aus Distanz lokales Min/Maximum und Amplitude min/max)
// erstellt. entsprechend wird dem zc-Index ein Wert zwischen minFactor und maxFactor zugeordnet
// ; d.h. bei geringem zc-Wert wird staerker resamplet als bei hohem zc-Wert
crossCount = 0;
for( i = 0; i < maxGrad; i++ ) {
crossCount += crossHist[i];
}
for( i = 0, j = 0; i <= maxGrad; i++ ) {
f1 = (float) j / (float) crossCount;
factorLookup[i] = f1 * maxFactor + (1.0f - f1) * minFactor;
j += crossHist[i];
}
// Debug.view( factorLookup, "factorLookUp" );
// zero crossing file erhaelt einen terminierenden eintrag
zcBuf[0] = inLength;
zcBuf[1] = inLength+1;
zcBuf[2] = inLength+2;
zcBuf[3] = 0;
zcFloatFile.writeInts( zcBuf );
// ============= PASS TWO ================
// factorOff = 0;
offStart = overlapSize;
finished = false;
framesRead = 0;
framesWritten = 0;
timeIndex = -overlapSize;
inF.seekFrame( 0 );
zcFloatFile.seekFloat( 0 );
zcFloatFile.readInts( zcBuf );
while( threadRunning && !finished ) {
// ==================== read input chunk ====================
len = Math.min( inLength - framesRead, inBufSize - offStart );
chunkLength = offStart + len;
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( chunkLength < inBufSize ) {
for( ch = 0; ch < inChanNum; ch++ ) {
convBuf1 = inBuf[ ch ];
for( i = chunkLength; i < inBufSize; i++ ) {
convBuf1[ i ] = 0.0f;
}
}
chunkLength += overlapSize; // YYY
if( chunkLength <= inBufSize ) {
finished = true;
} else {
chunkLength = inBufSize;
}
}
// ==================== read mod chunk ====================
// i = outBufSize - factorOff;
// for( j = factorOff, k = 0; k < i; j++, k++ ) {
// d1 = 1.5; // XXX
// factor[ j ] = d1;
// }
// ---- FreqMod ----------------------------------------------------------------------
dOff = (double) overlapSize + inPhase;
d1 = (double) chunkLength - overlapSize; // YYY
d2 = dOff;
d3 = d2 + (double) timeIndex;
for( outTransLen = 0; (d2 < d1) && (outTransLen < outBufSize); outTransLen++ ) {
while( zcBuf[2] < d3 ) { // ggf. naechste zc koordinaten lesen
zcFloatFile.readInts( zcBuf );
}
if( (double) zcBuf[0] > d3 ) { // zwischen zwei zc's
factor[outTransLen] = 1.0f;
} else if( (double) zcBuf[1] > d3 ) { // kurz vor zc
f1 = factorLookup[ zcBuf[3] ] - 1.0f;
f2 = ((float) d3 - (float) zcBuf[0]) / (float) (zcBuf[1] - zcBuf[0]);
factor[outTransLen] = f2 * f1 + 1.0f;
} else { // kurz nach zc
f1 = factorLookup[ zcBuf[3] ] - 1.0f;
f2 = ((float) zcBuf[2] - (float) d3) / (float) (zcBuf[2] - zcBuf[1]);
factor[outTransLen] = f2 * f1 + 1.0f;
}
inputIncr = 1.0 / factor[outTransLen];
// System.out.println( inputIncr );
d2 += inputIncr;
d3 += inputIncr;
}
// System.out.println( "dOff "+dOff+"; d1 "+d1+"; d2 "+d2+"; outTransLen "+outTransLen );
for( ch = 0; ch < outChanNum; ch++ ) {
convBuf1 = outBuf[ ch ];
resample( inBuf[ ch % inChanNum ], dOff, convBuf1, 0, outTransLen, factor, filter );
// measure max. gain
for( j = 0; j < outTransLen; j++ ) {
if( Math.abs( convBuf1[ j ]) > maxAmp ) {
maxAmp = Math.abs( convBuf1[ j ]);
}
}
}
dOff = d2;
progOff += len;
// ---- write output or temp ----
if( floatF != null ) {
for( ch = 0; ch < outChanNum; ch++ ) {
floatF[ ch ].writeFloats( outBuf[ ch ], 0, outTransLen );
}
} else {
// adjust gain
for( ch = 0; ch < outChanNum; ch++ ) {
Util.mult( outBuf[ ch ], 0, outTransLen, gain );
}
outF.writeFrames( outBuf, 0, outTransLen );
}
framesWritten += outTransLen;
// ---- FreqMod : dun ----------------------------------------------------------------------
i = (int) (dOff - overlapSize);
j = chunkLength - i;
inPhase = dOff % 1.0;
offStart = j; // YYY
timeIndex += i;
// shift buffers
for( ch = 0; ch < inChanNum; ch++ ) {
System.arraycopy( inBuf[ ch ], i, inBuf[ ch ], 0, j );
}
// factorOff = outBufSize - outTransLen;
// System.arraycopy( factor, outTransLen, factor, 0, factorOff );
// .... progress ....
progOff += len;
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 / (double) maxAmp, Param.ABS_AMP ), null )).value;
normalizeAudioFile( floatF, outF, outBuf, gain, 1.0f );
for( ch = 0; ch < outChanNum; ch++ ) {
floatF[ ch ].cleanUp();
floatF[ ch ] = null;
tempFile[ ch ].delete();
tempFile[ ch ] = null;
}
}
outF.close();
outF = null;
zcFloatFile.cleanUp();
zcFloatFile = null;
zcTempFile.delete();
// ---- Finish ----
// inform about clipping/ low level
maxAmp *= gain;
handleClipping( maxAmp );
}
catch( IOException e1 ) {
setError( e1 );
}
catch( OutOfMemoryError e2 ) {
inStream = null;
outStream = null;
inBuf = null;
outBuf = null;
System.gc();
setError( new Exception( ERR_MEMORY ));
}
// ---- cleanup (topLevel) ----
if( inF != null ) {
inF.cleanUp();
}
if( outF != null ) {
outF.cleanUp();
}
if( zcFloatFile != null ) {
zcFloatFile.cleanUp();
zcTempFile.delete();
}
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 --------
/**
* Daten resamplen; "bandlimited interpolation"
*
* @param srcOff erlaubt verschiebung unterhalb sample-laenge!
* @param length bzgl. dest!! src muss laenge 'length/factor' aufgerundet haben!!
* @param factor dest-smpRate/src-smpRate
* @param filter Dimension 0: flt (erstes createAntiAliasFilter Argument), 1: fltD;
* 2: fltSmpPerCrossing; 3: fltGain (createAAF result)
*/
protected void resample( float[] src, double srcOff, float[] dest, int destOffStart, int length,
double[] factor, float[][] filter )
{
int i, fltOffI, srcOffI;
double q, val, f, fltIncr, fltOff;
double phase = srcOff;
float[] flt = filter[ 0 ];
// float fltD[] = filter[ 1 ];
double fltSmpPerCrossing = (double) filter[ 2 ][ 0 ];
double gain = (double) filter[ 2 ][ 1 ];
double rsmpGain;
int fltLen = flt.length;
int srcLen = src.length;
// System.out.println( "Rsmp "+srcOff+" ["+srcLen+"] => "+destOff+" +"+length+" ["+dest.length+"]" );
int destOff = destOffStart;
for( i = 0; i < length; i++, phase += 1.0 / f ) {
f = factor[ i ];
if( f < 1.0 ) {
fltIncr = fltSmpPerCrossing * f;
rsmpGain= gain;
} else {
fltIncr = fltSmpPerCrossing;
rsmpGain= gain / f;
}
q = phase % 1.0;
val = 0.0;
srcOffI = (int) phase;
fltOff = q * fltIncr + 0.5f; // wenn wir schon keine interpolation mehr benutzen...
fltOffI = (int) fltOff;
while( (fltOffI < fltLen) && (srcOffI >= 0) ) {
val += (double) src[ srcOffI ] * flt[ fltOffI ];
srcOffI--;
fltOff += fltIncr;
fltOffI = (int) fltOff;
}
srcOffI = (int) phase + 1;
fltOff = (1.0 - q) * fltIncr;
fltOffI = (int) fltOff;
while( (fltOffI < fltLen) && (srcOffI < srcLen) ) {
val += (double) src[ srcOffI ] * flt[ fltOffI ];
srcOffI++;
fltOff += fltIncr;
fltOffI = (int) fltOff;
}
dest[ destOff++ ] = (float) (val * rsmpGain);
}
}
/**
* Set new input file
*/
protected void setInput( String fname )
{
AudioFile f = null;
AudioFileDescr stream = null;
ParamField ggSlave;
Param ref;
// ---- Header lesen ----
try {
f = AudioFile.openAsRead( new File( fname ));
stream = f.getDescr();
f.close();
ref = new Param( stream.rate, Param.ABS_HZ );
ggSlave = (ParamField) gui.getItemObj( GG_MINRSMP );
if( ggSlave != null ) {
ggSlave.setReference( ref );
}
ggSlave = (ParamField) gui.getItemObj( GG_MAXRSMP );
if( ggSlave != null ) {
ggSlave.setReference( ref );
}
} catch( IOException ignored) {}
}
}