/*
* FourierDlg.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:
* 07-Jan-05 uses indicateOutputWrite() ; automatic gain compensation
* preserves original gain for 0 dB immediate forward + backward
*/
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.spect.Fourier;
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 de.sciss.io.Marker;
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;
import java.util.ArrayList;
import java.util.List;
/**
* Processing module to transform a
* sound file from time domain to
* frequency domain and vice versa
* using hard-disk temp files.
*/
public class FourierDlg
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_REOUTPUTFILE = 2;
private static final int PR_IMOUTPUTFILE = 3;
private static final int PR_OUTPUTTYPE = 0; // pr.intg
private static final int PR_OUTPUTRES = 1;
private static final int PR_DIRECTION = 2;
private static final int PR_GAINTYPE = 3;
private static final int PR_FORMAT = 4;
private static final int PR_LENGTH = 5;
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_HASIMOUTPUT = 1;
private static final int DIR_FORWARD = 0;
private static final int DIR_BACKWARD = 1;
private static final int FORMAT_NORMAL = 0;
private static final int FORMAT_POLAR = 1;
// private static final int FORMAT_CEPSTRAL = 2;
private static final int LENGTH_EXPAND = 0;
private static final int LENGTH_TRUNC = 1;
private static final String PRN_REINPUTFILE = "ReInFile";
private static final String PRN_IMINPUTFILE = "ImInFile";
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_DIRECTION = "Dir";
private static final String PRN_FORMAT = "Format";
private static final String PRN_LENGTH = "Length";
private static final String PRN_MEMORY = "Memory";
private static final String PRN_HASIMINPUT = "HasImInput";
private static final String PRN_HASIMOUTPUT = "HasImOutput";
private static final String prText[] = { "", "", "", "" };
private static final String prTextName[] = { PRN_REINPUTFILE, PRN_IMINPUTFILE,
PRN_REOUTPUTFILE, PRN_IMOUTPUTFILE };
private static final int prIntg[] = { 0, 0, DIR_FORWARD, GAIN_UNITY, FORMAT_NORMAL, LENGTH_EXPAND };
private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_DIRECTION, PRN_GAINTYPE,
PRN_FORMAT, PRN_LENGTH };
private static final Param prPara[] = { null, null };
private static final String prParaName[] = { PRN_GAIN, PRN_MEMORY };
private static final boolean prBool[] = { false, true };
private static final String prBoolName[] = { PRN_HASIMINPUT, PRN_HASIMOUTPUT };
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_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_FORMAT = GG_OFF_CHOICE + PR_FORMAT;
private static final int GG_LENGTH = GG_OFF_CHOICE + PR_LENGTH;
private static final int GG_DIRECTION = GG_OFF_CHOICE + PR_DIRECTION;
private static final int GG_HASIMINPUT = GG_OFF_CHECKBOX + PR_HASIMINPUT;
private static final int GG_HASIMOUTPUT = GG_OFF_CHECKBOX + PR_HASIMOUTPUT;
private static PropertyArray static_pr = null;
private static Presets static_presets = null;
private static final String MARK_PIHALF = "PiHalf";
// -------- public methods --------
public FourierDlg() {
super("Fourier Translation");
init2();
}
protected void buildGUI() {
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_FREQSHIFT ] = new Param( 0.0, Param.OFFSET_SEMITONES );
static_pr.para[ PR_MEMORY ] = new Param( 16.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;
// GridBagLayout lay;
// Rectangle bounds;
PathField ggImInputFile, ggReInputFile, ggReOutputFile, ggImOutputFile;
JComboBox ggDirection, ggFormat, ggLength;
JCheckBox ggHasImInput, ggHasImOutput;
PathField[] ggInputs, ggOutputs;
Component[] ggGain;
// ParamField ggFreqShift;
ParamField ggMemory;
// ParamSpace[] spcFreqShift;
int i;
gui = new GUISupport();
con = gui.getGridBagConstraints();
// lay = gui.getGridBagLayout();
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_HASIMOUTPUT:
pr.bool[ ID - GG_OFF_CHECKBOX ] = ((JCheckBox) e.getSource()).isSelected();
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 ));
ggReInputFile = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD,
"Select real part of input" );
ggReInputFile.handleTypes( GenericFile.TYPES_SOUND );
con.gridwidth = 1;
con.weightx = 0.1;
gui.addLabel( new JLabel( "Input [Real]", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
con.weightx = 0.9;
gui.addPathField( ggReInputFile, GG_REINPUTFILE, null );
ggImInputFile = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD,
"Select imaginary part of input" );
ggImInputFile.handleTypes( GenericFile.TYPES_SOUND );
ggHasImInput = new JCheckBox( "Input [Imaginary]" );
con.gridwidth = 1;
con.weightx = 0.1;
i = con.anchor;
con.anchor = GridBagConstraints.EAST;
gui.addCheckbox( ggHasImInput, GG_HASIMINPUT, il );
con.anchor = i;
con.gridwidth = GridBagConstraints.REMAINDER;
con.weightx = 0.9;
gui.addPathField( ggImInputFile, GG_IMINPUTFILE, null );
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, null );
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;
con.anchor = GridBagConstraints.EAST;
gui.addCheckbox( ggHasImOutput, GG_HASIMOUTPUT, il );
con.anchor = i;
con.gridwidth = GridBagConstraints.REMAINDER;
con.weightx = 0.9;
gui.addPathField( ggImOutputFile, GG_IMOUTPUTFILE, null );
ggInputs = new PathField[ 1 ];
ggInputs[ 0 ] = ggReInputFile;
ggOutputs = new PathField[ 1 ];
ggOutputs[ 0 ] = ggReOutputFile;
ggImInputFile.deriveFrom( ggInputs, "$D0$F0i$X0" );
ggReOutputFile.deriveFrom( ggInputs, "$D0$F0FT$E" );
ggImOutputFile.deriveFrom( ggOutputs, "$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, null );
con.weightx = 0.5;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addChoice( (JComboBox) ggGain[ 1 ], GG_GAINTYPE, il );
// -------- Settings --------
gui.addLabel( new GroupLabel( "Translation", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE ));
ggDirection = new JComboBox();
ggDirection.addItem( "Forward" );
ggDirection.addItem( "Backward (Inverse)" );
con.gridwidth = 1;
con.weightx = 0.1;
gui.addLabel( new JLabel( "Direction", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addChoice( ggDirection, GG_DIRECTION, il );
ggFormat = new JComboBox();
ggFormat.addItem( "Normal (rect)" );
ggFormat.addItem( "Polar" );
ggFormat.addItem( "Cepstral (log.)" );
con.weightx = 0.1;
gui.addLabel( new JLabel( "Spectral format", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addChoice( ggFormat, GG_FORMAT, il );
ggLength = new JComboBox();
ggLength.addItem( "Expand to 2^n" );
ggLength.addItem( "Truncate to 2^n" );
con.gridwidth = 1;
con.weightx = 0.1;
gui.addLabel( new JLabel( "FFT Length", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addChoice( ggLength, GG_LENGTH, il );
// spcFreqShift = new ParamSpace[ 2 ];
// spcFreqShift[0] = Constants.spaces[ Constants.factorFreqSpace ];
// spcFreqShift[1] = Constants.spaces[ Constants.offsetSemitonesSpace ];
// ggFreqShift = new ParamField( spcFreqShift );
// ggFreqShift.setReference( new Param( 1.0, Param.ABS_HZ ));
ggMemory = new ParamField( new ParamSpace( 1.0, 2047.0, 1.0, Param.NONE ));
con.weightx = 0.1;
con.gridwidth = 1;
// gui.addLabel( new JLabel( "Freq scale", SwingConstants.RIGHT ));
gui.addLabel( new JLabel( "Mem.alloc. [MB]", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
// gui.addParamField( ggFreqShift, GG_FREQSHIFT, this );
gui.addParamField( ggMemory, GG_MEMORY, 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 --------
/**
* Translation durchfuehren
*/
public void process()
{
// int i, j, k;
// int ch;
float f1;
double d1, d2;
boolean b1;
long progOff, progLen;
// io
AudioFile reInF = null;
AudioFile imInF = null;
AudioFile reOutF = null;
AudioFile imOutF = null;
AudioFileDescr reInStream = null;
AudioFileDescr imInStream = null;
AudioFileDescr reOutStream = null;
AudioFileDescr imOutStream = null;
FloatFile floatF[][] = null; // see storageFFT
File tempFile[][] = null;
int inChanNum;
float[][] buf1 = null;
float[][] buf2 = null;
float[] buf3 = null;
float[][] fftBuf = null;
float[] convBuf1, convBuf2;
int memAmount;
long dataLen = 0;
long halfLen;
PathField ggOutput;
// Marker mark = null;
// Synthesize
Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz
float gain; // gain abs amp
long totalInSamples; // = 0; // reichen 32 bit?
long inLength;
long framesRead, framesWritten;
long n1;
// long n2;
int chunkLen, i1;
float maxAmp = 0.0f;
int scaleNum = 0;
float freqShift;
List<Marker> markers;
topLevel:
try {
// ---- first pass: copy input to two float files ----
reInF = AudioFile.openAsRead( new File( pr.text[ PR_REINPUTFILE ]));
reInStream = reInF.getDescr();
inChanNum = reInStream.channels;
inLength = (int) reInStream.length;
if( pr.bool[ PR_HASIMINPUT ]) {
imInF = AudioFile.openAsRead( new File( pr.text[ PR_IMINPUTFILE ]));
imInStream = imInF.getDescr();
if( imInStream.channels != inChanNum ) throw new IOException( ERR_COMPLEX );
inLength = (int) Math.min( inLength, imInStream.length );
}
totalInSamples= inLength * inChanNum;
// this helps to prevent errors from empty files!
if( totalInSamples < 1 ) throw new EOFException( ERR_EMPTY );
// ---- FFT Length ----
// i = Marker.find( reInStream.markers, MARK_PIHALF, 0 ); // use this marker if available
n1 = -1;
/*
if( i >= 0 ) {
mark = (Marker) reInStream.markers.elementAt( i );
reInStream.markers.removeElement( mark ); // we recreate it for the output
} else if( imInStream != null ) {
i = Marker.find( imInStream.markers, MARK_PIHALF, 0 ); // (maybe in the imag. file)
if( i >= 0 ) {
mark= (Marker) imInStream.markers.elementAt( i );
}
}
if( n1 >= 0 ) { // input contains "PiHalf" marker ==> use to calc FFTLength
n2 = mark.pos << 1;
for( dataLen = 2, scaleNum = 1; dataLen < n2; dataLen<<=1, scaleNum++ ) ;
if( dataLen != n2 ) n1 = -1; // calc the normal way below
}
*/
if( n1 < 0 ) { // otherwise expand/trunc input length to power of 2
for( dataLen = 2, scaleNum = 1; dataLen < inLength; dataLen<<=1, scaleNum++ ) ;
if( (pr.intg[ PR_LENGTH ] == LENGTH_TRUNC) && (dataLen > inLength) ) {
dataLen >>= 1;
scaleNum--;
inLength = dataLen;
totalInSamples = inLength * inChanNum;
}
}
halfLen = dataLen >> 1;
if( pr.intg[ PR_DIRECTION ] == DIR_FORWARD ) {
// will be carried forward to output files
markers = (List<Marker>) reInStream.getProperty( AudioFileDescr.KEY_MARKERS );
if( markers == null ) markers = new ArrayList<Marker>( 1 );
markers.add( new Marker( halfLen, MARK_PIHALF ));
reInStream.setProperty( AudioFileDescr.KEY_MARKERS, markers );
}
// ---- open files ----
ggOutput = (PathField) gui.getItemObj( GG_REOUTPUTFILE );
if( ggOutput == null ) throw new IOException( ERR_MISSINGPROP );
reOutStream = new AudioFileDescr( reInStream );
ggOutput.fillStream( reOutStream );
reOutF = AudioFile.openAsWrite( reOutStream );
if( pr.bool[ PR_HASIMOUTPUT ]) {
imOutStream = new AudioFileDescr( reInStream );
ggOutput.fillStream( imOutStream );
imOutStream.file = new File( pr.text[ PR_IMOUTPUTFILE ]);
imOutF = AudioFile.openAsWrite( imOutStream );
}
// .... check running ....
if( !threadRunning ) break topLevel;
// ---- Other init ----
// freqShift = (float) Param.transform( pr.para[ PR_FREQSHIFT ], Param.FACTOR_FREQ,
// new Param( 1.0, Param.ABS_HZ ), null ).value /
// ((pr.intg[ PR_DIRECTION ] == DIR_FORWARD) ? 100 : -100);
freqShift = (pr.intg[ PR_DIRECTION ] == DIR_FORWARD) ? 1 : -1;
// System.out.println( "freqShift "+freqShift );
// ---- Progress Length ----
progOff = 0; // write floats, write output, transform, read input
progLen = dataLen * (3 + scaleNum * inChanNum) + inLength;
if( pr.bool[ PR_HASIMINPUT ]) {
progLen += inLength;
}
if( pr.bool[ PR_HASIMOUTPUT ]) {
progLen += dataLen;
}
if( pr.intg[ PR_GAINTYPE ] == GAIN_UNITY ) {
progLen += dataLen * inChanNum;
}
// ---- create four temp files per channel ----
floatF = new FloatFile[ inChanNum ][ 4 ];
tempFile = new File[ inChanNum ][ 4 ];
for( int ch = 0; ch < inChanNum; ch++ ) {
for( int i = 0; i < 4; i++ ) {
tempFile[ ch ][ i ] = null;
floatF[ ch ][ i ] = null;
}
}
for( int ch = 0; ch < inChanNum; ch++ ) {
for( int i = 0; i < 4; i++ ) {
tempFile[ ch ][ i ] = IOUtil.createTempFile(); // for detail data
floatF[ ch ][ i ] = new FloatFile( tempFile[ ch ][ i ], GenericFile.MODE_OUTPUT );
}
}
// take 75% of free memory, divide by sizeof( float ), divide by 3 (storageFFT needs 3 buffers)
// i = Math.min( dataLen, (int) (Runtime.getRuntime().freeMemory() >> 4) );
i1 = (int) Math.min( 0x40000000, Math.min( dataLen, ((long) pr.para[ PR_MEMORY ].value << 20) / 12 )); // +/- (?) 3x float (=12 bytes)
// power of 2
for( memAmount = 4; memAmount <= i1; memAmount <<= 1 ) ;
memAmount >>= 1;
// memAmount = Math.min( dataLen, 65536 ); // 32768 );
// ---- write initial float files 1 + 2 ----
b1 = (pr.intg[ PR_DIRECTION ] == DIR_BACKWARD) && (pr.intg[ PR_FORMAT ] == FORMAT_POLAR);
buf1 = new float[ inChanNum ][ 8192 ];
if( pr.bool[ PR_HASIMINPUT ] || pr.bool[ PR_HASIMOUTPUT ]) {
buf2 = new float[ inChanNum ][ 8192 ];
} else {
buf2 = buf1;
}
buf3 = new float[ 16384 ];
for( framesRead = 0; threadRunning && (framesRead < inLength); ) {
chunkLen = (int) Math.min( 8192, inLength - framesRead );
if( (framesRead < halfLen) && ((framesRead + chunkLen) > halfLen) ) {
chunkLen = (int) (halfLen - framesRead);
}
reInF.readFrames( buf1, 0, chunkLen );
// .... progress ....
progOff += chunkLen;
setProgression( (float) progOff / (float) progLen );
if( imInF != null ) {
imInF.readFrames( buf2, 0, chunkLen );
// .... progress ....
progOff += chunkLen;
setProgression( (float) progOff / (float) progLen );
}
framesRead += chunkLen;
// interleave real/img
for( int ch = 0; threadRunning && (ch < inChanNum); ch++ ) {
convBuf1 = buf1[ ch ];
convBuf2 = buf2[ ch ];
// ---- real + img ----
if( pr.bool[ PR_HASIMINPUT ]) {
for( int j = 0, k = 0; j < chunkLen; j++ ) {
buf3[ k++ ] = convBuf1[ j ];
buf3[ k++ ] = convBuf2[ j ];
}
// ---- rect => polar ----
if( b1 ) {
Fourier.polar2Rect( buf3, 0, buf3, 0, chunkLen << 1 );
}
// ---- only real ---- NOTE: rect+polar is identical (amp = real; phase = 0)
} else {
for( int j = 0, k = 0; j < chunkLen; j++ ) {
buf3[ k++ ] = convBuf1[ j ];
buf3[ k++ ] = 0.0f; // zero imaginary part
}
}
if( framesRead <= halfLen ) {
floatF[ ch ][ 0 ].writeFloats( buf3, 0, chunkLen << 1 );
} else {
floatF[ ch ][ 1 ].writeFloats( buf3, 0, chunkLen << 1 );
}
}
// .... progress ....
progOff += chunkLen;
setProgression( (float) progOff / (float) progLen );
}
// .... check running ....
if( !threadRunning ) break topLevel;
// zero pad to power of 2
for( int i = 0; i < buf3.length; i++ ) {
buf3[ i ] = 0.0f;
}
while( threadRunning && (framesRead < dataLen) ) {
chunkLen = (int) Math.min( 8192, dataLen - framesRead );
if( (framesRead < halfLen) && ((framesRead + chunkLen) > halfLen) ) {
chunkLen = (int) (halfLen - framesRead);
}
framesRead += chunkLen;
// interleave real/img
for( int ch = 0; threadRunning && (ch < inChanNum); ch++ ) {
if( framesRead <= halfLen ) {
floatF[ ch ][ 0 ].writeFloats( buf3, 0, chunkLen << 1 );
} else {
floatF[ ch ][ 1 ].writeFloats( buf3, 0, chunkLen << 1 );
}
}
// .... progress ....
progOff += chunkLen;
setProgression( (float) progOff / (float) progLen );
}
// .... check running ....
if( !threadRunning ) break topLevel;
reInF.close();
reInF = null;
if( imInF != null ) {
imInF.close();
imInF = null;
}
// ---- transform ----
fftBuf = new float[ 3 ][ memAmount ];
for( int ch = 0; threadRunning && (ch < inChanNum); ch++ ) {
progOff += dataLen * scaleNum; // = progEnd
storageFFT( floatF[ ch ], tempFile[ ch ], dataLen, freqShift, fftBuf, (float) progOff / (float) progLen );
}
// .... check running ....
if( !threadRunning ) break topLevel;
// ---- calc unity gain ----
b1 = (pr.intg[ PR_DIRECTION ] == DIR_FORWARD) && (pr.intg[ PR_FORMAT ] == FORMAT_POLAR);
if( pr.intg[ PR_GAINTYPE ] == GAIN_UNITY ) {
for( int ch = 0; ch < inChanNum; ch++ ) {
for( int i = 2; i < 4; i++ ) {
floatF[ ch ][ i ].seekFloat( 0 );
for( framesRead = 0; threadRunning && (framesRead < dataLen); ) {
chunkLen = (int) Math.min( 8192, dataLen - framesRead );
floatF[ ch ][ i ].readFloats( buf3, 0, chunkLen );
framesRead += chunkLen;
// ---- rect => polar ----
if( b1 ) {
for( int k = 0; k < chunkLen; ) {
d1 = buf3[ k++ ];
d2 = buf3[ k++ ];
f1 = (float) Math.sqrt( d1*d1 + d2*d2 );
if( f1 > maxAmp ) {
maxAmp = f1;
}
}
// ---- normal ----
} else {
// ---- real + img ----
if( pr.bool[ PR_HASIMOUTPUT ]) {
for( int k = 0; k < chunkLen; k++ ) {
if( Math.abs( buf3[ k ]) > maxAmp ) {
maxAmp = Math.abs( buf3[ k ]);
}
}
// ---- only real ----
} else {
for( int k = 0; k < chunkLen; k += 2 ) {
if( Math.abs( buf3[ k ]) > maxAmp ) {
maxAmp = Math.abs( buf3[ k ]);
}
}
}
}
// .... progress ....
progOff += chunkLen >> 1;
setProgression( (float) progOff / (float) progLen );
}
}
}
gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP,
new Param( 1.0 / (double) maxAmp, Param.ABS_AMP ), null )).value;
// absolute gain change
} else {
gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ampRef, null )).value;
gain = (float) (gain / Math.sqrt( dataLen ));
}
// ---- write output ----
// delete unused + rewind
for( int ch = 0; ch < inChanNum; ch++ ) {
int i = 0;
for( ; i < 2; i++ ) {
floatF[ ch ][ i ].cleanUp();
floatF[ ch ][ i ] = null;
//System.out.println( "deleting : " + tempFile[ch][i].getName() );
tempFile[ ch ][ i ].delete();
tempFile[ ch ][ i ] = null;
}
for( ; i < 4; i++ ) {
floatF[ ch ][ i ].seekFloat( 0 );
}
}
indicateOutputWrite();
// ---- write data ----
for( framesWritten = 0; threadRunning && (framesWritten < dataLen); ) {
chunkLen = (int) Math.min( 8192, dataLen - framesWritten );
if( (framesWritten < halfLen) && ((framesWritten + chunkLen) > halfLen) ) {
chunkLen = (int) (halfLen - framesWritten);
}
// de-interleave real/img
for( int ch = 0; threadRunning && (ch < inChanNum); ch++ ) {
if( framesWritten < halfLen ) {
floatF[ ch ][ 2 ].readFloats( buf3, 0, chunkLen << 1 ); // floatF[ ch ][ 2 ]
} else {
floatF[ ch ][ 3 ].readFloats( buf3, 0, chunkLen << 1 ); // floatF[ ch ][ 3 ]
}
convBuf1 = buf1[ ch ];
convBuf2 = buf2[ ch ];
// ---- rect => polar ----
if( b1 ) {
// ---- real + img ----
if( pr.bool[ PR_HASIMOUTPUT ]) {
Fourier.rect2Polar( buf3, 0, buf3, 0, chunkLen << 1 );
// ---- needs gain measurement ----
if( pr.intg[ PR_GAINTYPE ] == GAIN_ABSOLUTE ) {
for( int j = 0, k = 0; j < chunkLen; j++ ) {
if( Math.abs( buf3[ k ]) > maxAmp ) {
maxAmp = Math.abs( buf3[ k ]);
}
convBuf1[ j ] = gain * buf3[ k++ ];
convBuf2[ j ] = (float) (buf3[ k++ ] / Math.PI); // -pi <= Phi <= +pi ===> map to -1...+1
}
// ---- gain already known ----
} else {
for( int j = 0, k = 0; j < chunkLen; j++ ) {
convBuf1[ j ] = gain * buf3[ k++ ];
convBuf2[ j ] = (float) (buf3[ k++ ] / Math.PI); // -pi <= Phi <= +pi ===> map to -1...+1
}
}
// ---- only real ----
} else {
// ---- needs gain measurement ----
if( pr.intg[ PR_GAINTYPE ] == GAIN_ABSOLUTE ) {
for( int j = 0, k = 0; j < chunkLen; j++ ) {
d1 = buf3[ k++ ];
d2 = buf3[ k++ ];
f1 = (float) Math.sqrt( d1*d1 + d2*d2 );
if( f1 > maxAmp ) {
maxAmp = f1;
}
convBuf1[ j ] = gain * f1;
}
// ---- gain already known ----
} else {
for( int j = 0, k = 0; j < chunkLen; j++ ) {
d1 = buf3[ k++ ];
d2 = buf3[ k++ ];
f1 = (float) Math.sqrt( d1*d1 + d2*d2 );
convBuf1[ j ] = gain * f1;
}
}
}
// ---- normal ----
} else {
// ---- real + img ----
if( pr.bool[ PR_HASIMOUTPUT ]) {
// ---- needs gain measurement ----
if( pr.intg[ PR_GAINTYPE ] == GAIN_ABSOLUTE ) {
for( int j = 0, k = 0; j < chunkLen; j++ ) {
if( Math.abs( buf3[ k ]) > maxAmp ) {
maxAmp = Math.abs( buf3[ k ]);
}
convBuf1[ j ] = gain * buf3[ k++ ];
if( Math.abs( buf3[ k ]) > maxAmp ) {
maxAmp = Math.abs( buf3[ k ]);
}
convBuf2[ j ] = gain * buf3[ k++ ];
}
// ---- gain already known ----
} else {
for( int j = 0, k = 0; j < chunkLen; j++ ) {
convBuf1[ j ] = gain * buf3[ k++ ];
convBuf2[ j ] = gain * buf3[ k++ ];
}
}
// ---- only real ----
} else {
// ---- needs gain measurement ----
if( pr.intg[ PR_GAINTYPE ] == GAIN_ABSOLUTE ) {
for( int j = 0, k = 0; j < chunkLen; j++, k += 2 ) {
if( Math.abs( buf3[ k ]) > maxAmp ) {
maxAmp = Math.abs( buf3[ k ]);
}
convBuf1[ j ] = gain * buf3[ k ];
}
// ---- gain already known ----
} else {
for( int j = 0, k = 0; j < chunkLen; j++, k += 2 ) {
convBuf1[ j ] = gain * buf3[ k ];
}
}
}
}
}
// .... progress ....
progOff += chunkLen;
setProgression( (float) progOff / (float) progLen );
reOutF.writeFrames( buf1, 0, chunkLen );
// .... progress ....
progOff += chunkLen;
setProgression( (float) progOff / (float) progLen );
if( imOutF != null ) {
imOutF.writeFrames( buf2, 0, chunkLen );
// .... progress ....
progOff += chunkLen;
setProgression( (float) progOff / (float) progLen );
}
framesWritten += chunkLen;
}
// .... check running ....
if( !threadRunning ) break topLevel;
reOutF.close();
reOutF = null;
if( imOutF != null ) {
imOutF.close();
imOutF = null;
}
// ---- Finish ----
// inform about clipping/ low level
maxAmp *= gain;
handleClipping( maxAmp );
}
catch( IOException e1 ) {
setError( e1 );
}
catch( OutOfMemoryError e2 ) {
buf1 = null;
buf2 = null;
buf3 = null;
System.gc();
setError( new Exception( ERR_MEMORY ));
}
// ---- cleanup (topLevel) ----
buf1 = null;
buf2 = null;
buf3 = null;
if( reInF != null ) {
reInF.cleanUp();
reInF = null;
}
if( imInF != null ) {
imInF.cleanUp();
imInF = null;
}
if( reOutF != null ) {
reOutF.cleanUp();
reOutF = null;
}
if( imOutF != null ) {
imOutF.cleanUp();
imOutF = null;
}
if( floatF != null ) {
for( int ch = 0; ch < floatF.length; ch++ ) {
for( int i = 0; i < 4; i++ ) {
if( floatF[ ch ][ i ] != null ) {
floatF[ ch ][ i ].cleanUp();
floatF[ ch ][ i ] = null;
}
if( tempFile[ ch ][ i ] != null ) {
//System.out.println( "deleting : " + tempFile[ch][i].getName() );
tempFile[ ch ][ i ].delete();
tempFile[ ch ][ i ] = null;
}
}
}
}
} // process()
// -------- private methods --------
/**
* One-dimensional Fourier transform of a large data set stored on external media.
* len must be a power of 2. file[ 0...3 ] contains the stream pointers to 4 temporary files,
* each large enough to hold half of the data. The input data must have its first half stored
* in file file[ 0 ], its second half in file[ 1 ], in native floating point form.
* memAmount real numbers are processed per buffered read or write.
* Output: the first half of the result is stored in file[ 2 ], the second half in file[ 3 ].
*
* @param file 0 = first half input, 1 = second half input
* 2 = first half output, 3 = second half output
* @param len complex fft length (power of 2!)
* @param dir 1 = forward, -1 = inverse transform (multiply by freqShift for special effect!)
* @param buf 3 gleichgrosse float-puffer
* @param progEnd (progress-indicator)
*
* Adaption von Numerical Recipes
*/
protected void storageFFT( FloatFile[] file, File[] tempFile, long len, float dir, float[][] buf, float progEnd )
throws IOException
{
int g, h, i, j, k, kk, ks, kd;
float tempRe, tempIm;
float[] buf1, buf2, buf3;
double wRe, wIm, wpRe, wpIm, tempW, theta, thetaBase;
float wReF, wImF; // reusable float versions
int[] indexMap = { 1, 0, 3, 2 };
int[] fileIndex = new int[ 4 ];
int memAmount;
int framesRead, framesWritten;
long mMax, step, numSteps, halfSteps, jk, n1, n2, kc;
float progOff = getProgression();
float progWeight = progEnd - progOff;
int prog = 0;
int progLen;
memAmount = buf[ 0 ].length;
buf1 = buf[ 0 ];
buf2 = buf[ 1 ];
buf3 = buf[ 2 ];
kc = 0;
mMax = len;
numSteps= len/memAmount;
halfSteps= numSteps >> 1;
thetaBase= dir * Math.PI;
// System.out.println( "memAmount" +memAmount+"; len "+len+"; numSteps "+numSteps+"; halfSteps "+halfSteps );
// calc prog len by simulation (can't figure it out in abstract ;)
prog = 0;
progLen = 0;
n2 = len;
kd = memAmount >> 1;
jk = len;
do {
progLen += Math.max( 1, halfSteps ) * (memAmount << 2) * (halfSteps == 0 ? 1 : 2);
jk >>= 1;
n2 >>= 1;
if( n2 > memAmount ) {
n1 = 2 * ((numSteps + (n2/memAmount) - 1) / (n2/memAmount)) * ((n2 + memAmount - 1) / memAmount);
progLen += n1 * (memAmount << 1);
}
} while( n2 >= memAmount );
j = 0;
do {
progLen += 2 * numSteps * memAmount;
ks = kd;
kd >>= 1;
for( i = 0; i < 2; i++ ) {
for( step = 0; step < numSteps; step++ ) {
kk = 0;
k = ks;
do {
h = (k - kk + 1) & ~1;
j += h;
kk += h;
kk += ks;
k = kk + ks;
} while( kk < memAmount );
if( j >= memAmount ) {
progLen += memAmount << 1;
j = 0;
}
}
}
jk >>= 1;
} while( jk > 1 );
progWeight /= progLen;
n2 = len;
kd = memAmount >> 1;
jk = len;
rewind( file, tempFile, fileIndex );
// file = rewind( file, fileIndex );
// The first phase of the transform starts here.
do { // Start of the computing pass.
theta = thetaBase / (len/mMax);
tempW = Math.sin( theta/2 );
wpRe = -2.0 * tempW * tempW;
wpIm = Math.sin( theta );
wRe = 1.0;
wIm = 0.0;
wReF = 1.0f;
wImF = 0.0f;
mMax >>= 1;
for( i = 0; threadRunning && (i < 2); i++ ) {
step = 0;
do {
for( framesRead = 0; threadRunning && (framesRead < memAmount); ) {
g = Math.min( 32768, memAmount - framesRead );
file[ fileIndex[ 0 ]].readFloats( buf1, framesRead, g );
framesRead += g;
prog += g;
// .... progress ....
setProgression( (float) prog * progWeight + progOff );
}
for( framesRead = 0; threadRunning && (framesRead < memAmount); ) {
g = Math.min( 32768, memAmount - framesRead );
file[ fileIndex[ 1 ]].readFloats( buf2, framesRead, g );
framesRead += g;
prog += g;
// .... progress ....
setProgression( (float) prog * progWeight + progOff );
}
// .... check running ....
if( !threadRunning ) return;
for( j = 0; j < memAmount; j += 2 ) {
h = j + 1;
// double operations more accurate but probably slower ;(
tempRe = wReF * buf2[ j ] - wImF * buf2[ h ];
tempIm = wImF * buf2[ j ] + wReF * buf2[ h ];
// tempRe = (float) (wRe * buf2[ j ] - wIm * buf2[ h ]);
// tempIm = (float) (wIm * buf2[ j ] + wRe * buf2[ h ]);
buf2[ j ] = buf1[ j ] - tempRe;
buf1[ j ] += tempRe;
buf2[ h ] = buf1[ h ] - tempIm;
buf1[ h ] += tempIm;
}
kc += kd;
if( kc == mMax ) {
kc = 0;
tempW = wRe;
wRe += tempW * wpRe - wIm * wpIm;
wIm += tempW * wpIm + wIm * wpRe;
wReF = (float) wRe;
wImF = (float) wIm;
}
for( framesWritten = 0; threadRunning && (framesWritten < memAmount); ) {
g = Math.min( 32768, memAmount - framesWritten );
file[ fileIndex[ 2 ]].writeFloats( buf1, framesWritten, g );
framesWritten += g;
prog += g;
// .... progress ....
setProgression( (float) prog * progWeight + progOff );
}
for( framesWritten = 0; threadRunning && (framesWritten < memAmount); ) {
g = Math.min( 32768, memAmount - framesWritten );
file[ fileIndex[ 3 ]].writeFloats( buf2, framesWritten, g );
framesWritten += g;
prog += g;
// .... progress ....
setProgression( (float) prog * progWeight + progOff );
}
} while( threadRunning && (++step < halfSteps) );
// .... check running ....
if( !threadRunning ) return;
if( (i == 0) && (n2 != len) && (n2 == memAmount) ) {
fileIndex[ 0 ] = indexMap[ fileIndex[ 0 ]];
fileIndex[ 1 ] = fileIndex[ 0 ];
}
if( halfSteps == 0 ) break;
}
// .... check running ....
if( !threadRunning ) return;
rewind( file, tempFile, fileIndex ); // Start of the permutation pass.
// file = rewind( file, fileIndex ); // Start of the permutation pass.
jk >>= 1;
if( jk == 1 ) {
mMax = len;
jk = len;
System.out.println( "We never get here?!!" );
}
n2 >>= 1;
if( n2 > memAmount ) {
for( i = 0; i < 2; i++ ) {
for( step = 0; step < numSteps; step += n2/memAmount ) {
for( n1 = 0; n1 < n2; n1 += memAmount ) {
for( framesRead = 0; threadRunning && (framesRead < memAmount); ) {
g = Math.min( 32768, memAmount - framesRead );
file[ fileIndex[ 0 ]].readFloats( buf1, framesRead, g );
framesRead += g;
prog += g;
// .... progress ....
setProgression( (float) prog * progWeight + progOff );
}
for( framesWritten = 0; threadRunning && (framesWritten < memAmount); ) {
g = Math.min( 32768, memAmount - framesWritten );
file[ fileIndex[ 2 ]].writeFloats( buf1, framesWritten, g );
framesWritten += g;
prog += g;
// .... progress ....
setProgression( (float) prog * progWeight + progOff );
}
// .... check running ....
if( !threadRunning ) return;
}
fileIndex[ 2 ] = indexMap[ fileIndex[ 2 ]];
}
fileIndex[ 0 ] = indexMap[ fileIndex[ 0 ]];
}
rewind( file, tempFile, fileIndex );
// file = rewind( file, fileIndex );
} else if( n2 == memAmount) {
fileIndex[ 1 ] = fileIndex[ 0 ];
}
} while( threadRunning && (n2 >= memAmount) );
// .... check running ....
if( !threadRunning ) return;
// System.out.println( "part 2" );
j = 0;
// The second phase of the transform starts here. Now, the remaining permutations
// are sufficiently local to be done in place.
do {
theta = thetaBase / (len/mMax);
tempW = Math.sin( theta/2 );
wpRe = -2.0 * tempW * tempW;
wpIm = Math.sin( theta );
wRe = 1.0;
wIm = 0.0;
wReF = 1.0f;
wImF = 0.0f;
mMax >>= 1;
ks = kd;
kd >>= 1;
for( i = 0; threadRunning && (i < 2); i++ ) {
for( step = 0; threadRunning && (step < numSteps); step++ ) {
for( framesRead = 0; threadRunning && (framesRead < memAmount); ) {
g = Math.min( 32768, memAmount - framesRead );
file[ fileIndex[ 0 ]].readFloats( buf3, framesRead, g );
framesRead += g;
prog += g;
// .... progress ....
setProgression( (float) prog * progWeight + progOff );
}
// .... check running ....
if( !threadRunning ) return;
kk = 0;
k = ks;
do {
h = kk + ks + 1;
tempRe = wReF * buf3[ kk + ks ] - wImF * buf3[ h ];
tempIm = wImF * buf3[ kk + ks ] + wReF * buf3[ h ];
// tempRe = (float) (wRe * buf3[ kk + ks ] - wIm * buf3[ h ]);
// tempIm = (float) (wIm * buf3[ kk + ks ] + wRe * buf3[ h ]);
buf1[ j ] = buf3[ kk ] + tempRe;
buf2[ j ] = buf3[ kk ] - tempRe;
buf1[ ++j ] = buf3[ ++kk ] + tempIm;
buf2[ j++ ] = buf3[ kk++ ] - tempIm;
if( kk < k ) continue;
kc += kd;
if( kc == mMax ) {
kc = 0;
tempW = wRe;
wRe += tempW * wpRe - wIm * wpIm;
wIm += tempW * wpIm + wIm * wpRe;
wReF = (float) wRe;
wImF = (float) wIm;
}
kk += ks;
// k = kk + ks;
// } while( kk < memAmount );
if( kk >= memAmount ) break;
k = kk + ks;
} while( true );
// flush
if( j >= memAmount ) {
for( framesWritten = 0; threadRunning && (framesWritten < memAmount); ) {
g = Math.min( 32768, memAmount - framesWritten );
file[ fileIndex[ 2 ]].writeFloats( buf1, framesWritten, g );
framesWritten += g;
prog += g;
// .... progress ....
setProgression( (float) prog * progWeight + progOff );
}
for( framesWritten = 0; threadRunning && (framesWritten < memAmount); ) {
g = Math.min( 32768, memAmount - framesWritten );
file[ fileIndex[ 3 ]].writeFloats( buf2, framesWritten, g );
framesWritten += g;
prog += g;
// .... progress ....
setProgression( (float) prog * progWeight + progOff );
}
// .... check running ....
if( !threadRunning ) return;
j = 0;
}
} // for steps
fileIndex[ 0 ] = indexMap[ fileIndex[ 0 ]];
} // for( 1 ... 2 )
// .... check running ....
// if( !threadRunning ) return;
rewind( file, tempFile, fileIndex );
// file = rewind( file, fileIndex );
jk >>= 1;
} while( threadRunning && (jk > 1) );
buf1 = null;
buf2 = null;
buf3 = null;
// file = null;
}
// Utility used by storageFFT. Rewinds and renumbers the four files.
protected void rewind( FloatFile[] file, File[] tempFile, int[] fileIndex )
throws IOException
{
int i;
FloatFile tempF;
File tempF2;
for( i = 0; i < 4; i++ )
{
file[ i ].seekFloat( 0 );
}
tempF = file[ 1 ];
file[ 1 ] = file[ 3 ];
file[ 3 ] = tempF;
tempF = file[ 0 ];
file[ 0 ] = file[ 2 ];
file[ 2 ] = tempF;
tempF2 = tempFile[ 1 ];
tempFile[ 1 ] = tempFile[ 3 ];
tempFile[ 3 ] = tempF2;
tempF2 = tempFile[ 0 ];
tempFile[ 0 ] = tempFile[ 2 ];
tempFile[ 2 ] = tempF2;
fileIndex[ 0 ] = 2;
fileIndex[ 1 ] = 3;
fileIndex[ 2 ] = 0;
fileIndex[ 3 ] = 1;
}
protected void reflectPropertyChanges()
{
super.reflectPropertyChanges();
Component c;
c = gui.getItemObj( GG_IMINPUTFILE );
if( c != null ) {
c.setEnabled( pr.bool[ PR_HASIMINPUT ]);
}
c = gui.getItemObj( GG_IMOUTPUTFILE );
if( c != null ) {
c.setEnabled( pr.bool[ PR_HASIMOUTPUT ]);
}
}
}