/*
* LogFreqOp.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.SpectFrame;
import de.sciss.fscape.spect.SpectStream;
import de.sciss.fscape.spect.SpectStreamSlot;
import de.sciss.fscape.util.Constants;
import de.sciss.fscape.util.Param;
import de.sciss.fscape.util.Slots;
import java.io.EOFException;
import java.io.IOException;
import java.util.Random;
/**
* Operator to map the (linear) frequencies to
* logarithmic frequencies
*/
public class LogFreqOp
extends Operator {
// -------- private fields --------
protected static final String defaultName = "Log freq";
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_RANDPHASE = 0; // pr.bool
// private static final int PR_CALCTYPE = 0; // pr.intg
private static final int PR_HIFREQ = 0; // pr.para
private static final int PR_LOFREQ = 1;
// private static final int PR_MIDMODENV = 0; // pr.envl
private static final String PRN_RANDPHASE = "RandPhase";
private static final String PRN_HIFREQ = "HiFreq";
private static final String PRN_LOFREQ = "LoFreq";
private static final boolean prBool[] = { false };
private static final String prBoolName[] = { PRN_RANDPHASE };
private static final Param prPara[] = { null, null };
private static final String prParaName[] = { PRN_HIFREQ, PRN_LOFREQ };
// -------- public methods --------
// public Container createGUI( int type );
public LogFreqOp()
{
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.bool = prBool;
static_pr.boolName = prBoolName;
static_pr.para = prPara;
static_pr.para[ PR_HIFREQ ] = new Param( 18000.0, Param.ABS_HZ );
static_pr.para[ PR_LOFREQ ] = new Param( 32.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 = "LogFreqOp";
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
icon = new OpIcon( this, OpIcon.ID_FLIPFREQ, defaultName );
}
// -------- Runnable methods --------
public void run()
{
runInit();
final SpectStreamSlot runInSlot;
final SpectStreamSlot runOutSlot;
SpectStream runInStream = null;
SpectStream runOutStream;
SpectFrame runInFr = null;
SpectFrame runOutFr = null;
double srcFreq;
float srcBand;
final int[] srcFloorBands, srcCeilBands;
final float[] srcFloorWeights, srcCeilWeights;
int srcFloorBand;
int srcCeilBand;
float srcFloorWeight;
float srcCeilWeight;
float srcAmp, srcPhase;
double destReal, destImg;
final float loFreq, hiFreq, freqSpacing;
final boolean randPhase = pr.bool[ PR_RANDPHASE ];
final float pi = (float) Math.PI;
int startBand = 0, stopBand;
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;
// srcBand[ x ] ist das Band, das fuer das x-te destBand ausgelesen werden muss
srcFloorBands = new int[ runInStream.bands + 1 ]; // ein Overhead-Band!
// dazugehoerige Gewichtung 0...100%; zur Interpolation wird addiert
srcFloorWeights = new float[ runInStream.bands + 1 ];
srcCeilBands = new int[ runInStream.bands + 1 ];
srcCeilWeights = new float[ runInStream.bands + 1 ];
stopBand = runInStream.bands;
// Frequency spacing (linear!)
freqSpacing = (runInStream.hiFreq - runInStream.loFreq) / runInStream.bands;
// ------------------------------ Output-Slot ------------------------------
runOutSlot = slots.elementAt( SLOT_OUTPUT );
runOutStream = new SpectStream( runInStream );
runOutSlot.initWriter( runOutStream );
loFreq = (float) pr.para[ PR_LOFREQ ].value;
hiFreq = (float) pr.para[ PR_HIFREQ ].value;
// freqSpan = hiFreq / loFreq;
// // calculate lookup tables
// for( int band = 0; band <= runInStream.bands; band++ ) {
// srcFreq = Math.pow( freqSpan, (double) band / runInStream.bands ) * loFreq;
// srcBand = (float) ((srcFreq - runInStream.loFreq) / freqSpacing);
// srcBands[ band ] = (int) Math.floor( srcBand ); // Ceil = srcBands[ x + 1 ]
// srcWeights[ band ] = 1.0f - (srcBand - srcBands[ band ]);
// }
// final float firstBandFreq = runInStream.loFreq + freqSpacing;
// final double expLinFactor = (runInStream.bands - 1) / Math.log( runInStream.hiFreq / firstBandFreq );
final double expLinFactor = (runInStream.bands - 1) / Math.log( hiFreq / loFreq );
for( int band = 0; band <= runInStream.bands; band++ ) {
srcFreq = runInStream.loFreq + band * freqSpacing;
srcBand = (float) (Math.log( srcFreq / loFreq ) * expLinFactor + 1);
srcFloorBands[ band ] = (int) Math.floor( srcBand ); // Floor = srcBands[ x + 1 ]
srcFloorWeights[ band ] = 1.0f - (srcBand - srcFloorBands[ band ]);
// System.out.println( "band " + band + " -> srcBand = " + srcBand );
}
fixLp: for( int band = 0; band < runInStream.bands; band++ ) {
srcFloorBand = srcFloorBands[ band ];
srcCeilBand = srcFloorBands[ band + 1 ];
srcFloorWeight = srcFloorWeights[ band ];
srcCeilWeight = 1.0f - (srcFloorWeights[ band + 1 ]);
if( srcFloorBand < 0 ) {
srcFloorBand = 0;
if( srcFloorBand < srcCeilBand ) {
srcFloorWeight = 1.0f;
} else {
startBand = band + 1;
continue fixLp;
}
}
if( srcCeilBand >= runInStream.bands ) {
srcCeilBand = runInStream.bands - 1;
if( srcCeilBand > srcFloorBand ) {
srcCeilWeight = 1.0f;
} else {
stopBand = band;
break fixLp;
}
}
if( srcFloorBand == srcCeilBand ) {
srcFloorWeight = srcCeilWeight - (1.0f - srcFloorWeight);
srcCeilWeight = 0.0f;
// assert( srcFloorWeight >= 0f );
}
srcFloorBands[ band ] = srcFloorBand;
srcFloorWeights[ band ] = srcFloorWeight;
srcCeilBands[ band ] = srcCeilBand;
srcCeilWeights[ band ] = srcCeilWeight;
// System.out.println( "band = " + band + " -> srcFloorBand = " + srcFloorBand + "; srcFloorWeight = " + srcFloorWeight + "; srcCeilBand = " + srcCeilBand + "; srcCeilWeight = " + srcCeilWeight );
}
/*
// debug: check the cumulative weights
for( int band = startBand; band < stopBand; band++ ) {
srcFloorBand = srcFloorBands[ band ];
srcCeilBand = srcCeilBands[ band ];
srcFloorWeight = srcFloorWeights[ band ];
srcCeilWeight = srcCeilWeights[ band ];
float f1 = srcFloorWeight;
for( int i = srcFloorBand + 1; i < srcCeilBand; i++ ) {
f1 += 1f;
}
f1 += srcCeilWeight;
System.out.println( "band " + band + " -> " + f1 );
}
*/
// ------------------------------ main loop ------------------------------
runSlotsReady();
final Random rnd = new Random();
mainLoop: while( !threadDead ) {
// ---------- read input frame ----------
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;
// ---------- calculate output frame ----------
for( int band = 0; band < startBand; band++ ) {
for( int ch = 0; ch < runInStream.chanNum; ch++ ) {
runOutFr.data[ ch ][ (band << 1) + SpectFrame.AMP ] = 0.0f;
runOutFr.data[ ch ][ (band << 1) + SpectFrame.PHASE ] = 0.0f;
}
}
for( int band = startBand; band < stopBand; band++ ) {
srcFloorBand = srcFloorBands[ band ];
srcCeilBand = srcCeilBands[ band ];
srcFloorWeight = srcFloorWeights[ band ];
srcCeilWeight = srcCeilWeights[ band ];
for( int ch = 0; ch < runInStream.chanNum; ch++ ) { // alle Kanaele
// unterstes Band berechnen
srcAmp = runInFr.data[ ch ][ (srcFloorBand << 1) + SpectFrame.AMP ];
srcPhase = runInFr.data[ ch ][ (srcFloorBand << 1) + SpectFrame.PHASE ];
destImg = srcAmp * Math.sin( srcPhase ) * srcFloorWeight;
destReal = srcAmp * Math.cos( srcPhase ) * srcFloorWeight;
// Zwischenbaender addieren (sofern vorhanden)
for( int i = srcFloorBand + 1; i < srcCeilBand; i++ ) {
srcAmp = runInFr.data[ ch ][ (i << 1) + SpectFrame.AMP ];
srcPhase = runInFr.data[ ch ][ (i << 1) + SpectFrame.PHASE ];
destImg += srcAmp * Math.sin( srcPhase ); // Gewichtung 1.0
destReal += srcAmp * Math.cos( srcPhase );
}
if( srcCeilWeight > 0 ) {
// oberstes Band addieren
srcAmp = runInFr.data[ ch ][ (srcCeilBand << 1) + SpectFrame.AMP ];
srcPhase = runInFr.data[ ch ][ (srcCeilBand << 1) + SpectFrame.PHASE ];
destImg += srcAmp * Math.sin( srcPhase ) * srcCeilWeight;
destReal += srcAmp * Math.cos( srcPhase ) * srcCeilWeight;
}
runOutFr.data[ ch ][ (band << 1) + SpectFrame.AMP ] =
(float) Math.sqrt( destImg*destImg + destReal*destReal );
runOutFr.data[ ch ][ (band << 1) + SpectFrame.PHASE ] =
randPhase ? rnd.nextFloat() * pi :
(float) Math.atan2( destImg, destReal );
}
} // calculation done
for( int band = stopBand; band < runInStream.bands ; band++ ) {
for( int ch = 0; ch < runInStream.chanNum; ch++ ) {
runOutFr.data[ ch ][ (band << 1) + SpectFrame.AMP ] = 0.0f;
runOutFr.data[ ch ][ (band << 1) + SpectFrame.PHASE ] = 0.0f;
}
}
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( OutOfMemoryException e ) {
// abort( e );
// return;
// }
runQuit( null );
}
// -------- GUI methods --------
public PropertyGUI createGUI( int type )
{
PropertyGUI gui;
if( type != GUI_PREFS ) return null;
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" +
"cbRandom Phase,pr"+PRN_RANDPHASE );
return gui;
}
}