/*
* FIRDesignerDlg.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:
* 01-Mar-05 bugfix in calcIR()
* 05-Mar-05 uses VectorPanel for spectrum view;
* uses Java 1.2 API (Iterator instead of Enumeration,
* ArrayList instead of Vector)
* 14-Mar-05 support for roll-off (see FilterBox)
*/
package de.sciss.fscape.gui;
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.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.VectorSpace;
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.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;
/**
* Processing module for arranging and
* combining window'ed sinc filters
* that be written out as IR files
* to be used in the convolution modules.
*/
public class FIRDesignerDlg
extends ModulePanel
implements VectorPanel.Client {
// -------- public variables --------
public static final int QUAL_LOW = 0; // PR_QUALITY
public static final int QUAL_MEDIUM = 1;
public static final int QUAL_GOOD = 2;
public static final int QUAL_VERYGOOD = 3;
public static final String[] QUAL_NAMES = { "Short", "Medium", "Long", "Very Long" };
public static final String MARK_SUPPORT = "Support";
// -------- private variables --------
// Properties (defaults)
private static final int PR_OUTPUTFILE = 0; // pr.text
private static final int PR_CIRCUIT = 1;
private static final int PR_OUTPUTTYPE = 0; // pr.intg
private static final int PR_OUTPUTRES = 1;
private static final int PR_OUTPUTRATE = 2;
private static final int PR_QUALITY = 3;
private static final int PR_WINDOW = 4;
private static final int PR_GAINTYPE = 5;
private static final int PR_MINPHASE = 0; // pr.bool
private static final int PR_GAIN = 0; // pr.para
private static final String PRN_OUTPUTFILE = "OutputFile";
private static final String PRN_CIRCUIT = "Circuit";
private static final String PRN_OUTPUTTYPE = "OutputType";
private static final String PRN_OUTPUTRES = "OutputRes";
private static final String PRN_OUTPUTRATE = "OutputRate";
private static final String PRN_QUALITY = "Quality";
private static final String PRN_WINDOW = "Window";
private static final String PRN_MINPHASE = "MinPhase";
private static final String[] prText = { "", "03{1;false;1000.0,3;250.0,35;0.0,785;0.0,2;false;5000.0,3;1000.0,35}" };
private static final String[] prTextName = { PRN_OUTPUTFILE, PRN_CIRCUIT };
private static final int[] prIntg = { 0, PathField.SNDRES_32F, 0, QUAL_GOOD, 0, GAIN_UNITY };
private static final String[] prIntgName = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_OUTPUTRATE, PRN_QUALITY,
PRN_WINDOW, PRN_GAINTYPE };
private static final boolean[] prBool = { false };
private static final String[] prBoolName = { PRN_MINPHASE };
private static final Param[] prPara = { null };
private static final String[] prParaName = { PRN_GAIN };
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_OUTPUTRATE = GG_OFF_CHOICE + PR_OUTPUTRATE;
private static final int GG_QUALITY = GG_OFF_CHOICE + PR_QUALITY;
private static final int GG_WINDOW = GG_OFF_CHOICE + PR_WINDOW;
private static final int GG_GAINTYPE = GG_OFF_CHOICE + PR_GAINTYPE;
private static final int GG_MINPHASE = GG_OFF_CHECKBOX + PR_MINPHASE;
private static final int GG_GAIN = GG_OFF_PARAMFIELD + PR_GAIN;
private static final int GG_CIRCUIT = GG_OFF_OTHER + 0;
private static final int GG_FILTERTYPE = GG_OFF_OTHER + 1;
private static final int GG_SIGN = GG_OFF_OTHER + 2;
private static final int GG_CUTOFF = GG_OFF_OTHER + 3;
private static final int GG_BANDWIDTH = GG_OFF_OTHER + 4;
private static final int GG_FILTERGAIN = GG_OFF_OTHER + 5;
private static final int GG_DELAY = GG_OFF_OTHER + 6;
private static final int GG_OTLIMIT = GG_OFF_OTHER + 7;
private static final int GG_OTSPACING = GG_OFF_OTHER + 8;
private static final int GG_OVERTONES = GG_OFF_OTHER + 9;
private static final int GG_ROLLOFF = GG_OFF_OTHER + 10;
private static PropertyArray static_pr = null;
private static Presets static_presets = null;
private static final int CMD_UNKNOWN = -1;
private static final int CMD_SELECTED = 0;
private static final int CMD_DESELECTED = 1;
private static final int CMD_CREATED = 2;
private static final int CMD_DELETED = 3;
private FilterBox currentFlt = null;
private static final String ERR_RESULT_EMPTY = "IR length will be zero";
private final MessageFormat msgHertz = new MessageFormat( "{0,number,0.0} Hz", Locale.US ); // XXX US locale
private final MessageFormat msgDecibel = new MessageFormat( "{0,number,0.0} dB", Locale.US ); // XXX US locale
private final MessageFormat msgPlain = new MessageFormat( "{0,number,0.000}", Locale.US ); // XXX US locale
private VectorPanel spectPane;
private CircuitPanel ggCircuit;
// -------- public methods --------
public FIRDesignerDlg() {
super("FIR Designer");
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.bool = prBool;
static_pr.boolName = prBoolName;
static_pr.para = prPara;
static_pr.paraName = prParaName;
// static_pr.superPr = DocumentFrame.static_pr;
fillDefaultAudioDescr( static_pr.intg, PR_OUTPUTTYPE, -1, PR_OUTPUTRATE );
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 --------
PathField ggOutputFile;
JComboBox ggFilterType, ggQuality, ggWindow;
ParamField ggCutOff, ggRollOff, ggBandwidth, ggFilterGain, ggDelay, ggOTLimit, ggOTSpacing;
JCheckBox ggSign, ggOvertones, ggMinPhase;
ParamSpace spcBandwidth[], spcDelay[], spcLimit[];
JTabbedPane ggTab;
Box box;
CompactPanel panel;
Component[] ggGain;
gui = new GUISupport();
ItemListener il = new ItemListener() {
public void itemStateChanged( ItemEvent e )
{
int ID = gui.getItemID( e );
JComboBox ch;
JCheckBox cb;
boolean b = (currentFlt != null);
switch( ID ) {
case GG_FILTERTYPE:
ch = (JComboBox) e.getSource();
if( b ) {
currentFlt.filterType = ch.getSelectedIndex();
ggCircuit.repaintBox( currentFlt );
reflectPropertyChanges();
}
break;
case GG_SIGN:
cb = (JCheckBox) e.getSource();
if( b ) {
currentFlt.sign = cb.isSelected();
ggCircuit.repaintBox( currentFlt );
}
break;
case GG_OVERTONES:
cb = (JCheckBox) e.getSource();
if( b ) {
currentFlt.overtones = cb.isSelected();
ggCircuit.repaintBox( currentFlt );
reflectPropertyChanges();
}
break;
}
}
};
ParamListener paramL = new ParamListener() {
public void paramChanged( ParamEvent e )
{
int ID = gui.getItemID( e );
ParamField pf;
boolean b = (currentFlt != null);
switch( ID ) {
case GG_CUTOFF:
pf = (ParamField) e.getSource();
if( b ) currentFlt.cutOff = pf.getParam();
break;
case GG_BANDWIDTH:
pf = (ParamField) e.getSource();
if( b ) currentFlt.bandwidth = pf.getParam();
break;
case GG_ROLLOFF:
pf = (ParamField) e.getSource();
if( b ) currentFlt.rollOff = pf.getParam();
break;
case GG_FILTERGAIN:
pf = (ParamField) e.getSource();
if( b ) {
currentFlt.gain = pf.getParam();
ggCircuit.repaintBox( currentFlt );
}
break;
case GG_DELAY:
pf = (ParamField) e.getSource();
if( b ) {
currentFlt.delay = pf.getParam();
ggCircuit.repaintBox( currentFlt );
}
break;
case GG_OTLIMIT:
pf = (ParamField) e.getSource();
if( b ) currentFlt.otLimit = pf.getParam();
break;
case GG_OTSPACING:
pf = (ParamField) e.getSource();
if( b ) currentFlt.otSpacing = pf.getParam();
break;
}
}
};
ActionListener al = new ActionListener() {
public void actionPerformed( ActionEvent e )
{
int ID = gui.getItemID( e );
String val;
int cmd;
ParamField pf;
JComboBox ch;
JCheckBox cb;
// boolean b = (currentFlt != null);
// boolean b2;
FilterBox flt;
switch( ID ) {
case GG_CIRCUIT:
val = e.getActionCommand();
cmd = CMD_UNKNOWN;
if(val.equals(CircuitPanel.ACTION_BOXSELECTED)) {
cmd = CMD_SELECTED;
} else if(val.equals(CircuitPanel.ACTION_BOXDESELECTED)) {
cmd = CMD_DESELECTED;
} else if(val.equals(CircuitPanel.ACTION_BOXCREATED)) {
cmd = CMD_CREATED;
} else if(val.equals(CircuitPanel.ACTION_BOXDELETED)) {
cmd = CMD_DELETED;
}
flt = null;
switch( cmd ) {
case CMD_SELECTED:
case CMD_CREATED:
flt = (FilterBox) ((CircuitPanel) e.getSource()).getActiveBox();
break;
}
if( flt == currentFlt ) return;
currentFlt = flt;
// ---- display new values ----
boolean b = (flt != null);
if( b ) {
// b2 = true;
// b3 = true;
ch = (JComboBox) gui.getItemObj( GG_FILTERTYPE );
if( ch != null ) {
ch.setSelectedIndex( flt.filterType );
switch( flt.filterType ) {
case FilterBox.FLT_ALLPASS:
// b3 = false;
// THRU
case FilterBox.FLT_LOWPASS:
case FilterBox.FLT_HIGHPASS:
// b2 = false; // !!
break;
}
}
cb = (JCheckBox) gui.getItemObj( GG_SIGN );
if( cb != null ) {
cb.setSelected( flt.sign );
}
pf = (ParamField) gui.getItemObj( GG_CUTOFF );
if( pf != null ) {
pf.setParam( flt.cutOff );
}
pf = (ParamField) gui.getItemObj( GG_BANDWIDTH );
if( pf != null ) {
pf.setParam( flt.bandwidth );
}
pf = (ParamField) gui.getItemObj( GG_ROLLOFF );
if( pf != null ) {
pf.setParam( flt.rollOff );
}
pf = (ParamField) gui.getItemObj( GG_FILTERGAIN );
if( pf != null ) {
pf.setParam( flt.gain );
}
pf = (ParamField) gui.getItemObj( GG_DELAY );
if( pf != null ) {
pf.setParam( flt.delay );
}
cb = (JCheckBox) gui.getItemObj( GG_OVERTONES );
if( cb != null ) {
cb.setSelected( flt.overtones );
// b2 = (b2 && flt.overtones); // !!
}
pf = (ParamField) gui.getItemObj( GG_OTLIMIT );
if( pf != null ) {
pf.setParam( flt.otLimit );
}
pf = (ParamField) gui.getItemObj( GG_OTSPACING );
if( pf != null ) {
pf.setParam( flt.otSpacing );
}
}
reflectPropertyChanges();
break;
}
}
};
// -------- I/O-Gadgets --------
final Box panelTop = Box.createVerticalBox();
panelTop.add(new GroupLabel("FIR Output", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE));
panel = new CompactPanel();
ggOutputFile = new PathField( PathField.TYPE_OUTPUTFILE + PathField.TYPE_FORMATFIELD +
PathField.TYPE_RESFIELD + PathField.TYPE_RATEFIELD,
"Select output file" );
ggOutputFile.handleTypes(GenericFile.TYPES_SOUND);
gui.registerGadget(ggOutputFile, GG_OUTPUTFILE);
// ggOutputFile.addActionListener( this );
gui.registerGadget(ggOutputFile.getTypeGadget(), GG_OUTPUTTYPE);
gui.registerGadget(ggOutputFile.getResGadget(), GG_OUTPUTRES);
gui.registerGadget(ggOutputFile.getRateGadget(), GG_OUTPUTRATE);
panel.addGadget(new JLabel("File name:", SwingConstants.RIGHT));
panel.addGadget(ggOutputFile);
panel.newLine();
box = Box.createHorizontalBox();
ggGain = createGadgets(GGTYPE_GAIN);
panel.addGadget(new JLabel("Gain", SwingConstants.RIGHT));
gui.registerGadget(ggGain[0], GG_GAIN);
((ParamField) ggGain[0]).addParamListener(paramL);
gui.registerGadget(ggGain[1], GG_GAINTYPE);
((JComboBox) ggGain[1]).addItemListener(il);
box.add(ggGain[0]);
box.add(ggGain[1]);
panel.addGadget(box);
panel.compact();
panelTop.add(panel);
final JPanel pageBox = new JPanel(new BorderLayout());
pageBox.add(panelTop, BorderLayout.NORTH);
// -------- FIR Design-Parameter --------
final JPanel panelMid = new JPanel(new BorderLayout());
panelMid.add(new GroupLabel("Filter Settings", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE), BorderLayout.NORTH);
ggCircuit = new CircuitPanel(new FilterBox());
ggTab = new JTabbedPane();
ggTab.putClientProperty("styleId", "attached");
ggTab.addTab("Circuit", ggCircuit);
gui.registerGadget(ggCircuit, GG_CIRCUIT);
ggCircuit.addActionListener(al);
spectPane = new VectorPanel(this, VectorPanel.FLAG_HLOG_GADGET | VectorPanel.FLAG_VLOG_GADGET |
VectorPanel.FLAG_UPDATE_GADGET);
ggTab.addTab("Mag. spectrum", spectPane);
panel = new CompactPanel();
box = Box.createHorizontalBox();
ggFilterType = new JComboBox();
ggFilterType.addItem("All Pass");
ggFilterType.addItem("Low Pass");
ggFilterType.addItem("High Pass");
ggFilterType.addItem("Band Pass");
ggFilterType.addItem("Band Stop");
panel.addGadget(new JLabel("Type:", SwingConstants.RIGHT));
gui.registerGadget(ggFilterType, GG_FILTERTYPE);
ggFilterType.addItemListener(il);
box.add(ggFilterType);
ggSign = new JCheckBox("Subtract");
gui.registerGadget(ggSign, GG_SIGN);
ggSign.addItemListener(il);
box.add(ggSign);
panel.addGadget(box);
spcBandwidth = new ParamSpace[ 2 ];
spcBandwidth[0] = Constants.spaces[ Constants.offsetHzSpace ];
spcBandwidth[1] = Constants.spaces[ Constants.offsetSemitonesSpace ];
spcLimit = new ParamSpace[ 3 ];
spcLimit[0] = Constants.spaces[ Constants.absHzSpace ];
spcLimit[1] = spcBandwidth[ 0 ];
spcLimit[2] = spcBandwidth[ 1 ];
spcDelay = new ParamSpace[ 2 ];
spcDelay[0] = Constants.spaces[ Constants.absMsSpace ];
spcDelay[1] = Constants.spaces[ Constants.absBeatsSpace ];
panel.newLine();
ggCutOff = new ParamField(Constants.spaces[Constants.absHzSpace]);
panel.addGadget(new JLabel("Cutoff:", SwingConstants.RIGHT));
gui.registerGadget(ggCutOff, GG_CUTOFF);
ggCutOff.addParamListener(paramL);
panel.addGadget(ggCutOff);
panel.newLine();
ggRollOff = new ParamField(spcBandwidth);
ggRollOff.setReference(ggCutOff);
panel.addGadget(new JLabel("Roll-off:", SwingConstants.RIGHT));
gui.registerGadget(ggRollOff, GG_ROLLOFF);
ggRollOff.addParamListener(paramL);
panel.addGadget(ggRollOff);
panel.newLine();
ggBandwidth = new ParamField(spcBandwidth);
ggBandwidth.setReference(ggCutOff);
panel.addGadget(new JLabel("Bandwidth:", SwingConstants.RIGHT));
gui.registerGadget(ggBandwidth, GG_BANDWIDTH);
ggBandwidth.addParamListener(paramL);
panel.addGadget(ggBandwidth);
panel.newLine();
ggFilterGain = new ParamField(Constants.spaces[Constants.decibelAmpSpace]);
panel.addGadget(new JLabel("Gain:", SwingConstants.RIGHT));
gui.registerGadget(ggFilterGain, GG_FILTERGAIN);
ggFilterGain.addParamListener(paramL);
panel.addGadget(ggFilterGain);
panel.newLine();
ggDelay = new ParamField(spcDelay);
panel.addGadget(new JLabel("Delay:", SwingConstants.RIGHT));
gui.registerGadget(ggDelay, GG_DELAY);
ggDelay.addParamListener(paramL);
panel.addGadget(ggDelay);
panel.newLine();
panel.addEmptyColumn();
ggOvertones = new JCheckBox("Add 'overtones'");
gui.registerGadget(ggOvertones, GG_OVERTONES);
ggOvertones.addItemListener(il);
panel.addGadget(ggOvertones);
panel.newLine();
ggOTLimit = new ParamField(spcLimit);
ggOTLimit.setReference(ggCutOff);
panel.addGadget(new JLabel("Limit freq:", SwingConstants.RIGHT));
gui.registerGadget(ggOTLimit, GG_OTLIMIT);
ggOTLimit.addParamListener(paramL);
panel.addGadget(ggOTLimit);
panel.newLine();
ggOTSpacing = new ParamField(spcBandwidth);
ggOTSpacing.setReference(ggCutOff);
panel.addGadget(new JLabel("Spacing:", SwingConstants.RIGHT));
gui.registerGadget(ggOTSpacing, GG_OTSPACING);
ggOTSpacing.addParamListener(paramL);
panel.addGadget(ggOTSpacing);
panel.compact(8, 0);
final JPanel p2 = new JPanel(new BorderLayout());
panelMid.add(ggTab, BorderLayout.CENTER);
p2.add(panel, BorderLayout.NORTH);
panelMid.add(p2 , BorderLayout.EAST);
pageBox.add(panelMid, BorderLayout.CENTER);
final JPanel panelBot = new JPanel(new FlowLayout(FlowLayout.LEADING, 4, 2));
// panel = new CompactPanel();
ggWindow = new JComboBox();
GUISupport.addItemsToChoice(Filter.getWindowNames(), ggWindow);
panelBot.add(new JLabel("IIR\u2192FIR win:", SwingConstants.RIGHT)); // unicode 2192 = arrow right
gui.registerGadget(ggWindow, GG_WINDOW);
ggWindow.addItemListener(il);
panelBot.add(ggWindow);
ggQuality = new JComboBox();
for (String QUAL_NAME : QUAL_NAMES) {
ggQuality.addItem(QUAL_NAME);
}
panelBot.add(Box.createHorizontalStrut(4));
panelBot.add(new JLabel("Filter Length:", SwingConstants.RIGHT));
gui.registerGadget(ggQuality, GG_QUALITY);
ggQuality.addItemListener(il);
panelBot.add(ggQuality);
ggMinPhase = new JCheckBox("Make minimum phase");
gui.registerGadget(ggMinPhase, GG_MINPHASE);
ggMinPhase.addItemListener(il);
panelBot.add(Box.createHorizontalStrut(4));
panelBot.add(ggMinPhase);
// panel.compact();
pageBox.add(panelBot, BorderLayout.SOUTH);
initGUI( this, FLAGS_PRESETS | FLAGS_PROGBAR, pageBox );
}
/**
* Transfer values from prop-array to GUI
*/
public void fillGUI()
{
super.fillGUI();
super.fillGUI( gui );
ggCircuit.setCircuit( pr.text[ PR_CIRCUIT ]);
}
/**
* Transfer values from GUI to prop-array
*/
public void fillPropertyArray()
{
super.fillPropertyArray();
super.fillPropertyArray( gui );
pr.text[ PR_CIRCUIT ] = ggCircuit.getCircuit();
}
// -------- Processor Interface --------
protected void process()
{
long progOff, progLen;
float maxAmp = 0.0f;
AudioFile outF = null;
PathField ggOutput;
AudioFileDescr outStream;
int outChanNum = 1; // fix
int outLength, fftLength, complexFFTsize, support;
float[] outBuf;
float[][] outBufWrap, impBuf;
Point impLength;
float f1;
Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz
float gain; // = 1.0f; // gain abs amp
java.util.List<Marker> markers;
topLevel: try {
// ---- open files ----
ggOutput = (PathField) gui.getItemObj( GG_OUTPUTFILE );
if( ggOutput == null ) throw new IOException( ERR_MISSINGPROP );
outStream = new AudioFileDescr();
ggOutput.fillStream( outStream );
outStream.channels = outChanNum;
// .... check running ....
if( !threadRunning ) break topLevel;
// ---- preparations ----
impLength = calcLength( ggCircuit, outStream );
outLength = impLength.x + impLength.y;
if( pr.bool[ PR_MINPHASE ]) outLength *= 2; // account for modified ringing out
if( outLength <= 0 ) throw new IOException( ERR_RESULT_EMPTY );
for( fftLength = 2; fftLength < outLength; fftLength <<= 1 ) ;
complexFFTsize = fftLength << 1;
IOUtil.createEmptyFile( new File( pr.text[ PR_OUTPUTFILE ]));
// System.out.println( "impLength === "+impLength.x+" ... "+impLength.y+" fft "+fftLength );
progOff = 0;
progLen = (long) outLength * 3;
// ---- da FIRDesigner ----
outBuf = new float[ pr.bool[ PR_MINPHASE ] ? complexFFTsize : fftLength + 2 ];
outBufWrap = new float[ outChanNum ][];
for( int ch = 0; ch < outChanNum; ch++ ) {
outBufWrap[ ch ] = outBuf;
}
impBuf = new float[ 3 ][];
impBuf[ 0 ] = outBuf;
impBuf[ 1 ] = new float[ 1 ];
impBuf[ 1 ][ 0 ]= 0.0f; // time domain
impBuf[ 2 ] = new float[ impLength.x ]; // rotate buffer
calcIR( ggCircuit, outStream, impBuf, impLength, fftLength );
progOff += outLength;
// .... progress ....
setProgression( (float) progOff / (float) progLen );
if( !threadRunning ) break topLevel;
if( pr.bool[ PR_MINPHASE ]) {
// need freq domain
if( impBuf[ 1 ][ 0 ] == 0.0f ) {
Util.rotate( outBuf, fftLength, impBuf[ 2 ], -impLength.x ); // rotation
Fourier.realTransform( outBuf, fftLength, Fourier.FORWARD );
impBuf[ 1 ][ 0 ] = 1.0f;
}
// complex log : real' = log(mag); imag' = phase
Fourier.rect2Polar( outBuf, 0, outBuf, 0, fftLength + 2 );
// Fourier.unwrapPhases( outBuf, 0, outBuf, 0, fftLength + 2 );
// calc log(mag)
for( int i = 0; i <= fftLength; i += 2 ) {
// outBuf[ i ] = (float) Math.log( Math.max( 1.0e-24, outBuf[ i ]));
outBuf[ i ] = (float) Math.log( Math.max( 1.0e-48, outBuf[ i ]));
}
// make full (complex) spectrum
for( int i = fftLength + 2, j = fftLength - 2; i < complexFFTsize; j -= 2 ) {
outBuf[ i++ ] = outBuf[ j ];
outBuf[ i++ ] = 0.0f;
}
// transform to cepstrum domain
Fourier.complexTransform( outBuf, fftLength, Fourier.INVERSE );
// fold cepstrum (make anticausal parts causal)
for( int i = 2, j = complexFFTsize - 2; i < fftLength; i += 2, j -= 2 ) {
outBuf[ i ] += outBuf[ j ]; // add conjugate left wing to right wing
outBuf[ i+1 ] -= outBuf[ j+1 ];
}
outBuf[ fftLength + 1 ] = -outBuf[ fftLength + 1 ];
// clear left wing
for( int i = fftLength + 2; i < complexFFTsize; i++ ) {
outBuf[ i ] = 0.0f;
}
// back to frequency domain
Fourier.complexTransform( outBuf, fftLength, Fourier.FORWARD );
// complex exponential : mag' = exp(real); phase' = imag
for( int i = 0; i <= fftLength; i += 2 ) {
outBuf[ i ] = (float) Math.exp( outBuf[ i ]);
}
Fourier.polar2Rect( outBuf, 0, outBuf, 0, fftLength + 2 );
}
// make sure we're in the time domain
if( impBuf[ 1 ][ 0 ] == 1.0f ) {
Fourier.realTransform( outBuf, fftLength, Fourier.INVERSE );
Util.rotate( outBuf, fftLength, impBuf[ 2 ], impLength.x ); // undo rotation
impBuf[ 1 ][ 0 ] = 0.0f;
}
progOff += outLength;
// .... progress ....
setProgression( (float) progOff / (float) progLen );
if( !threadRunning ) break topLevel;
// ---- normalize output ----
support = impLength.x;
for( int i = 0; i < outLength; i++ ) {
f1 = Math.abs( outBuf[ i ]);
if( f1 > maxAmp ) {
maxAmp = f1;
if( pr.bool[ PR_MINPHASE ]) support = i; // empiricially determine support from greatest elongation
}
}
if( pr.bool[ PR_MINPHASE ]) { // apply window once more to improve stopband atten
float[] win;
int winLen;
winLen = support >> 1;
win = Filter.createWindow( winLen, Filter.WIN_KAISER6 );
for( int i = winLen - 1, j = 0; j < win.length; i--, j++ ) {
outBuf[ i ] *= win[ j ];
}
for( int i = outLength - winLen, j = 0; i < outLength; i++, j++ ) {
outBuf[ i ] *= win[ j ];
}
}
if( pr.intg[ PR_GAINTYPE ] == GAIN_ABSOLUTE ) {
gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ampRef, null )).value;
} else {
gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP,
new Param( 1.0 / maxAmp, Param.ABS_AMP ), null )).value;
}
for( int i = 0; i < outLength; i++ ) {
outBuf[ i ] *= gain;
}
maxAmp *= gain;
// ---- write output ----
// add "support"
markers = new ArrayList<Marker>( 1 );
markers.add( new Marker( support, MARK_SUPPORT ));
outStream.setProperty( AudioFileDescr.KEY_MARKERS, markers );
outF = AudioFile.openAsWrite( outStream );
for( int framesWritten = 0; threadRunning && (framesWritten < outLength); ) {
int len = Math.min( 8192, outLength - framesWritten );
outF.writeFrames( outBufWrap, framesWritten, len );
framesWritten += len;
progOff += len;
// .... progress ....
setProgression( (float) progOff / (float) progLen );
}
// .... check running ....
if( !threadRunning ) break topLevel;
// ---- Finish ----
outF.close();
outF = null;
// outStream = null;
// .... check running ....
if( !threadRunning ) break topLevel;
// System.out.println( "progOff "+progOff+"; progLen "+progLen );
} // topLevel
catch( IOException e1 ) {
setError( e1 );
}
catch( OutOfMemoryError e2 ) {
impBuf = null;
outBuf = null;
outBufWrap = null;
outStream = null;
System.gc();
setError( new Exception( ERR_MEMORY ));
}
// ---- cleanup (topLevel) ----
if( outF != null ) {
outF.cleanUp();
}
}
// -------- VectorPanel.Client interface --------
public void requestUpdate(boolean hLog, boolean vLog)
{
int i, j, decimate = 1, dispLen, fftLength, outLength;
Point impLength;
float[] outBuf, magBuf;
float[][] impBuf;
float fMag, max = Float.NEGATIVE_INFINITY, min = Float.POSITIVE_INFINITY;
double decibelWeight = 20.0 / Constants.ln10;
double d2, d3, log, scale, weight, offset, fmin, fmax, fc = 1.0, f0, fRe, fIm;
VectorSpace space;
AudioFileDescr afd = new AudioFileDescr();
PathField ggOutput = (PathField) gui.getItemObj( GG_OUTPUTFILE );
CircuitPanel cp = (CircuitPanel) gui.getItemObj( GG_CIRCUIT );
if( ggOutput == null || cp == null ) return;
fillPropertyArray();
ggOutput.fillStream( afd );
afd.channels = 1;
impLength = calcLength( cp, afd );
outLength = impLength.x + impLength.y;
// outStream.length= outLength;
if( outLength <= 0 ) return;
for( fftLength = 2; fftLength < outLength; fftLength <<= 1 ) ;
if(hLog) { // oversampling in log.freq mode because lo freq are spread
for( ; fftLength < 8192; fftLength <<= 1, impLength = new Point( impLength.x * 2, impLength.y * 2 )) ;
} else { // fft size min. 4096, decimation so displayed vector is not greater than 4096
for( ; fftLength < 4096; fftLength <<= 1, impLength = new Point( impLength.x * 2, impLength.y * 2 )) ;
for( i = 4096; i < fftLength; i <<= 1, decimate++ ) ;
}
// outBuf = new float[ fftLength + 4 ]; // + 2 extra safety bound for log.freq interpolation!
outBuf = new float[ fftLength + 2 ];
impBuf = new float[ 3 ][];
impBuf[ 0 ] = outBuf;
impBuf[ 1 ] = new float[ 1 ];
impBuf[ 1 ][ 0 ]= 0.0f; // time domain
impBuf[ 2 ] = new float[ impLength.x ]; // rotate buffer
calcIR( cp, afd, impBuf, impLength, fftLength );
if( impBuf[ 1 ][ 0 ] != 1.0f ) {
Fourier.realTransform( outBuf, fftLength, Fourier.FORWARD );
}
// impBuf = null;
// System.err.println( "length = "+fftLength );
// Random r = new Random( System.currentTimeMillis() );
if(hLog) {
dispLen = 2049; // 4097;
magBuf = new float[ dispLen ];
fc = 1000.0; // gewuenschte centerfreq
fmin = 16.0; // gewuenschte min. freq
fmax = afd.rate/2; // oberste freq.
f0 = fc*fc / fmax;
log = Math.log( fmax/f0 );
offset = Math.log( fmin/f0 ) / log;
weight = (1.0 - offset) / (dispLen - 1);
scale = f0 / afd.rate * fftLength;
// System.err.println( "fmax = "+fmax+"; f0 = "+f0+"; log = "+log+"; offset = "+offset+"; weight = "+weight+"; fftSize = "+fftLength );
for( j = 0; j < dispLen; j++ ) {
d2 = Math.exp( (j * weight + offset) * log );
// System.err.println( "freq = "+d2*f0 );
d3 = d2 * scale;
// System.err.println( "bin = "+d3 );
i = ((int) d3) << 1;
d2 = d3 % 1.0;
// fRe = outBuf[ i ] * (1.0 - d2) + outBuf[ i+2 ] * d2;
// fIm = outBuf[ i+1 ] * (1.0 - d2) + outBuf[ i+3 ] * d2;
fRe = outBuf[ i ];
fIm = outBuf[ i+1 ];
d3 = Math.sqrt( fRe*fRe + fIm*fIm ) * (1.0 - d2);
if( i < fftLength ) {
fRe = outBuf[ i+2 ];
fIm = outBuf[ i+3 ];
fMag = (float) (d3 + Math.sqrt( fRe*fRe + fIm*fIm ) * d2);
} else {
fMag = (float) d3;
}
magBuf[ j ] = fMag;
if( fMag > max ) max = fMag;
if( fMag < min ) min = fMag;
}
} else {
dispLen = (fftLength >> decimate) + 1;
magBuf = new float[ dispLen ];
// System.err.println( "dispLen = "+dispLen+"; decimate = "+decimate+"; fftLength "+fftLength+"; 1 << decimate "+(1 << decimate) );
for( i = 0, j = 0, decimate = 1 << decimate; i <= fftLength; i += decimate, j++ ) {
fRe = outBuf[ i ];
fIm = outBuf[ i+1 ];
fMag = (float) Math.sqrt( fRe*fRe + fIm*fIm );
magBuf[ j ] = fMag;
if( fMag > max ) max = fMag;
if( fMag < min ) min = fMag;
}
fmin = 0.0;
fmax = afd.rate/2;
}
if(vLog) {
for( j = 0; j < dispLen; j++ ) {
magBuf[ j ] = (float) (decibelWeight * Math.log( Math.max( 1.0e-8, magBuf[ j ])));
}
min = (float) (decibelWeight * Math.log( Math.max( 1.0e-8, min )));
max = (float) (decibelWeight * Math.log( Math.max( 1.0e-8, max )));
//System.err.println( "min = "+min+"; max = "+max );
max = (float) (Math.ceil( max / 6.0 ) * 6.0);
min = (float) (Math.ceil( min / 6.0 ) * 6.0);
//System.err.println( "now min = "+min+"; max = "+max );
} else {
//System.err.println( "min = "+min+"; max = "+max );
max = (float) (Math.ceil( max * 10.0 ) / 10.0);
min = (float) (Math.floor( min * 10.0 ) / 10.0);
//System.err.println( "now min = "+min+"; max = "+max );
}
// outBuf = null;
if(hLog) {
space = VectorSpace.createLogLinSpace( fmin, fmax, fc, min, max, null, null, null, null );
} else {
space = VectorSpace.createLinSpace( fmin, fmax, min, max, null, null, null, null );
}
spectPane.setSpace( space );
spectPane.setVector( magBuf );
}
public String formatHText(double hValue, boolean hLog)
{
return( msgHertz.format( new Object[] {hValue}));
}
public String formatVText(double vValue, boolean vLog)
{
if(vLog) {
return( msgDecibel.format( new Object[] {vValue}));
} else {
return( msgPlain.format( new Object[] {vValue}));
}
}
// -------- private methods --------
/*
* Impulslaenge in Samples berechnen
*
* @return Point.x ist zeitlich negativer Anteil ("support"), Point.y positiver
*/
private Point calcLength( CircuitPanel cp, AudioFileDescr outStream )
{
Iterator iter = cp.getElements();
Point totalLength = new Point( 0, 0 );
Point len;
Object o;
if( iter.hasNext() ) {
o = iter.next();
if( o instanceof CircuitPanel ) {
totalLength = calcLength( (CircuitPanel) o, outStream ); // recurse
} else if( o instanceof FilterBox ) {
totalLength = ((FilterBox) o).calcLength( outStream, pr.intg[ PR_QUALITY ]);
} else {
assert false : o.getClass().getName();
}
}
while( iter.hasNext() ) {
o = iter.next();
if( o instanceof CircuitPanel ) {
len = calcLength( (CircuitPanel) o, outStream ); // recurse
} else if( o instanceof FilterBox ) {
len = ((FilterBox) o).calcLength( outStream, pr.intg[ PR_QUALITY ]);
} else {
assert false : o.getClass().getName();
len = null;
}
if( cp.getType() == CircuitPanel.TYPE_PARALLEL ) {
totalLength.x = Math.max( totalLength.x, len.x );
totalLength.y = Math.max( totalLength.y, len.y );
} else {
totalLength.x += len.x;
totalLength.y += Math.max( 0, len.y - 1 );
}
}
return totalLength;
}
/*
* IR berechnen
*
* @param buf1 erste Dimension die Daten, zweite Dimension 1. Feld = 0 fuer timedomain, 1 fuer fft
* dritte Dimension Rotations-Puffer (totalLength.x gross)
* buf1[ 0 ].length must >= fftLength + 2 !!
*/
private void calcIR( CircuitPanel cp, AudioFileDescr outStream, float[][] buf1, Point totalLength, int fftLength )
{
Iterator iter = cp.getElements();
Object o;
float[][] buf2 = null;
float[] convBuf1, convBuf2;
int i;
if( iter.hasNext() ) {
o = iter.next();
if( o instanceof CircuitPanel ) {
calcIR( (CircuitPanel) o, outStream, buf1, totalLength, fftLength ); // recurse
} else if( o instanceof FilterBox ) {
((FilterBox) o).calcIR( outStream, pr.intg[ PR_QUALITY ], pr.intg[ PR_WINDOW ], buf1[ 0 ], totalLength );
} else {
assert false : o.getClass().getName();
}
}
while( iter.hasNext() ) {
o = iter.next();
if( buf2 == null ) {
buf2 = new float[ 3 ][];
buf2[ 0 ] = new float[ buf1[ 0 ].length ];
buf2[ 1 ] = new float[ 1 ];
buf2[ 2 ] = buf1[ 2 ];
}
buf2[ 1 ][ 0 ] = 0.0f; // time domain
if( o instanceof CircuitPanel ) {
calcIR( (CircuitPanel) o, outStream, buf2, totalLength, fftLength ); // recurse
} else if( o instanceof FilterBox ) {
((FilterBox) o).calcIR( outStream, pr.intg[ PR_QUALITY ], pr.intg[ PR_WINDOW ], buf2[ 0 ], totalLength );
} else {
assert false : o.getClass().getName();
}
convBuf1 = buf1[ 0 ];
convBuf2 = buf2[ 0 ];
if( buf1[ 1 ][ 0 ] == buf2[ 1 ][ 0 ] ) { // same domain
if( (cp.getType() == CircuitPanel.TYPE_SERIAL) && (buf1[ 1 ][ 0 ] == 0.0f) ) { // need 2 ffts
Util.rotate( convBuf1, fftLength, buf1[ 2 ], -totalLength.x );
Fourier.realTransform( convBuf1, fftLength, Fourier.FORWARD );
Util.rotate( convBuf2, fftLength, buf1[ 2 ], -totalLength.x );
Fourier.realTransform( convBuf2, fftLength, Fourier.FORWARD );
buf1[ 1 ][ 0 ] = 1.0f; // freq domain
buf2[ 1 ][ 0 ] = 1.0f;
}
} else {
if( buf1[ 1 ][ 0 ] == 0.0f ) {
Util.rotate( convBuf1, fftLength, buf1[ 2 ], -totalLength.x );
Fourier.realTransform( convBuf1, fftLength, Fourier.FORWARD );
buf1[ 1 ][ 0 ] = 1.0f; // freq domain
} else {
Util.rotate( convBuf2, fftLength, buf1[ 2 ], -totalLength.x );
Fourier.realTransform( convBuf2, fftLength, Fourier.FORWARD );
buf2[ 1 ][ 0 ] = 1.0f; // freq domain
}
}
if( cp.getType() == CircuitPanel.TYPE_PARALLEL ) {
for( i = fftLength + 1; i >= 0; i-- ) {
convBuf1[ i ] += convBuf2[ i ]; // parallel adds up
}
} else {
for( i = fftLength + 1; i >= 0; i-- ) {
convBuf1[ i ] *= convBuf2[ i ]; // serial is convolution
}
}
}
}
protected void reflectPropertyChanges()
{
super.reflectPropertyChanges();
Component c;
FilterBox flt = currentFlt;
boolean b = (flt != null);
boolean b2 = true;
boolean b3 = true;
c = gui.getItemObj( GG_FILTERTYPE );
if( c != null ) {
c.setEnabled( b );
}
c = gui.getItemObj( GG_SIGN );
if( c != null ) {
c.setEnabled( b );
}
c = gui.getItemObj( GG_FILTERGAIN );
if( c != null ) {
c.setEnabled( b );
}
c = gui.getItemObj( GG_DELAY );
if( c != null ) {
c.setEnabled( b );
}
if( b ) {
switch( flt.filterType ) {
case FilterBox.FLT_ALLPASS:
b3 = false;
// THRU
case FilterBox.FLT_LOWPASS:
case FilterBox.FLT_HIGHPASS:
b2 = false; // !!
break;
}
}
c = gui.getItemObj( GG_CUTOFF );
if( c != null ) {
c.setEnabled( b && b3 );
}
c = gui.getItemObj( GG_ROLLOFF );
if( c != null ) {
c.setEnabled( b && b3 );
}
c = gui.getItemObj( GG_BANDWIDTH );
if( c != null ) {
c.setEnabled( b && b2 );
}
c = gui.getItemObj( GG_OVERTONES );
if( c != null ) {
c.setEnabled( b && b2 );
if( b ) {
b2 = (b2 && flt.overtones); // !!
}
}
c = gui.getItemObj( GG_OTLIMIT );
if( c != null ) {
c.setEnabled( b && b2 );
}
c = gui.getItemObj( GG_OTSPACING );
if( c != null ) {
c.setEnabled( b && b2 );
}
}
}