/*
* TarnishOp.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.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 java.io.EOFException;
import java.io.IOException;
public class TarnishOp
extends Operator {
// -------- private variables --------
protected static final String defaultName = "Tarnish";
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_WINDOW = 0; // pr.intg
private static final int PR_OVERSMP = 1;
private static final int PR_MODE = 2;
private static final int PR_RESIDUAL = 0; // pr.bool
private static final int PR_MINPHASE = 1;
protected static final int MODE_OVERTONES = 0;
protected static final int MODE_ALL = 1;
private static final String PRN_LOFREQ = "LoFreq";
private static final String PRN_HIFREQ = "HiFreq";
private static final String PRN_OVERSMP = "OverSmp";
private static final String PRN_WINDOW = "Window";
private static final String PRN_RESIDUAL = "Residual";
private static final String PRN_MINPHASE = "MinPhase";
private static final String PRN_MODE = "Mode";
private static final Param prPara[] = { null, null };
private static final String prParaName[] = { PRN_LOFREQ, PRN_HIFREQ };
private static final int prIntg[] = { 0, 0, MODE_OVERTONES };
private static final String prIntgName[] = { PRN_OVERSMP, PRN_WINDOW, PRN_MODE };
private static final boolean prBool[] = { false, true };
private static final String prBoolName[] = { PRN_RESIDUAL, PRN_MINPHASE };
// -------- public methods --------
// public Container createGUI( int type );
public TarnishOp()
{
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_LOFREQ ] = new Param( 100.0, Param.ABS_HZ );
static_pr.para[ PR_HIFREQ ] = new Param( 3000.0, Param.ABS_HZ );
static_pr.paraName = prParaName;
static_pr.intg = prIntg;
static_pr.intgName = prIntgName;
static_pr.bool = prBool;
static_pr.boolName = prBoolName;
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 = "TarnishOp";
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, k, m, n, p, q;
float f1, f2, f3;
SpectStreamSlot runInSlot;
SpectStreamSlot runOutSlot;
SpectStream runInStream = null;
SpectStream runOutStream;
SpectFrame runInFr = null;
SpectFrame runOutFr = null;
// Ziel-Frame Berechnung
int srcBands, fftSize, fullFFTsize, complexFFTsize, winSize, winHalf;
float[] fftBuf, convBuf1, convBuf2, win;
int[] belowMin, aboveMin;
float freqSpacing, minAmp;
float[] slope, maxima;
int oversmp = pr.intg[ PR_OVERSMP ];
int loBand, hiBand;
int numPeaks;
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 ------------------------------
srcBands = runInStream.bands;
winSize = (srcBands - 1) << 1 >> oversmp;
winHalf = winSize >> 1;
win = Filter.createFullWindow( winSize, pr.intg[ PR_WINDOW ]);
fftSize = srcBands - 1;
fullFFTsize = fftSize << 1;
complexFFTsize = fullFFTsize << 1;
// fftBuf = new float[ fullFFTsize + 2];
fftBuf = new float[ complexFFTsize ];
freqSpacing = (runInStream.hiFreq - runInStream.loFreq) / runInStream.bands; // bin width
// for( i = 0; i < winSize; i++ ) {
// fftBuf[ i ] = (float) (Math.sin( Math.PI/2 * i ) + Math.sin( Math.PI/4 * i )) * win[ i ];
// }
// for( ; i < fullFFTsize; ) fftBuf[ i++ ] = 0.0f;
System.arraycopy( win, winHalf, fftBuf, 0, winHalf ); // Zero Phase Rotation!
System.arraycopy( win, 0, fftBuf, fullFFTsize - winHalf, winHalf );
win = new float[ fftSize + 1];
Fourier.realTransform( fftBuf, fullFFTsize, Fourier.FORWARD );
f1 = 1.0f / fftBuf[ 0 ];
for( i = 0, j = 0; i <= fullFFTsize; i += 2, j++ ) {
// win[ j ] = Math.max( 0.0f, fftBuf[ i ] * f1 ); // because of the zero phase we can just take the real values
win[ j ] = fftBuf[ i ] * f1; // because of the zero phase we can just take the real values
}
for( j = 0; j <= fftSize; j++ ) {
if( win[ j ] < 0.125f ) break; // stop at -24 dB window slope falloff
}
winSize = j; // bin-smearing size of the window (till -12 dB)
// Debug.view( win, "win" );
loBand = Math.max( winSize,
Math.min( srcBands - 1, (int) (((pr.para[ PR_LOFREQ].value - runInStream.loFreq) / freqSpacing) + 0.5) ));
hiBand = Math.min( srcBands - winSize,
Math.max( loBand, (int) (((pr.para[ PR_HIFREQ].value - runInStream.loFreq) / freqSpacing) + 0.5) ));
// System.out.println( "bands "+srcBands+"; loBand "+loBand+"; hiBand "+hiBand+ "; winSize "+winSize );
slope = new float[ srcBands ];
maxima = new float[ srcBands ];
belowMin = new int[ srcBands ];
aboveMin = new int[ srcBands ];
// minDamp = 0.25f;
minAmp = 1.0e-3f;
// win2Size = fullFFTsize;
// win2Half = fftSize;
// win2 = Filter.createFullWindow( win2Size, Filter.WIN_BLACKMAN );
// for( i = 0, j = 0; i < fullFFTsize; i+=2, j++ ) {
// slope[j] = fftBuf[i];
// }
// Debug.view( slope, "window spectrum" );
// exp = (Param.transform( pr.para[ PR_CONTRAST ], Param.ABS_AMP, ampRef, null )).value - 1.0;
// maxGain = (float) (Param.transform( pr.para[ PR_MAXBOOST ], Param.ABS_AMP, ampRef, null )).value;
// ------------------------------ 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 ----------
// System.out.println( "frame "+runInStream.framesRead );
for( ch = 0; ch < runOutStream.chanNum; ch++ ) {
convBuf1 = runInFr.data[ ch ];
convBuf2 = runOutFr.data[ ch ];
Util.clear( maxima );
f1 = 0.0f;
for( i = 0, j = 0; j < srcBands; i += 2, j++ ) {
f2 = f1;
f1 = convBuf1[ i ];
slope[ j ] = f1 - f2;
}
// if( runInStream.framesRead < 2 ) Debug.view( slope, "slope "+runInStream.framesRead );
// numPeaks = 0;
f1 = slope[ 0 ];
for( i = loBand, m = i << 1; i < (srcBands - winSize); i++, m += 2 ) {
f2 = f1;
f1 = slope[ i ];
f3 = convBuf1[ m ];
peakPick: if( (f2 >= 0.0f) && (f1 <= 0.0f) && (f3 > minAmp) ) { // local maximum
// System.out.println(" check "+ (i * freqSpacing + runInStream.loFreq) );
k = i - winSize;
for( j = i; j > k; ) {
if( slope[ --j ] <= 0.0f ) break peakPick;
}
while( j > 0 ) {
if( slope[ --j ] <= 0.0f ) break;
}
j++;
// if( (convBuf1[ j << 1 ] / f3) > (2 * win[i-j]) ) break peakPick;
k = i + winSize;
for( p = i; p < k; ) {
if( slope[ ++p ] >= 0.0f ) break peakPick;
}
while( p < fftSize ) {
if( slope[ ++p ] >= 0.0f ) break;
}
p--;
// if( (convBuf1[ m << 1 ] / f3) > (2 * win[m-i]) ) break peakPick;
maxima[ i ] = f3;
belowMin[ i ] = j;
aboveMin[ i ] = p;
// numPeaks++;
// System.out.println( "peak "+i );
}
}
// System.out.println( numPeaks );
// init allpass
for( i = 0; i <= fftSize; ) {
fftBuf[ i++ ] = 1.0f;
}
numPeaks=0;
// kill overtones
for( i = loBand; i <= hiBand; i++ ) {
if( maxima[i] == 0.0f ) continue;
switch( pr.intg[ PR_MODE ] ) {
case MODE_OVERTONES:
for( j = i << 1, q = 2; j < srcBands; j += i, q++ ) {
for( m = Math.max( i + 1, j - winSize - q ); m < Math.min( srcBands, j + winSize + q ); m++ ) {
if( maxima[m] == 0.0f ) continue;
// overtone detected
n = belowMin[m];
p = aboveMin[m];
for( n = m - 1, p = 0; p < (m - 1); n--, p++ ) {
fftBuf[ n ] *= 1.0f - win[ p ]; // * f1;
}
fftBuf[ m ] = 0.0f;
for( n = m+1, p = 0; p < fftSize - m; n++, p++ ) {
fftBuf[ n ] *= 1.0f - win[ p ]; // * f1;
}
maxima[m] = 0.0f;
numPeaks++;
}
}
break;
case MODE_ALL:
m = i;
// overtone detected
n = belowMin[m];
p = aboveMin[m];
for( n = m - 1, p = 0; p < (m - 1); n--, p++ ) {
fftBuf[ n ] *= 1.0f - win[ p ]; // * f1;
}
fftBuf[ m ] = 0.0f;
for( n = m+1, p = 0; p < fftSize - m; n++, p++ ) {
fftBuf[ n ] *= 1.0f - win[ p ]; // * f1;
}
maxima[m] = 0.0f;
numPeaks++;
break;
}
}
// System.out.println( numPeaks );
// Debug.view( fftBuf, "filter "+runInStream.framesRead );
// float[] test= new float[fftSize];
// System.arraycopy( fftBuf, 0, test, 0, fftSize );
// Debug.view( test, "spect1" );
if( pr.bool[ PR_RESIDUAL ]) {
for( i = fftSize+1, j = fullFFTsize+2; i > 0; ) {
fftBuf[--j] = 0.0f;
fftBuf[--j] = 1.0f - fftBuf[--i];
}
} else {
for( i = fftSize+1, j = fullFFTsize+2; i > 0; ) {
fftBuf[--j] = 0.0f;
fftBuf[--j] = fftBuf[--i];
}
}
//for( i = 0; i <= fullFFTsize; i += 2 ) {
// fftBuf[ i ] = (float) (0.6 + 0.4 * Math.sin( i * 0.001 ));
//}
/*
float[] ttt= new float[fftSize+1];
float[] fftBuf2= new float[fullFFTsize+2];
for( i = 0, j = 0; i <= fullFFTsize; i+=2, j++ ) {
ttt[j] = fftBuf[i];
}
Debug.view( ttt, "amp spect1" );
System.arraycopy( fftBuf, 0, fftBuf2, 0, fullFFTsize + 2 );
Fourier.realTransform( fftBuf2, fullFFTsize, Fourier.INVERSE );
Debug.view( fftBuf2, "time1" );
*/
// ---- make minimum phase ----
if( pr.bool[ PR_MINPHASE ] && (numPeaks > 0) ) {
// log magnitude
for( i = 0; i <= fullFFTsize; i += 2 ) {
fftBuf[ i ] = (float) Math.log( Math.max( 1.0e-24, fftBuf[ i ]));
}
// make full spectrum
for( i = fullFFTsize + 2, j = fullFFTsize - 2; i < complexFFTsize; j -= 2 ) {
fftBuf[ i++ ] = fftBuf[ j ];
fftBuf[ i++ ] = 0.0f;
}
// cepstrum domain
Fourier.complexTransform( fftBuf, fullFFTsize, Fourier.INVERSE );
// fold cepstrum (make anticausal parts causal)
for( i = 2, j = complexFFTsize - 2; i < fullFFTsize; i += 2, j -= 2 ) {
fftBuf[ i ] += fftBuf[ j ]; // add conjugate left wing to right wing
fftBuf[ i+1 ] -= fftBuf[ j+1 ];
}
i++;
fftBuf[ i ] = -fftBuf[ i ];
i++;
// clear left wing
while( i < complexFFTsize ) fftBuf[ i++ ] = 0.0f;
// back to frequency domain
Fourier.complexTransform( fftBuf, fullFFTsize, Fourier.FORWARD );
// complex exponential (mag' = exp(real); phase' = imag)
for( i = 0; i <= fullFFTsize; i += 2 ) {
fftBuf[ i ] = (float) Math.exp( fftBuf[ i ]);
// imag untouched -> rect2polar automatic
}
}
/*
for( i = 0, j = 0; i <= fullFFTsize; i+=2, j++ ) {
ttt[j] = fftBuf[i];
}
Debug.view( ttt, "amp spect2" );
System.arraycopy( fftBuf, 0, fftBuf2, 0, fullFFTsize + 2 );
Fourier.realTransform( fftBuf2, fullFFTsize, Fourier.INVERSE );
Debug.view( fftBuf2, "time2" );
*/
for( i = 0; i <= fullFFTsize; ) {
convBuf2[ i ] = convBuf1[ i ] * fftBuf[ i ];
i++;
convBuf2[ i ] = convBuf1[ i ] + fftBuf[ i ];
i++;
}
}
// 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;
String[] winNames = Filter.getWindowNames();
StringBuffer winChoise = new StringBuffer();
if( type != GUI_PREFS ) return null;
for( int i = 0; i < winNames.length; i++ ) {
winChoise.append( ",it" );
winChoise.append( winNames[ i ]);
}
gui = new PropertyGUI(
"gl"+GroupLabel.NAME_GENERAL+"\n" +
"lbLow frequency;pf"+Constants.absHzSpace+",pr"+PRN_LOFREQ+"\n" +
"lbHigh frequency;pf"+Constants.absHzSpace+",pr"+PRN_HIFREQ+"\n" +
"lbTarnish;ch,pr"+PRN_MODE+",itOvertones,itAll Harmonics\n"+
"lbWindow;ch,pr"+PRN_WINDOW+winChoise.toString()+"\n"+
"lbOversampling;ch,pr"+PRN_OVERSMP+"," +
"it1x (none)," +
"it2x," +
"it4x," +
"it8x\n"+
"cbMinimum phase,pr"+PRN_MINPHASE+"\n"+
"cbResidual,pr"+PRN_RESIDUAL+"\n" );
return gui;
}
}