/*
* LaguerreDlg.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.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.util.Constants;
import de.sciss.fscape.util.Envelope;
import de.sciss.fscape.util.Filter;
import de.sciss.fscape.util.Param;
import de.sciss.fscape.util.Util;
import de.sciss.gui.PathEvent;
import de.sciss.gui.PathListener;
import de.sciss.io.AudioFile;
import de.sciss.io.AudioFileDescr;
import de.sciss.io.IOUtil;
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;
/**
* Frequency Warping processing module via Short-Time Laguerre Transform
* based on papers by G. Evangelista. This is totally slow
* and also buggy causing comb filter effects for wrong window
* ratios. However kind of works when splitting the sound file
* into two frequency bands which are warped independently with
* small windows for high frequencies and large windows for low
* frequencies. Tends to sound chirpy.
*/
public class LaguerreDlg
extends ModulePanel {
// -------- private variables --------
// Properties (defaults)
private static final int PR_INPUTFILE = 0; // pr.text
private static final int PR_OUTPUTFILE = 1;
private static final int PR_OUTPUTTYPE = 0; // pr.intg
private static final int PR_OUTPUTRES = 1;
private static final int PR_GAINTYPE = 2;
private static final int PR_FRAMESIZE = 3;
private static final int PR_OVERLAP = 4;
private static final int PR_GAIN = 0; // pr.para
private static final int PR_WARP = 1;
private static final int PR_WARPMODDEPTH = 2;
private static final int PR_INFREQ = 3;
private static final int PR_OUTFREQ = 4;
private static final int PR_WARPMOD = 0; // pr.bool
private static final int PR_WARPENV = 0; // pr.envl
private static final String PRN_INPUTFILE = "InputFile";
private static final String PRN_OUTPUTFILE = "OutputFile";
private static final String PRN_OUTPUTTYPE = "OutputType";
private static final String PRN_OUTPUTRES = "OutputReso";
private static final String PRN_WARP = "Warp";
private static final String PRN_WARPMODDEPTH = "WarpModDepth";
private static final String PRN_INFREQ = "InFreq";
private static final String PRN_OUTFREQ = "OutFreq";
private static final String PRN_WARPMOD = "WarpMod";
private static final String PRN_WARPENV = "WarpEnv";
private static final String PRN_FRAMESIZE = "FrameSize";
private static final String PRN_OVERLAP = "Overlap";
private static final String prText[] = { "", "" };
private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE };
private static final int prIntg[] = { 0, 0, 0, 4, 2 };
private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_GAINTYPE, PRN_FRAMESIZE,
PRN_OVERLAP };
private static final boolean prBool[] = { false };
private static final String prBoolName[] = { PRN_WARPMOD };
private static final Param prPara[] = { null, null, null, null, null };
private static final String prParaName[] = { PRN_GAIN, PRN_WARP, PRN_WARPMODDEPTH, PRN_INFREQ, PRN_OUTFREQ };
private static final Envelope prEnvl[] = { null };
private static final String prEnvlName[] = { PRN_WARPENV };
private static final int GG_INPUTFILE = GG_OFF_PATHFIELD + PR_INPUTFILE;
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_GAINTYPE = GG_OFF_CHOICE + PR_GAINTYPE;
private static final int GG_FRAMESIZE = GG_OFF_CHOICE + PR_FRAMESIZE;
private static final int GG_OVERLAP = GG_OFF_CHOICE + PR_OVERLAP;
private static final int GG_GAIN = GG_OFF_PARAMFIELD + PR_GAIN;
private static final int GG_WARP = GG_OFF_PARAMFIELD + PR_WARP;
private static final int GG_WARPMODDEPTH = GG_OFF_PARAMFIELD + PR_WARPMODDEPTH;
private static final int GG_INFREQ = GG_OFF_PARAMFIELD + PR_INFREQ;
private static final int GG_OUTFREQ = GG_OFF_PARAMFIELD + PR_OUTFREQ;
private static final int GG_WARPMOD = GG_OFF_CHECKBOX + PR_WARPMOD;
private static final int GG_WARPENV = GG_OFF_ENVICON + PR_WARPENV;
private static PropertyArray static_pr = null;
private static Presets static_presets = null;
private float inRate = 0.0f; // sample rate input file
// -------- public methods --------
public LaguerreDlg() {
super("Laguerre Warping");
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.para[ PR_WARP ] = new Param( -10.0, Param.FACTOR );
static_pr.para[ PR_WARPMODDEPTH ] = new Param( 20.0, Param.OFFSET_AMP );
static_pr.para[ PR_INFREQ ] = new Param( 1000.0, Param.ABS_HZ );
static_pr.para[ PR_OUTFREQ ] = new Param( 1000.0, Param.ABS_HZ );
static_pr.paraName = prParaName;
static_pr.envl = prEnvl;
static_pr.envl[ PR_WARPENV ] = Envelope.createBasicEnvelope( Envelope.BASIC_TIME );
static_pr.envlName = prEnvlName;
// 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;
PathField ggInputFile, ggOutputFile;
PathField[] ggParent1;
ParamField ggWarp, ggWarpModDepth, ggInFreq, ggOutFreq;
JCheckBox ggWarpMod;
EnvIcon ggWarpEnv;
Component[] ggGain;
JComboBox ggFrameSize, ggOverlap;
gui = new GUISupport();
con = gui.getGridBagConstraints();
con.insets = new Insets( 1, 2, 1, 2 );
ParamListener paramL = new ParamListener() {
public void paramChanged( ParamEvent e )
{
int ID = gui.getItemID( e );
switch( ID ) {
case GG_WARP:
case GG_INFREQ:
pr.para[ ID - GG_OFF_PARAMFIELD ] = ((ParamField) e.getSource()).getParam();
recalcOutFreq();
break;
case GG_OUTFREQ:
pr.para[ ID - GG_OFF_PARAMFIELD ] = ((ParamField) e.getSource()).getParam();
recalcWarpAmount();
break;
}
}
};
ItemListener il = new ItemListener() {
public void itemStateChanged( ItemEvent e )
{
int ID = gui.getItemID( e );
switch( ID ) {
case GG_WARPMOD:
pr.bool[ ID - GG_OFF_CHECKBOX ] = ((JCheckBox) e.getSource()).isSelected();
reflectPropertyChanges();
break;
}
}
};
PathListener pathL = new PathListener() {
public void pathChanged( PathEvent e )
{
int ID = gui.getItemID( e );
switch( ID ) {
case GG_INPUTFILE:
setInput( ((PathField) e.getSource()).getPath().getPath() );
break;
}
}
};
// -------- I/O-Gadgets --------
con.fill = GridBagConstraints.BOTH;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addLabel( new GroupLabel( "Waveform I/O", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE ));
ggInputFile = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD,
"Select input file" );
ggInputFile.handleTypes( GenericFile.TYPES_SOUND );
con.gridwidth = 1;
con.weightx = 0.1;
gui.addLabel( new JLabel( "Input file", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
con.weightx = 0.9;
gui.addPathField( ggInputFile, GG_INPUTFILE, pathL );
ggOutputFile = new PathField( PathField.TYPE_OUTPUTFILE + PathField.TYPE_FORMATFIELD +
PathField.TYPE_RESFIELD, "Select output file" );
ggOutputFile.handleTypes( GenericFile.TYPES_SOUND );
con.gridwidth = 1;
con.weightx = 0.1;
gui.addLabel( new JLabel( "Output file", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
con.weightx = 0.9;
gui.addPathField( ggOutputFile, GG_OUTPUTFILE, pathL );
gui.registerGadget( ggOutputFile.getTypeGadget(), GG_OUTPUTTYPE );
gui.registerGadget( ggOutputFile.getResGadget(), GG_OUTPUTRES );
ggParent1 = new PathField[ 1 ];
ggParent1[ 0 ] = ggInputFile;
ggOutputFile.deriveFrom( ggParent1, "$D0$F0Wrp$E" );
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, paramL );
con.weightx = 0.5;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addChoice( (JComboBox) ggGain[ 1 ], GG_GAINTYPE, il );
// -------- Settings-Gadgets --------
gui.addLabel( new GroupLabel( "Warp settings", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE ));
ggWarp = new ParamField( Constants.spaces[ Constants.modSpace ]); // XXX
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Warp amount", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggWarp, GG_WARP, paramL );
ggWarpModDepth = new ParamField( Constants.spaces[ Constants.offsetAmpSpace ]); // XXX
ggWarpModDepth.setReference( ggWarp );
ggWarpMod = new JCheckBox();
con.weightx = 0.1;
gui.addCheckbox( ggWarpMod, GG_WARPMOD, il );
con.weightx = 0.4;
gui.addParamField( ggWarpModDepth, GG_WARPMODDEPTH, paramL );
ggWarpEnv = new EnvIcon( getComponent() );
con.weightx = 0.1;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addGadget( ggWarpEnv, GG_WARPENV );
ggInFreq = new ParamField( Constants.spaces[ Constants.absHzSpace ]);
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Input freq.", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggInFreq, GG_INFREQ, paramL );
ggOutFreq = new ParamField( Constants.spaces[ Constants.absHzSpace ]);
con.weightx = 0.1;
gui.addLabel( new JLabel( "\u2192 Output freq.", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggOutFreq, GG_OUTFREQ, paramL );
ggFrameSize = new JComboBox();
for( int i = 32; i <= 32768; i <<= 1 ) {
ggFrameSize.addItem( String.valueOf( i ));
}
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Frame size [smp]", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addChoice( ggFrameSize, GG_FRAMESIZE, il );
ggOverlap = new JComboBox();
for( int i = 1; i <= 16; i++ ) {
ggOverlap.addItem( i + "x" );
}
con.weightx = 0.1;
gui.addLabel( new JLabel( "Overlap", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addChoice( ggOverlap, GG_OVERLAP, il );
initGUI( this, FLAGS_PRESETS | FLAGS_PROGBAR, gui );
}
public void fillGUI() {
super.fillGUI();
super.fillGUI(gui);
}
public void fillPropertyArray() {
super.fillPropertyArray();
super.fillPropertyArray(gui);
}
// -------- Processor Interface --------
protected void process() {
int i, j, len, ch, chunkLength;
long progOff, progLen;
float f1;
// io
AudioFile inF = null;
AudioFile outF = null;
AudioFileDescr inStream;
AudioFileDescr outStream;
FloatFile[] floatF = null;
File tempFile[] = null;
// buffers
float[][] inBuf, outBuf;
float[] win;
float[] convBuf1, convBuf2;
float[] tempFlt;
long inLength;
int inChanNum, inputStep, outputStep, winSize;
int transLen, skip, inputLen, outputLen, fltLen;
long framesRead, framesWritten;
float warp, a1, b0, b1, x0, x1, y0, y1, b0init;
Param ampRef = new Param(1.0, Param.ABS_AMP); // transform reference
Param peakGain;
float gain = 1.0f; // gain abs amp
float maxAmp = 0.0f;
PathField ggOutput;
topLevel:
try {
// ---- open input, output ----
// input
inF = AudioFile.openAsRead(new File(pr.text[PR_INPUTFILE]));
inStream = inF.getDescr();
inChanNum = inStream.channels;
inLength = inStream.length;
// this helps to prevent errors from empty files!
if ((inLength * inChanNum) < 1) throw new EOFException(ERR_EMPTY);
// .... check running ....
if (!threadRunning) break topLevel;
// output
ggOutput = (PathField) gui.getItemObj(GG_OUTPUTFILE);
if (ggOutput == null) throw new IOException(ERR_MISSINGPROP);
outStream = new AudioFileDescr(inStream);
ggOutput.fillStream(outStream);
outF = AudioFile.openAsWrite(outStream);
// .... check running ....
if (!threadRunning) break topLevel;
// ---- parameter init ----
warp = Math.max(-0.98f, Math.min(0.98f, (float) (pr.para[PR_WARP].value / 100))); // DAFx2000 'b'
f1 = (1.0f - warp) / (1.0f + warp); // DAFx2000 (25)
winSize = 32 << pr.intg[PR_FRAMESIZE]; // DAFx2000 'N'
j = winSize >> 1;
transLen = (int) (f1 * winSize + 0.5f); // DAFx2000 'P' (26)
i = pr.intg[PR_OVERLAP] + 1;
while (((float) transLen / (float) i) > j) i++;
inputStep = (int) (((float) transLen / (float) i) + 0.5f); // DAFx2000 'L'
fltLen = Math.max(winSize, transLen);
win = Filter.createFullWindow(winSize, Filter.WIN_HANNING); // DAFx2000 (27)
outputStep = inputStep;
b0init = (float) Math.sqrt(1.0f - warp * warp);
progOff = 0;
progLen = inLength * (2 + inChanNum); // + winSize;
tempFlt = new float[fltLen];
inputLen = winSize + inputStep;
inBuf = new float[inChanNum][inputLen];
outputLen = transLen + outputStep;
outBuf = new float[inChanNum][outputLen];
// normalization requires temp files
if (pr.intg[PR_GAINTYPE] == GAIN_UNITY) {
tempFile = new File [inChanNum];
floatF = new FloatFile[inChanNum];
for (ch = 0; ch < inChanNum; ch++) { // first zero them because an exception might be thrown
tempFile[ch] = null;
floatF [ch] = null;
}
for (ch = 0; ch < inChanNum; ch++) {
tempFile[ch] = IOUtil.createTempFile();
floatF [ch] = new FloatFile(tempFile[ch], GenericFile.MODE_OUTPUT);
}
progLen += inLength;
} else {
gain = (float) ((Param.transform(pr.para[PR_GAIN], Param.ABS_AMP, ampRef, null)).value);
}
// .... check running ....
if (!threadRunning) break topLevel;
// ----==================== the real stuff ====================----
framesRead = 0L;
framesWritten = 0L;
skip = 0;
while (threadRunning && (framesWritten < inLength)) {
chunkLength = (int) Math.min(inputLen, inLength - framesRead + skip);
// ---- read input chunk ----
len = Math.max(0, chunkLength - skip);
inF.readFrames(inBuf, skip, len);
framesRead += len;
progOff += len;
// .... progress ....
setProgression((float) progOff / (float) progLen);
// .... check running ....
if (!threadRunning) break topLevel;
// zero padding
if (chunkLength < inputLen) {
for (ch = 0; ch < inChanNum; ch++) {
convBuf1 = inBuf[ch];
for (i = chunkLength; i < convBuf1.length; i++) {
convBuf1[i] = 0.0f;
}
}
}
for (ch = 0; threadRunning && (ch < inChanNum); ch++) {
convBuf1 = inBuf [ch];
convBuf2 = outBuf[ch];
for (i = 0, j = fltLen; i < winSize; i++) {
tempFlt[--j] = convBuf1[i] * win[i];
}
while (j > 0) {
tempFlt[--j] = 0.0f;
}
a1 = -warp; // initial allpass
b0 = b0init;
b1 = 0.0f;
for (j = 0; j < transLen; j++) {
x1 = 0.0f;
y1 = 0.0f;
for (i = 0; i < fltLen; i++) { // DAFx2000 (2 resp. 3)
x0 = tempFlt[i];
y0 = b0*x0 + b1*x1 - a1*y1;
tempFlt[i] = y0; // (work with double precision while computing cascades)
y1 = y0;
x1 = x0;
}
a1 = -warp; // cascaded allpass filters
b0 = -warp;
b1 = 1.0f;
convBuf2[j] += y1;
}
// .... progress ....
progOff += chunkLength - skip;
setProgression((float) progOff / (float) progLen);
} // for channels
// .... check running ....
if (!threadRunning) break topLevel;
chunkLength = (int) Math.min(outputStep, inLength - framesWritten);
// ---- write output chunk ----
if (floatF != null) {
for (ch = 0; ch < inChanNum; ch++) {
floatF[ch].writeFloats(outBuf[ch], 0, chunkLength);
}
progOff += chunkLength;
framesWritten += chunkLength;
// .... progress ....
setProgression((float) progOff / (float) progLen);
} else {
for (ch = 0; ch < inChanNum; ch++) {
Util.mult(outBuf[ch], 0, chunkLength, gain);
}
outF.writeFrames(outBuf, 0, chunkLength);
progOff += chunkLength;
framesWritten += chunkLength;
// .... progress ....
setProgression((float) progOff / (float) progLen);
}
// .... check running ....
if (!threadRunning) break topLevel;
// check max amp
for (ch = 0; ch < inChanNum; ch++) {
convBuf1 = outBuf[ch];
for (i = 0; i < chunkLength; i++) {
f1 = Math.abs(convBuf1[i]);
if (f1 > maxAmp) {
maxAmp = f1;
}
}
}
// overlaps
skip = winSize;
for (ch = 0; ch < inChanNum; ch++) {
System.arraycopy(inBuf[ch], inputStep, inBuf[ch], 0, winSize);
convBuf1 = outBuf[ch];
System.arraycopy(convBuf1, outputStep, convBuf1, 0, transLen);
for (i = transLen; i < outputLen; ) {
convBuf1[i++] = 0.0f;
}
}
} // until framesWritten == outLength
// .... check running ....
if (!threadRunning) break topLevel;
// ----==================== normalize output ====================----
if (pr.intg[PR_GAINTYPE] == GAIN_UNITY) {
peakGain = new Param((double) maxAmp, Param.ABS_AMP);
gain = (float) (Param.transform(pr.para[PR_GAIN], Param.ABS_AMP,
new Param(1.0 / peakGain.value, peakGain.unit), null)).value;
normalizeAudioFile(floatF, outF, inBuf, gain, 1.0f);
maxAmp *= gain;
for (ch = 0; ch < inChanNum; ch++) {
floatF[ch].cleanUp();
floatF[ch] = null;
tempFile[ch].delete();
tempFile[ch] = null;
}
}
// .... check running ....
if (!threadRunning) break topLevel;
// ---- Finish ----
outF.close();
outF = null;
outStream = null;
inF.close();
inF = null;
inStream = null;
inBuf = null;
// inform about clipping/ low level
handleClipping(maxAmp);
} catch (IOException e1) {
setError(e1);
} catch (OutOfMemoryError e2) {
inStream = null;
outStream = null;
inBuf = null;
convBuf1 = null;
convBuf2 = null;
System.gc();
setError(new Exception(ERR_MEMORY));
}
// ---- cleanup (topLevel) ----
if (inF != null) {
inF.cleanUp();
inF = null;
}
if (outF != null) {
outF.cleanUp();
outF = null;
}
if (floatF != null) {
for (ch = 0; ch < floatF.length; ch++) {
if (floatF[ch] != null) {
floatF[ch].cleanUp();
floatF[ch] = null;
}
if (tempFile[ch] != null) {
tempFile[ch].delete();
tempFile[ch] = null;
}
}
}
} // process()
protected void reflectPropertyChanges() {
super.reflectPropertyChanges();
Component c;
c = gui.getItemObj(GG_WARPMODDEPTH);
if (c != null) {
c.setEnabled(pr.bool[PR_WARPMOD]);
}
c = gui.getItemObj(GG_WARPENV);
if (c != null) {
c.setEnabled(pr.bool[PR_WARPMOD]);
}
c = gui.getItemObj(GG_OUTFREQ);
if (c != null) {
c.setEnabled(inRate != 0f);
}
}
/**
* Set new input file
*/
protected void setInput(String fname) {
AudioFile f = null;
AudioFileDescr stream = null;
// ---- read header ----
try {
f = AudioFile.openAsRead(new File(fname));
stream = f.getDescr();
f.close();
inRate = (float) stream.rate;
recalcOutFreq();
} catch (IOException e1) {
inRate = 0f;
}
reflectPropertyChanges();
}
protected void recalcOutFreq() {
if (inRate == 0f) return;
double omegaIn, omegaOut, warp;
ParamField ggOutFreq;
omegaIn = pr.para[PR_INFREQ].value / inRate * Constants.PI2;
warp = Math.max(-0.98, Math.min(0.98, pr.para[PR_WARP].value / 100)); // DAFx2000 'b'
omegaOut = omegaIn + 2 * Math.atan2(warp * Math.sin(omegaIn), 1.0 - warp * Math.cos(omegaIn));
ggOutFreq = (ParamField) gui.getItemObj(GG_OUTFREQ);
if (ggOutFreq != null) {
ggOutFreq.setParam(new Param(omegaOut / Constants.PI2 * inRate, Param.ABS_HZ));
}
}
protected void recalcWarpAmount() {
if (inRate == 0f) return;
double omegaIn, omegaOut, warp, d1;
ParamField ggWarp;
omegaIn = pr.para[PR_INFREQ ].value / inRate * Constants.PI2;
omegaOut = pr.para[PR_OUTFREQ].value / inRate * Constants.PI2;
d1 = Math.tan((omegaOut - omegaIn) / 2);
warp = Math.max(-0.98, Math.min(0.98, d1 / (Math.sin(omegaIn) + Math.cos(omegaIn) * d1))); // DAFx2000 'b'
ggWarp = (ParamField) gui.getItemObj(GG_WARP);
if (ggWarp != null) {
ggWarp.setParam(new Param(warp * 100, Param.FACTOR));
}
}
}