/*
* CepstralOp.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.op;
import de.sciss.fscape.gui.FIRDesignerDlg;
import de.sciss.fscape.gui.FilterBox;
import de.sciss.fscape.gui.GroupLabel;
import de.sciss.fscape.gui.OpIcon;
import de.sciss.fscape.gui.PropertyGUI;
import de.sciss.fscape.prop.OpPrefs;
import de.sciss.fscape.prop.Prefs;
import de.sciss.fscape.prop.Presets;
import de.sciss.fscape.prop.PropertyArray;
import de.sciss.fscape.spect.Fourier;
import de.sciss.fscape.spect.SpectFrame;
import de.sciss.fscape.spect.SpectStream;
import de.sciss.fscape.spect.SpectStreamSlot;
import de.sciss.fscape.util.Constants;
import de.sciss.fscape.util.Filter;
import de.sciss.fscape.util.Param;
import de.sciss.fscape.util.Slots;
import de.sciss.fscape.util.Util;
import de.sciss.io.AudioFileDescr;
import java.awt.*;
import java.io.EOFException;
import java.io.IOException;
/**
* @version 0.71, 14-Nov-07
*/
public class CepstralOp
extends Operator
{
// -------- public variables --------
// -------- private variables --------
protected static final String defaultName = "Cepstral";
protected static Presets static_presets = null;
protected static Prefs static_prefs = null;
protected static PropertyArray static_pr = null;
// Slots
protected static final int SLOT_INPUT = 0;
protected static final int SLOT_OUTPUT = 1;
// Properties (defaults)
private static final int PR_LOFREQ = 0; // pr.para
private static final int PR_HIFREQ = 1;
private static final int PR_QUALITY = 0; // pr.intg
private static final String PRN_HIFREQ = "HiFreq";
private static final String PRN_LOFREQ = "LoFreq";
private static final String PRN_QUALITY = "Quality";
private static final int prIntg[] = { FIRDesignerDlg.QUAL_MEDIUM };
private static final String prIntgName[] = { PRN_QUALITY };
private static final Param prPara[] = { null, null };
private static final String prParaName[] = { PRN_LOFREQ, PRN_HIFREQ };
protected static final String ERR_BANDS = "Band# not power of 2";
// -------- public methods --------
public CepstralOp() {
super();
// initialize only in the first instance
// preferences laden
if( static_prefs == null ) {
static_prefs = new OpPrefs( getClass(), getDefaultPrefs() );
}
// propertyarray defaults
if( static_pr == null ) {
static_pr = new PropertyArray();
static_pr.intg = prIntg;
static_pr.intgName = prIntgName;
static_pr.para = prPara;
static_pr.para[ PR_HIFREQ ] = new Param( 22050.0, Param.ABS_HZ );
static_pr.para[ PR_LOFREQ ] = new Param( 50.0, Param.ABS_HZ );
static_pr.paraName = prParaName;
static_pr.superPr = Operator.op_static_pr;
}
// default preset
if( static_presets == null ) {
static_presets = new Presets( getClass(), static_pr.toProperties( true ));
}
// superclass-Felder uebertragen
opName = "CepstralOp";
prefs = static_prefs;
presets = static_presets;
pr = (PropertyArray) static_pr.clone();
// slots
slots.addElement( new SpectStreamSlot( this, Slots.SLOTS_READER )); // SLOT_INPUT
slots.addElement( new SpectStreamSlot( this, Slots.SLOTS_WRITER )); // SLOT_OUTPUT
// icon // XXX
icon = new OpIcon( this, OpIcon.ID_FLIPFREQ, defaultName );
}
// -------- Runnable methods --------
public void run() {
runInit(); // superclass
// Haupt-Variablen fuer den Prozess
int ch, i, j;
float f1, f2, phase1, phase2;
SpectStreamSlot runInSlot;
SpectStreamSlot runOutSlot;
SpectStream runInStream = null;
SpectStream runOutStream;
SpectFrame runInFr = null;
SpectFrame runOutFr = null;
// Ziel-Frame Berechnung
int skip, srcBands, srcSize, fftSize, fullFFTsize;
float loFreq, hiFreq;
float[] fftBuf, fltBuf;
AudioFileDescr tmpStream;
FilterBox fltBox;
Point fltLength;
topLevel:
try {
// ------------------------------ Input-Slot ------------------------------
runInSlot = slots.elementAt( SLOT_INPUT );
if( runInSlot.getLinked() == null ) {
runStop(); // threadDead = true -> folgendes for() wird uebersprungen
}
// diese while Schleife ist noetig, da beim initReader ein Pause eingelegt werden kann
// und die InterruptException ausgeloest wird; danach versuchen wir es erneut
for( boolean initDone = false; !initDone && !threadDead; ) {
try {
runInStream = runInSlot.getDescr(); // throws InterruptedException
initDone = true;
}
catch( InterruptedException ignored) {}
runCheckPause();
}
if( threadDead ) break topLevel;
// ------------------------------ Output-Slot ------------------------------
runOutSlot = slots.elementAt( SLOT_OUTPUT );
runOutStream = new SpectStream( runInStream );
runOutSlot.initWriter( runOutStream );
// ------------------------------ Vorberechnungen ------------------------------
// freqSpacing = (runInStream.hiFreq - runInStream.loFreq) / runInStream.bands;
srcBands = runInStream.bands;
srcSize = srcBands << 1;
loFreq = Math.min( runInStream.smpRate / 2, (float) pr.para[ PR_LOFREQ ].value);
hiFreq = Math.max( 0.5f, Math.min( runInStream.smpRate / 2, (float) pr.para[ PR_HIFREQ ].value));
if( loFreq > hiFreq ) {
f1 = loFreq;
loFreq = hiFreq;
hiFreq = f1;
}
fltBox = new FilterBox();
if( loFreq <= 0.1f ) {
fltBox.filterType = FilterBox.FLT_LOWPASS;
fltBox.cutOff = new Param( hiFreq, Param.ABS_HZ );
} else if( hiFreq >= runInStream.smpRate/2 ) {
fltBox.filterType = FilterBox.FLT_HIGHPASS;
fltBox.cutOff = new Param( loFreq, Param.ABS_HZ );
} else {
fltBox.filterType = FilterBox.FLT_BANDPASS;
fltBox.cutOff = new Param( (loFreq + hiFreq) / 2, Param.ABS_HZ );
fltBox.bandwidth = new Param( hiFreq - loFreq, Param.OFFSET_HZ );
}
// System.out.println( "cutoff "+fltBox.cutOff+"; bandw "+fltBox.bandwidth );
tmpStream = new AudioFileDescr(); // unflexibel
tmpStream.rate = runInStream.smpRate;
fltLength = fltBox.calcLength( tmpStream, pr.intg[ PR_QUALITY ]);
skip = fltLength.x; // complex support not written out
i = fltLength.x + fltLength.y;
j = i + srcBands - 1; // length after conv. with input
for( fftSize = 2; fftSize < j; fftSize <<= 1 ) ;
fullFFTsize = fftSize << 1;
fftBuf = new float[ fullFFTsize ];
fltBuf = new float[ fullFFTsize ];
Util.clear( fltBuf );
fltBox.calcIR( tmpStream, pr.intg[ PR_QUALITY ], Filter.WIN_BLACKMAN, fltBuf, fltLength );
for( i = fftSize, j = fullFFTsize; i > 0; ) {
fltBuf[ --j ] = 0.0f; // img
fltBuf[ --j ] = fltBuf[ --i ]; // real
}
Util.rotate( fltBuf, fullFFTsize, fftBuf, -(skip<<1) ); // adjust support
Fourier.complexTransform( fltBuf, fftSize, Fourier.FORWARD );
// Debug.view( fltBuf, "flt" );
// System.out.println( "SrcSize "+srcSize+"; fltSize "+(fltLength.x+fltLength.y)+"; fftSize "+fftSize );
// ------------------------------ Hauptschleife ------------------------------
runSlotsReady();
mainLoop:
while( !threadDead ) {
// ---------- Frame einlesen ----------
for( boolean readDone = false; (!readDone) && !threadDead; ) {
try {
runInFr = runInSlot.readFrame(); // throws InterruptedException
readDone = true;
runOutFr = runOutStream.allocFrame();
}
catch( InterruptedException ignored) {}
catch( EOFException e ) {
break mainLoop;
}
runCheckPause();
}
if( threadDead ) break mainLoop;
// ---------- Process: Ziel-Frame berechnen ----------
for( ch = 0; ch < runOutStream.chanNum; ch++ ) {
System.arraycopy( runInFr.data[ ch ], 0, fftBuf, 0, srcSize );
// amp1 = fftBuf[ 0 ];
// fftBuf[ 0 ] = -1.0f;
for( i = 0; i < srcSize; i+= 2 ) {
f1 = fftBuf[ i ]; // Re(ln z)=ln r
if( f1 > 1.266416555e-14f ) {
fftBuf[ i ] = (float) (Math.log( f1 )); // (float) (Math.log( f1 ) / 32);
} else {
fftBuf[ i ] = -32f; // -1.0f; // c. ln( 2^-47 ) / 32
}
}
Fourier.unwrapPhases( fftBuf, 0, fftBuf, 0, srcSize );
// amp2 = fftBuf[ srcSize - 2 ];
phase1 = fftBuf[ 1 ];
phase2 = fftBuf[ srcSize - 1 ];
f2 = (float) (srcSize - 2);
for( i = 0; i < srcSize; ) {
f1 = (float) i++ / f2;
// fftBuf[ i++ ] += amp1 * (f1 - 1.0f) - amp2 * f1;
fftBuf[ i++ ] += phase1 * (f1 - 1.0f) - phase2 * f1;
}
// for( i = fftSize, j = fftSize; i > 0; i -= 2, j += 2 ) { // neg.freq. complex conjugate
// fftBuf[ j ] = fftBuf[ i ];
// fftBuf[ j+1 ] = -fftBuf[ i+1 ];
// }
for( i = srcSize; i < fullFFTsize; ) {
fftBuf[ i++ ] = 0.0f;
}
// if( runOutStream.framesWritten < 1 ) {
// Debug.view( fftBuf, "pre" );
// }
// for( i = 0, f1 = 0.0f; i < srcSize; i++ ) {
// f2 = Math.abs( fftBuf[ i ]);
// if( f2 > f1 ) {
// f1 = f2;
// }
// }
// Util.mult( fftBuf, 0, srcSize, 1.0f / f1 );
Fourier.complexTransform( fftBuf, fftSize, Fourier.FORWARD );
// for( i = 0; i < loBand; ) { // erase below (pos) lo
// fftBuf[ i++ ] = 0.0f;
// fftBuf[ i++ ] = 0.0f;
// }
// for( i = hiBand + 2; i < fullFFTsize - hiBand; ) { // erase above (pos) hi / below (neg) hi
// fftBuf[ i++ ] = 0.0f;
// fftBuf[ i++ ] = 0.0f;
// }
// for( i = fullFFTsize - loBand; i < fullFFTsize; ) { // erase above (neg) lo
// fftBuf[ i++ ] = 0.0f;
// fftBuf[ i++ ] = 0.0f;
// }
Fourier.complexMult( fltBuf, 0, fftBuf, 0, fftBuf, 0, fullFFTsize );
Fourier.complexTransform( fftBuf, fftSize, Fourier.INVERSE );
// Util.mult( fftBuf, 0, srcSize, f1 );
f2 = (float) (srcSize - 2);
for( i = 0; i < srcSize; ) { // |exp z|=exp(Re(z))
fftBuf[ i ] = (float) Math.exp( fftBuf[ i ]); // (float) Math.exp( fftBuf[ i ] * 32 );
// fftBuf[ i++ ] = (float) Math.exp( fftBuf[ i ] + amp1 * (1.0f - f1) + amp2 * f1 ); // (float) Math.exp( fftBuf[ i ] * 32 );
f1 = (float) i++ / f2;
fftBuf[ i++ ] += phase1 * (1.0f - f1) + phase2 * f1;
}
// fftBuf[ 0 ] = amp1;
System.arraycopy( fftBuf, 0, runOutFr.data[ ch ], 0, srcSize );
// if( runOutStream.framesWritten < 1 ) {
// Debug.view( fftBuf, "post" );
// }
}
// calculation done
runInSlot.freeFrame( runInFr );
for( boolean writeDone = false; (!writeDone) && !threadDead; ) {
try { // Unterbrechung
runOutSlot.writeFrame( runOutFr ); // throws InterruptedException
writeDone = true;
runFrameDone( runOutSlot, runOutFr );
runOutStream.freeFrame( runOutFr );
}
catch( InterruptedException ignored) {} // mainLoop wird eh gleich verlassen
runCheckPause();
}
} // end of main loop
runInStream.closeReader();
runOutStream.closeWriter();
} // break topLevel
catch (IOException e) {
runQuit(e);
return;
} catch (SlotAlreadyConnectedException e) {
runQuit(e);
return;
}
// catch( OutOfMemoryError e ) {
// abort( e );
// return;
// }
runQuit(null);
}
// -------- GUI methods --------
public PropertyGUI createGUI(int type) {
PropertyGUI gui;
StringBuffer qualJComboBox = new StringBuffer();
if (type != GUI_PREFS) return null;
for (int i = 0; i < FIRDesignerDlg.QUAL_NAMES.length; i++) {
qualJComboBox.append(",it");
qualJComboBox.append(FIRDesignerDlg.QUAL_NAMES[i]);
}
gui = new PropertyGUI(
"gl"+GroupLabel.NAME_GENERAL+"\n" +
"lbHigh frequency;pf"+Constants.absHzSpace+",pr"+PRN_HIFREQ+"\n" +
"lbLow frequency;pf"+Constants.absHzSpace+",pr"+PRN_LOFREQ+"\n" +
"lbQuality;ch,pr"+PRN_QUALITY+qualJComboBox.toString()+"\n" );
return gui;
}
}