/*
* RotationDlg.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:
* 05-Nov-04 bugfix
* 12-Mar-05 added 'subtract dry signal' + output gain
*/
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.Param;
import de.sciss.fscape.util.ParamSpace;
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;
/**
* Processing module that tracks zero crossings and
* "rotates" or "flips" wave sections, hence producing
* nice distortions.
*
* @todo pass two needs I/O speed up by increasing
* inBuf.length to 8k and reading multiple
* chunks at once
*/
public class RotationDlg
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_MODE = 2;
private static final int PR_GAINTYPE = 3;
private static final int PR_REPEATS = 0; // pr.para
private static final int PR_GAIN = 1;
private static final int PR_SUBDRY = 0; // pr.bool
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_MODE = "Mode";
private static final String PRN_REPEATS = "Repeats";
private static final String PRN_SUBDRY = "SubDry";
private static final int MODE_ROTATION = 0;
private static final int MODE_REPEAT = 1;
private static final String prText[] = { "", "" };
private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE };
private static final int prIntg[] = { 0, 0, MODE_ROTATION, GAIN_UNITY };
private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_MODE, PRN_GAINTYPE };
private static final Param prPara[] = { new Param( 2.0, Param.NONE ),
null };
private static final String prParaName[] = { PRN_REPEATS, PRN_GAIN };
private static final boolean prBool[] = { false };
private static final String prBoolName[] = { PRN_SUBDRY };
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_MODE = GG_OFF_CHOICE + PR_MODE;
private static final int GG_GAINTYPE = GG_OFF_CHOICE + PR_GAINTYPE;
private static final int GG_REPEATS = GG_OFF_PARAMFIELD + PR_REPEATS;
private static final int GG_GAIN = GG_OFF_PARAMFIELD + PR_GAIN;
private static final int GG_SUBDRY = GG_OFF_CHECKBOX + PR_SUBDRY;
private static PropertyArray static_pr = null;
private static Presets static_presets = null;
// -------- public methods --------
/**
* !! setVisible() bleibt dem Aufrufer ueberlassen
*/
public RotationDlg()
{
super( "Rotation" );
init2();
}
protected void buildGUI()
{
// einmalig PropertyArray initialisieren
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, 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;
JComboBox ggMode;
JCheckBox ggSubDry;
ParamField ggRepeats;
PathField[] ggInputs;
Component[] ggGain;
gui = new GUISupport();
con = gui.getGridBagConstraints();
con.insets = new Insets( 1, 2, 1, 2 );
ItemListener il = new ItemListener() {
public void itemStateChanged( ItemEvent e )
{
int ID = gui.getItemID( e );
switch( ID ) {
case GG_MODE:
pr.intg[ ID - GG_OFF_CHOICE ] = ((JComboBox) e.getSource()).getSelectedIndex();
reflectPropertyChanges();
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, null );
ggOutputFile = new PathField( PathField.TYPE_OUTPUTFILE + PathField.TYPE_FORMATFIELD +
PathField.TYPE_RESFIELD, "Select output file" );
ggOutputFile.handleTypes( GenericFile.TYPES_SOUND );
ggInputs = new PathField[ 1 ];
ggInputs[ 0 ] = ggInputFile;
// ggInputs[ 1 ] = ggModFile;
ggOutputFile.deriveFrom( ggInputs, "$D0$F0Rot$E" );
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, null );
gui.registerGadget( ggOutputFile.getTypeGadget(), GG_OUTPUTTYPE );
gui.registerGadget( ggOutputFile.getResGadget(), GG_OUTPUTRES );
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, null );
con.weightx = 0.5;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addChoice( (JComboBox) ggGain[ 1 ], GG_GAINTYPE, il );
// -------- Settings-Gadgets --------
gui.addLabel( new GroupLabel( "Settings", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE ));
ggMode = new JComboBox();
ggMode.addItem( "Rotate" );
ggMode.addItem( "Repeat" );
con.gridwidth = 1;
con.weightx = 0.1;
gui.addLabel( new JLabel( "Mode", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addChoice( ggMode, GG_MODE, il );
ggRepeats = new ParamField( new ParamSpace( 2.0, 10000.0, 1.0, Param.NONE ));
con.weightx = 0.1;
gui.addLabel( new JLabel( "Repeats", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggRepeats, GG_REPEATS, null );
ggSubDry = new JCheckBox( "Subtract dry signal" );
con.weightx = 0.4;
// con.gridwidth = 1;
gui.addCheckbox( ggSubDry, GG_SUBDRY, il );
initGUI( this, FLAGS_PRESETS | FLAGS_PROGBAR, gui );
}
/**
* Transfer values from prop-array to GUI
*/
public void fillGUI()
{
super.fillGUI();
super.fillGUI( gui );
}
/**
* Transfer values from GUI to prop-array
*/
public void fillPropertyArray()
{
super.fillPropertyArray();
super.fillPropertyArray( gui );
}
// -------- Processor Interface --------
protected void process()
{
int len;
long progOff, progLen;
float f1, f2, f3, f4;
// io
AudioFile inF = null;
AudioFile outF = null;
AudioFileDescr inStream = null;
AudioFileDescr outStream = null;
FloatFile zcFloatFile = null;
File zcTempFile = null;
float[][] inBuf = null;
float[] convBuf1;
// Smp Init
int inLength, inChanNum, zcLength;
int framesRead, framesWritten;
int inBufSize, zcRead;
float oldGrad, newGrad;
int[] zcBuf;
int loc, lastLoc, maxDist;
float[] lastSample, steigung;
PathField ggOutput;
boolean rotate = pr.intg[ PR_MODE ] == MODE_ROTATION;
boolean subDry = rotate && pr.bool[PR_SUBDRY];
int numRepeats = rotate ? 1 : (int) (pr.para[ PR_REPEATS ].value + 0.5);
float gain = 1.0f; // gain abs amp
float maxAmp = 0.0f;
FloatFile[] floatF = null;
File tempFile[] = null;
Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz
int distance;
topLevel: try {
// ---- open input, output; init ----
// input
inF = AudioFile.openAsRead( new File( pr.text[ PR_INPUTFILE ]));
inStream = inF.getDescr();
inChanNum = inStream.channels;
inLength = (int) 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;
progOff = 0;
progLen = (long) inLength * 2 + (long) inLength * numRepeats;
// 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;
inBufSize = 8192;
inBuf = new float[ inChanNum ][ inBufSize ];
zcBuf = new int[ 1 ]; // next dist
zcTempFile = IOUtil.createTempFile();
zcFloatFile = new FloatFile( zcTempFile, GenericFile.MODE_OUTPUT );
// normalization requires temp files
if( pr.intg[ PR_GAINTYPE ] == GAIN_UNITY ) {
tempFile = new File[ inChanNum ];
floatF = new FloatFile[ inChanNum ];
for( int 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;
// ----==================== finding min/max's ====================----
framesRead = 0;
newGrad = Float.NaN; // 0.0f;
maxDist = 0;
loc = 0;
while( threadRunning && (framesRead < inLength) ) {
for( int ch = 0; ch < inChanNum; ch++ ) {
inBuf[ ch ][ 0 ] = inBuf[ ch ][ inBufSize - 1 ];
}
// ==================== read input chunk ====================
len = Math.min( inLength - framesRead, inBufSize - 1 );
inF.readFrames( inBuf, 1, len );
progOff += len;
// .... progress ....
setProgression( (float) progOff / (float) progLen );
// .... check running ....
if( !threadRunning ) break topLevel;
// ---- Rotation ----------------------------------------------------------------------
convBuf1 = inBuf[ 0 ];
f2 = convBuf1[ 0 ];
for( int i = 1; i <= len; i++ ) {
f1 = f2;
f2 = convBuf1[ i ];
oldGrad = newGrad;
newGrad = f2 - f1;
if( ((oldGrad <= 0f) && (newGrad > 0f) ) || ((oldGrad > 0f) && (newGrad <= 0f)) ) { // local min or max
lastLoc = loc;
loc = framesRead + i - 1;
distance= loc - lastLoc;
if( distance > maxDist ) {
maxDist = distance;
}
zcBuf[ 0 ] = distance;
zcFloatFile.writeInts( zcBuf );
// if( j == 0 ) {
// System.err.println( "j = 0! framesRead = "+framesRead+"; i = "+i+"; lastLoc = "+lastLoc+ "; loc = "+loc );
// }
}
}
convBuf1[ 0 ] = convBuf1[ len ];
framesRead += len;
}
// .... check running ....
if( !threadRunning ) break topLevel;
// zero crossing file erhaelt einen terminierenden eintrag
lastLoc = loc;
loc = inLength;
distance = loc - lastLoc;
if( distance > maxDist ) {
maxDist = distance;
}
zcBuf[ 0 ] = distance;
zcFloatFile.writeInts( zcBuf );
if( floatF == null ) indicateOutputWrite();
// ============= PASS TWO ================
inBufSize = Math.max( 8192, maxDist * numRepeats ); // min. 8192 otherwise normalize is slow
inBuf = new float[ inChanNum ][ inBufSize ];
//System.err.println( "maxDist : "+maxDist );
framesRead = 0;
zcRead = 0;
framesWritten = 0;
inF.seekFrame( 0 );
zcFloatFile.seekFloat( 0 );
zcLength = (int) zcFloatFile.getSize();
lastSample = new float[ inChanNum ];
steigung = new float[ inChanNum ];
while( threadRunning && (zcRead < zcLength) ) {
zcFloatFile.readInts( zcBuf );
zcRead++;
len = zcBuf[0];
if( !rotate && zcRead < zcLength ) { // repeats uses full cycle of course
zcFloatFile.readInts( zcBuf );
zcRead++;
len += zcBuf[0];
}
inF.readFrames( inBuf, 0, len );
framesRead += len;
progOff += len;
// .... progress ....
setProgression( (float) progOff / (float) progLen );
// .... check running ....
if( !threadRunning ) break topLevel;
if( rotate ) { // rotation
for( int ch = 0; ch < inChanNum; ch++ ) {
f1 = lastSample[ch];
convBuf1= inBuf[ch];
f2 = convBuf1[ len - 1 ];
f3 = f1 + f2;
if( subDry ) {
for( int i = 0, j = len - 1; i <= j; i++, j-- ) {
// f4 = f3 - convBuf1[i];
// convBuf1[i] = f3 - convBuf1[j] - convBuf1[i];
// convBuf1[j] = f4 - convBuf1[j];
f4 = f3 - convBuf1[i] - convBuf1[j];
convBuf1[i] = f4;
convBuf1[j] = f4;
}
} else {
for( int i = 0, j = len - 1; i <= j; i++, j-- ) {
f4 = f3 - convBuf1[i];
convBuf1[i] = f3 - convBuf1[j];
convBuf1[j] = f4;
}
}
lastSample[ch] = f2;
}
} else { // repetition
for( int ch = 0; ch < inChanNum; ch++ ) {
f2 = inBuf[ch][len-1];
f1 = f2 - lastSample[ch];
steigung[ch] = f1 / numRepeats;
lastSample[ch] = f2;
convBuf1 = inBuf[ch];
f1 = (-f1 + steigung[ch]) / len;
for( int j = 0, k = 1; j < len; j++, k++ ) {
convBuf1[j] += k * f1;
}
}
}
// ---- write output ----
if( floatF == null ) {
for( int ch = 0; ch < inChanNum; ch++ ) {
convBuf1 = inBuf[ ch ];
for( int i = 0; i < len; i++ ) {
convBuf1[ i ] *= gain;
f1 = Math.abs( convBuf1[ i ]);
if( f1 > maxAmp ) maxAmp = f1;
}
}
outF.writeFrames( inBuf, 0, len );
} else {
for( int ch = 0; ch < inChanNum; ch++ ) {
convBuf1 = inBuf[ ch ];
for( int i = 0; i < len; i++ ) {
f1 = Math.abs( convBuf1[ i ]);
if( f1 > maxAmp ) maxAmp = f1;
}
floatF[ ch ].writeFloats( inBuf[ ch ], 0, len );
}
}
framesWritten += len;
progOff += len;
// ---- repeats ----
for( int repeat = 1; repeat < numRepeats; repeat++ ) {
for( int ch = 0; ch < inChanNum; ch++ ) {
f1 = steigung[ch] * gain;
convBuf1 = inBuf[ch];
for( int i = 0; i < len; i++ ) {
convBuf1[ i ] += f1;
f2 = Math.abs( convBuf1[ i ]);
if( f2 > maxAmp ) maxAmp = f2;
}
}
// ---- write output ----
if( floatF == null ) {
outF.writeFrames( inBuf, 0, len );
} else {
for( int ch = 0; ch < inChanNum; ch++ ) {
floatF[ ch ].writeFloats( inBuf[ ch ], 0, len );
}
}
framesWritten += len;
progOff += len;
} // for repeats
// .... progress ....
setProgression( (float) progOff / (float) progLen );
}
// .... check running ....
if( !threadRunning ) break topLevel;
// ---- clean up, normalize ----
inF.close();
inF = null;
inStream = null;
if( pr.intg[ PR_GAINTYPE ] == GAIN_UNITY ) {
gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP,
new Param( 1.0 / maxAmp, Param.ABS_AMP ), null )).value;
normalizeAudioFile( floatF, outF, inBuf, gain, 1.0f );
maxAmp *= gain;
for( int ch = 0; ch < inChanNum; ch++ ) {
floatF[ ch ].cleanUp();
floatF[ ch ] = null;
tempFile[ ch ].delete();
tempFile[ ch ] = null;
}
}
// .... check running ....
if( !threadRunning ) break topLevel;
outF.close();
outF = null;
zcFloatFile.cleanUp();
zcFloatFile = null;
zcTempFile.delete();
// ---- Finish ----
// inform about clipping/ low level
handleClipping( maxAmp );
}
catch( IOException e1 ) {
setError( e1 );
}
catch( OutOfMemoryError e2 ) {
inStream = null;
outStream = null;
inBuf = null;
System.gc();
setError( new Exception( ERR_MEMORY ));
}
// ---- cleanup (topLevel) ----
if( inF != null ) {
inF.cleanUp();
}
if( outF != null ) {
outF.cleanUp();
}
if( zcFloatFile != null ) {
zcFloatFile.cleanUp();
zcTempFile.delete();
}
if( floatF != null ) {
for( int 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()
// -------- private methods --------
protected void reflectPropertyChanges() {
super.reflectPropertyChanges();
Component c;
c = gui.getItemObj(GG_REPEATS);
if (c != null) {
c.setEnabled(pr.intg[PR_MODE] == MODE_REPEAT);
}
c = gui.getItemObj(GG_SUBDRY);
if (c != null) {
c.setEnabled(pr.intg[PR_MODE] == MODE_ROTATION);
}
}
}