/*
* StepBackDlg.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:
* 04-Aug-10 corrected marker position (we lost one stepsize per marker)
*/
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.PathEvent;
import de.sciss.gui.PathListener;
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.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.Vector;
/**
* Experimentation with transient detection: Produces
* a sound file segmentation and rearranges the
* time slices.
*/
public class StepBackDlg
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_CORRLENGTH = 2;
private static final int PR_CORRSTEP = 3;
private static final int PR_CORRFINE = 4;
private static final int PR_MODE = 5;
private static final int PR_MINSPACING = 0; // pr.para
private static final int PR_MAXSPACING = 1;
private static final int PR_MINXFADE = 2;
private static final int PR_MAXXFADE = 3;
private static final int PR_OFFSET = 4;
private static final int PR_WEIGHT = 5;
private static final int PR_MARKERS = 0; // pr.bool
private static final int MODE_RVSDECON = 0;
private static final int MODE_RNDDECON = 1;
private static final int MODE_RVSRECON = 2;
private static final int MODE_FWD = 3;
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_CORRLENGTH = "CorrLength";
private static final String PRN_CORRSTEP = "CorrStep";
private static final String PRN_CORRFINE = "CorrFine";
private static final String PRN_MODE = "Mode";
private static final String PRN_MINSPACING = "MinSpacing";
private static final String PRN_MAXSPACING = "MaxSpacing";
private static final String PRN_MINXFADE = "MinXFade";
private static final String PRN_MAXXFADE = "MaxXFade";
private static final String PRN_OFFSET = "Offset";
private static final String PRN_WEIGHT = "Weight";
private static final String PRN_MARKERS = "Markers";
private static final String prText[] = { "", "" };
private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE };
private static final int prIntg[] = { 0, 0, 7, 9, 12, MODE_RVSDECON };
private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_CORRLENGTH, PRN_CORRSTEP,
PRN_CORRFINE, PRN_MODE };
private static final Param prPara[] = { null, null, null, null, null, null };
private static final String prParaName[] = { PRN_MINSPACING, PRN_MAXSPACING, PRN_MINXFADE, PRN_MAXXFADE,
PRN_OFFSET, PRN_WEIGHT };
private static final boolean prBool[] = { true };
private static final String prBoolName[] = { PRN_MARKERS };
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_CORRLENGTH = GG_OFF_CHOICE + PR_CORRLENGTH;
private static final int GG_CORRSTEP = GG_OFF_CHOICE + PR_CORRSTEP;
private static final int GG_CORRFINE = GG_OFF_CHOICE + PR_CORRFINE;
private static final int GG_MODE = GG_OFF_CHOICE + PR_MODE;
private static final int GG_MINSPACING = GG_OFF_PARAMFIELD + PR_MINSPACING;
private static final int GG_MAXSPACING = GG_OFF_PARAMFIELD + PR_MAXSPACING;
private static final int GG_MINXFADE = GG_OFF_PARAMFIELD + PR_MINXFADE;
private static final int GG_MAXXFADE = GG_OFF_PARAMFIELD + PR_MAXXFADE;
private static final int GG_OFFSET = GG_OFF_PARAMFIELD + PR_OFFSET;
private static final int GG_WEIGHT = GG_OFF_PARAMFIELD + PR_WEIGHT;
private static final int GG_MARKERS = GG_OFF_CHECKBOX + PR_MARKERS;
private static final int GG_CUTINFO = GG_OFF_OTHER + 0;
private static PropertyArray static_pr = null;
private static Presets static_presets = null;
private static final String MARK_CUT = "Cut";
private double inLengthMillis = -1.0; // [ms]
// -------- public methods --------
public StepBackDlg() {
super("Step Back");
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.para = prPara;
static_pr.para[ PR_MINSPACING ] = new Param( 100.0, Param.ABS_MS );
static_pr.para[ PR_MAXSPACING ] = new Param( 5000.0, Param.ABS_MS );
static_pr.para[ PR_MINXFADE ] = new Param( 1.0, Param.ABS_MS );
static_pr.para[ PR_MAXXFADE ] = new Param( 1000.0, Param.ABS_MS );
static_pr.para[ PR_OFFSET ] = new Param( 0.0, Param.OFFSET_MS );
static_pr.para[ PR_WEIGHT ] = new Param( 50.0, Param.FACTOR_AMP );
static_pr.paraName = prParaName;
static_pr.bool = prBool;
static_pr.boolName = prBoolName;
// static_pr.envl = prEnvl;
// static_pr.envlName = prEnvlName;
// static_pr.superPr = DocumentFrame.static_pr;
fillDefaultAudioDescr( static_pr.intg, PR_OUTPUTTYPE, PR_OUTPUTRES );
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 ggCorrLength, ggCorrStep, ggCorrFine, ggMode;
ParamField ggMinSpacing, ggMaxSpacing, ggMinXFade, ggMaxXFade, ggOffset, ggWeight;
JTextField ggCutInfo;
JCheckBox ggMarkers;
PathField[] ggInputs;
ParamSpace[] spcSpacing, spcOffset;
String s;
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_MINSPACING:
case GG_MAXSPACING:
pr.para[ ID - GG_OFF_PARAMFIELD ] = ((ParamField) e.getSource()).getParam();
recalcApprox();
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 );
ggInputs = new PathField[ 1 ];
ggInputs[ 0 ] = ggInputFile;
ggOutputFile.deriveFrom( ggInputs, "$D0$F0Back$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, pathL );
gui.registerGadget( ggOutputFile.getTypeGadget(), GG_OUTPUTTYPE );
gui.registerGadget( ggOutputFile.getResGadget(), GG_OUTPUTRES );
// -------- Settings --------
gui.addLabel( new GroupLabel( "Tiling Settings", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE ));
con.fill = GridBagConstraints.HORIZONTAL;
spcSpacing = new ParamSpace[ 2 ];
spcSpacing[0] = Constants.spaces[ Constants.absMsSpace ];
spcSpacing[1] = Constants.spaces[ Constants.absBeatsSpace ];
spcOffset = new ParamSpace[ 2 ];
spcOffset[0] = Constants.spaces[ Constants.offsetMsSpace ];
spcOffset[1] = Constants.spaces[ Constants.offsetBeatsSpace ];
ggMinSpacing = new ParamField( spcSpacing );
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Min.spacing", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggMinSpacing, GG_MINSPACING, paramL );
ggMinXFade = new ParamField( spcSpacing );
con.weightx = 0.1;
gui.addLabel( new JLabel( "Min.X-fade", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggMinXFade, GG_MINXFADE, paramL );
ggMaxSpacing = new ParamField( spcSpacing );
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Max.spacing", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggMaxSpacing, GG_MAXSPACING, paramL );
ggMaxXFade = new ParamField( spcSpacing );
con.weightx = 0.1;
gui.addLabel( new JLabel( "Max.X-fade", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggMaxXFade, GG_MAXXFADE, paramL );
ggOffset = new ParamField( spcOffset );
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Offset", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggOffset, GG_OFFSET, paramL );
ggWeight = new ParamField( Constants.spaces[ Constants.ratioAmpSpace ]);
con.weightx = 0.1;
gui.addLabel( new JLabel( "Energy priority", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggWeight, GG_WEIGHT, paramL );
ggCutInfo = new JTextField();
ggCutInfo.setEditable( false );
ggCutInfo.setBackground( null );
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Approx.# of tiles", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addTextField( ggCutInfo, GG_CUTINFO, null );
ggMarkers = new JCheckBox();
con.weightx = 0.1;
gui.addLabel( new JLabel( "Write markers", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addCheckbox( ggMarkers, GG_MARKERS, null );
ggCorrLength = new JComboBox();
ggCorrStep = new JComboBox();
ggCorrFine = new JComboBox();
ggCorrFine.setEnabled( false ); // XXX
for( int i = 131072; i > 0; i>>=1 ) {
s = String.valueOf( i );
if( i >= 32 ) ggCorrLength.addItem( s );
ggCorrStep.addItem( s );
ggCorrFine.addItem( s );
}
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Corr.length [smp]", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addChoice( ggCorrLength, GG_CORRLENGTH, null );
con.weightx = 0.1;
gui.addLabel( new JLabel( "Stepsize [smp]", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addChoice( ggCorrStep, GG_CORRSTEP, null );
ggMode = new JComboBox();
ggMode.addItem( "Reverse De-construct" );
ggMode.addItem( "Random De-construct" );
ggMode.addItem( "Reverse Re-construct" );
ggMode.addItem( "Forward Segmentation" );
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Mode", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addChoice( ggMode, GG_MODE, null );
con.weightx = 0.1;
gui.addLabel( new JLabel( "Fine step", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addChoice( ggCorrFine, GG_CORRFINE, null );
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, k, ch, len, off;
long progOff, progLen;
double d1, d2;
float f1, f2;
long n, chunkLength;
// io
AudioFile inF = null;
AudioFile outF = null;
AudioFileDescr inStream = null;
AudioFileDescr outStream = null;
float[][] inBuf = null;
float[][] xfadeBuf;
float[] fftBuf1, fftBuf2, fftBuf3, timeBuf, win, convBuf1;
float[] xcorrs, energies;
// Smp Init
long inLength, stopCut, lastCut, framesRead;
int inChanNum;
int numCuts, fadeLen, cutOffset;
int minLoc, spacing, minSpacing, maxSpacing; // [chunks (?) corrStep]
int minXFade, maxXFade, corrLength, corrStep; // [smp]
float minCorr, xcorr, minEnergy;
double energy1, energy2, crossTerm;
int bufOffset, bufLength, overlaps;
float noiseFloor = 1.0e-6f; // -120 dB
float spectWeight, rmsWeight; // , totMinCorr, totMaxCorr;
PathField ggOutput;
Vector<Marker> rndMarkers = new Vector<Marker>();
Random rnd = new Random(System.currentTimeMillis());
java.util.List<Marker> markers;
topLevel:
try {
// ---- open input, output; init ----
// 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);
if (!pr.bool[PR_MARKERS]) { // otherwise wait...
outF = AudioFile.openAsWrite(outStream);
} else {
IOUtil.createEmptyFile(new File(pr.text[PR_OUTPUTFILE]));
}
// .... check running ....
if (!threadRunning) break topLevel;
markers = (java.util.List<Marker>) outStream.getProperty(AudioFileDescr.KEY_MARKERS);
if (markers == null) {
markers = new Vector<Marker>();
outStream.setProperty(AudioFileDescr.KEY_MARKERS, markers);
}
// initialize various stuff
corrLength = 131072 >> pr.intg[PR_CORRLENGTH];
corrStep = Math.min(corrLength, 131072 >> pr.intg[PR_CORRSTEP]);
overlaps = corrLength / corrStep;
stopCut = (inLength + corrStep-1) / corrStep;
minSpacing = ((int) (AudioFileDescr.millisToSamples(inStream, Param.transform(pr.para[PR_MINSPACING],
Param.ABS_MS, null, null).value) + 0.5) + corrStep - 1) / corrStep;
maxSpacing = ((int) (AudioFileDescr.millisToSamples(inStream, Param.transform(pr.para[PR_MAXSPACING],
Param.ABS_MS, null, null).value) + 0.5) + corrStep - 1) / corrStep;
minXFade = (int) (AudioFileDescr.millisToSamples(inStream, Param.transform(pr.para[PR_MINXFADE],
Param.ABS_MS, null, null).value) + 0.5);
maxXFade = (int) (AudioFileDescr.millisToSamples(inStream, Param.transform(pr.para[PR_MAXXFADE],
Param.ABS_MS, null, null).value) + 0.5);
cutOffset = (int) (AudioFileDescr.millisToSamples(inStream, Param.transform(pr.para[PR_OFFSET],
Param.ABS_MS, new Param(0.0, Param.ABS_MS), null).value) + 0.5);
if (minSpacing > maxSpacing) {
i = minSpacing;
minSpacing = maxSpacing;
maxSpacing = i;
}
if (minXFade > maxXFade) {
i = minXFade;
minXFade = maxXFade;
maxXFade = i;
}
xcorrs = new float[maxSpacing - minSpacing + 1];
energies = new float[xcorrs.length];
spectWeight = Math.min(1.0f, 2.0f - (float) (pr.para[PR_WEIGHT].value / 50));
rmsWeight = Math.min(1.0f , (float) (pr.para[PR_WEIGHT].value / 50));
f1 = spectWeight + rmsWeight;
spectWeight /= f1;
rmsWeight /= f1;
// System.out.println( "spectWeight "+spectWeight+"; rmsWeight "+rmsWeight );
// totMinCorr = 2.0f;
// totMaxCorr = 0.0f;
// buffers
inBuf = new float[inChanNum][8192];
fftBuf1 = new float[corrLength + 2];
fftBuf2 = new float[corrLength + 2];
if (pr.intg[PR_MODE] == MODE_RVSRECON) {
fftBuf3 = new float[corrLength + 2];
} else {
fftBuf3 = null;
}
bufLength = corrLength * 3 - corrStep;
bufOffset = bufLength - corrLength; // for successive reads
timeBuf = new float[bufLength];
win = Filter.createFullWindow(corrLength, Filter.WIN_BLACKMAN);
progOff = 0;
progLen = inLength*3;
// ----==================== step one: segmentation ====================----
numCuts = 0;
lastCut = 0L; // XXX +offset
framesRead = 0L;
spacing = -(overlaps-1);
minCorr = -1.0f;
minLoc = 0;
do {
chunkLength = Math.min(inLength - framesRead, corrLength);
System.arraycopy(timeBuf, corrLength, timeBuf, 0, bufLength - corrLength);
for (off = 0; threadRunning && (off < chunkLength); ) {
len = (int) Math.min(8192, chunkLength - off);
inF.readFrames(inBuf, 0, len);
System.arraycopy(inBuf[0], 0, timeBuf, bufOffset + off, len);
for (ch = 1; ch < inChanNum; ch++) { // sum channels, not really true-rms-summing...
Util.add(inBuf[ch], 0, timeBuf, bufOffset + off, len);
}
off += len;
progOff += len;
framesRead += len;
// .... progress ....
setProgression((float) progOff / (float) progLen);
}
// .... check running ....
if (!threadRunning) break topLevel;
// post zero padding
if (chunkLength < corrLength) {
for (i = bufOffset + (int) chunkLength; i < bufLength; ) {
timeBuf[i++] = 0.0f;
}
}
for (i = 0; threadRunning && (i < overlaps); i++) {
// track corr.
if (spacing >= minSpacing) {
if (pr.intg[PR_MODE] != MODE_RVSRECON) {
System.arraycopy(timeBuf, i * corrStep, fftBuf1, 0, corrLength);
Util.mult(win, 0, fftBuf1, 0, corrLength);
Fourier.realTransform(fftBuf1, corrLength, Fourier.FORWARD);
}
System.arraycopy(timeBuf, i * corrStep + corrLength, fftBuf2, 0, corrLength);
Util.mult(win, 0, fftBuf2, 0, corrLength);
Fourier.realTransform(fftBuf2, corrLength, Fourier.FORWARD);
energy1 = 0.0;
energy2 = 0.0;
crossTerm = 0.0;
for (j = 0; j <= corrLength; j += 2) {
k = j + 1;
d1 = fftBuf1[j] * fftBuf1[j] + fftBuf1[k] * fftBuf1[k];
d2 = fftBuf2[j] * fftBuf2[j] + fftBuf2[k] * fftBuf2[k];
energy1 += d1;
energy2 += d2;
crossTerm += Math.sqrt(d1) * Math.sqrt(d2);
}
energy1 = Math.sqrt(energy1);
energy2 = Math.sqrt(energy2);
if ((energy1 > noiseFloor) && (energy2 > noiseFloor)) {
if (energy1 > energy2) {
d1 = energy2 / energy1;
} else {
d1 = energy1 / energy2;
}
xcorr = (float) (crossTerm / (energy1 * energy2) * spectWeight + d1 * rmsWeight);
} else {
xcorr = 0.0f;
}
xcorrs [spacing - minSpacing] = xcorr;
energies[spacing - minSpacing] = (float) (energy1 + energy2);
if (pr.intg[PR_MODE] == MODE_RVSRECON) {
if (xcorr > minCorr) {
minCorr = xcorr;
minLoc = spacing - minSpacing;
System.arraycopy(fftBuf2, 0, fftBuf3, 0, corrLength);
}
}
}
// .... progress ....
progOff += chunkLength/overlaps;
setProgression( (float) progOff / (float) progLen );
spacing++;
// find cut, reset buffers
if (spacing > maxSpacing) {
if (pr.intg[PR_MODE] != MODE_RVSRECON) {
minCorr = 2.0f;
minLoc = 0;
minEnergy = 0.0f;
findMinLp:
for (j = 0; j < xcorrs.length; j++) {
xcorr = xcorrs[j];
f1 = xcorr / minCorr;
if ((f1 < 0.9f) || ((f1 < 1.0f) && (energies[j] / minEnergy < 1.1))) {
minCorr = xcorr;
minEnergy = energies[j];
minLoc = j;
if (minCorr == 0.0f) break findMinLp;
} else if ((f1 < 1.1f) && (energies[j] / minEnergy < 0.9)) {
minCorr = Math.min(minCorr, xcorr);
minEnergy = energies[j];
minLoc = j;
}
}
} else {
minCorr = -1.0f;
minLoc = 0;
System.arraycopy(fftBuf3, 0, fftBuf1, 0, corrLength);
}
if (pr.intg[PR_MODE] == MODE_FWD) {
n = Math.min(inLength, Math.max(0, lastCut * corrStep + cutOffset)); // sample position
} else {
n = Math.min(inLength, Math.max(0, inLength - lastCut * corrStep - cutOffset)); // sample position
}
lastCut += minSpacing + minLoc + 1; // XXX why + 1?
if (pr.intg[PR_MODE] == MODE_RNDDECON) {
k = (int) (rnd.nextFloat() * numCuts + 0.5f);
markers.add(k, new Marker(n, MARK_CUT));
n = Math.min(inLength, Math.max(0, lastCut * corrStep + cutOffset));
rndMarkers.insertElementAt(new Marker(n, MARK_CUT), k);
} else {
markers.add(0, new Marker(n, MARK_CUT));
}
numCuts++;
minLoc++;
j = minLoc + minSpacing;
if (j < xcorrs.length) { // reuse (shifted) data
System.arraycopy(xcorrs , j, xcorrs , 0, xcorrs.length - j);
System.arraycopy(energies, j, energies, 0, xcorrs.length - j);
}
spacing = xcorrs.length - minLoc;
}
} // for i = 0 to overlaps-1
setProgression((float) progOff / (float) progLen);
} while (threadRunning && (lastCut < stopCut));
// .... check running ....
if (!threadRunning) break topLevel;
gui.stringToJTextField(numCuts + " detected.", GG_CUTINFO);
// ----==================== step two: write output ====================----
xcorrs = null;
energies = null;
timeBuf = null;
xfadeBuf = new float[inChanNum][maxXFade];
if (pr.bool[PR_MARKERS]) { // now got it
outF = AudioFile.openAsWrite(outStream);
}
lastCut = inLength;
// System.out.println( "progOff "+progOff+"; progLen-inLength "+(progLen-inLength) );
progOff = progLen - inLength;
if( pr.intg[ PR_MODE ] == MODE_FWD ) {
// simply copy input to output
framesRead = 0;
inF.seekFrame(0);
while (threadRunning && (framesRead < inLength)) {
len = (int) Math.min(8192, inLength - framesRead);
inF .readFrames (inBuf, 0, len);
outF.writeFrames(inBuf, 0, len);
progOff += len;
framesRead += len;
// .... progress ....
setProgression((float) progOff / (float) progLen);
}
} else {
for (i = 0; threadRunning && (i < numCuts); i++) {
if (pr.intg[PR_MODE] == MODE_RNDDECON) {
stopCut = rndMarkers.elementAt(i).pos;
} else {
stopCut = lastCut;
}
lastCut = inLength - markers.get(i).pos;
convBuf1 = fftBuf1;
fftBuf1 = fftBuf2;
fftBuf2 = convBuf1;
// ---------- x-fade part ----------
if (i > 0) {
n = lastCut - corrLength;
off = 0;
if (n < 0) {
n = -n;
while (off < n) {
fftBuf1[off++] = 0.0f;
}
}
inF.seekFrame(n - off);
while (off < corrLength) {
len = Math.min(8192, corrLength - off);
inF.readFrames(inBuf, 0, len);
System.arraycopy(inBuf[0], 0, fftBuf1, off, len);
for (ch = 1; ch < inChanNum; ch++) { // sum channels, not really true-rms-summing...
Util.add(inBuf[ch], 0, fftBuf1, off, len);
}
off += len;
}
Util.mult(win, 0, fftBuf1, 0, corrLength);
Fourier.realTransform(fftBuf1, corrLength, Fourier.FORWARD);
energy1 = 0.0;
energy2 = 0.0;
crossTerm = 0.0;
for (j = 0; j <= corrLength; j += 2) {
k = j + 1;
d1 = fftBuf1[j] * fftBuf1[j] + fftBuf1[k] * fftBuf1[k];
d2 = fftBuf2[j] * fftBuf2[j] + fftBuf2[k] * fftBuf2[k];
energy1 += d1;
energy2 += d2;
crossTerm += Math.sqrt(d1) * Math.sqrt(d2);
}
energy1 = Math.sqrt(energy1);
energy2 = Math.sqrt(energy2);
if ((energy1 > noiseFloor) && (energy2 > noiseFloor)) {
if (energy1 > energy2) {
d1 = energy2 / energy1;
} else {
d1 = energy1 / energy2;
}
xcorr = (float) (crossTerm / (energy1 * energy2) * spectWeight + d1 * rmsWeight);
} else {
xcorr = 0.0f;
}
xcorr = (float) Math.log(xcorr * 1.718281828f + 1.0f);
n = (long) (maxXFade * xcorr + minXFade * (1.0f - xcorr)) >> 1;
fadeLen = (int) Math.min(lastCut, Math.min(bufLength, Math.max(minXFade, n)));
// fade-out
energy1 = 0.0;
for (ch = 0; ch < inChanNum; ch++) {
convBuf1 = xfadeBuf[ch];
for (off = bufLength - fadeLen; off < bufLength; off++) {
energy1 += convBuf1[off] * convBuf1[off];
f1 = (float) (bufLength - off) / (float) fadeLen;
convBuf1[off] *= f1;
}
}
energy1 = Math.sqrt(energy1);
// fade-in
n = lastCut - fadeLen;
inF.seekFrame(n);
energy2 = 0.0;
for (off = bufLength - fadeLen; off < bufLength; ) {
len = Math.min(8192, bufLength - off);
inF.readFrames(inBuf, 0, len);
for (ch = 0; ch < inChanNum; ch++) { // sum channels, not really true-rms-summing...
convBuf1 = xfadeBuf[ch];
for (j = 0, k = off; j < len; ) {
energy2 += inBuf[ch][j] * inBuf[ch][j];
f1 = 1.0f - (float) (bufLength - k) / (float) fadeLen;
convBuf1[k++] += inBuf[ch][j++] * f1;
}
}
off += len;
}
energy1 = (energy1 + Math.sqrt(energy2)) / 2;
// adjusting volume in 1 dB Schritten
k = bufLength - fadeLen/2;
volumeLp:
for (j = 0, f1 = 1.122462f; j < 6; j++, f1 *= 1.122462f) {
energy2 = 0.0;
for (ch = 0; ch < inChanNum; ch++) { // sum channels, not really true-rms-summing...
convBuf1 = xfadeBuf[ch];
for (off = bufLength - fadeLen; off < bufLength; off++) {
// parabola
f2 = 2 * (off - k) / (float) fadeLen;
f2 = (f1 - 1.0f) * (1.0f - f2 * f2) + 1.0f;
d1 = convBuf1[off] * f2;
d1 *= d1;
if (d1 > 1.0) break volumeLp;
energy2 += d1;
}
}
if (energy2 > energy1) break volumeLp;
}
energy1 = 0;
if (j > 0) {
f1 /= 1.122462f;
for (ch = 0; ch < inChanNum; ch++) { // sum channels, not really true-rms-summing...
convBuf1 = xfadeBuf[ch];
for (off = bufLength - fadeLen; off < bufLength; off++) {
f2 = 2 * (off - k) / (float) fadeLen;
f2 = (f1 - 1.0f) * (1.0f - f2 * f2) + 1.0f;
convBuf1[off] *= f2;
}
}
}
// write x-fade
for (off = 0; off < bufLength; ) {
len = Math.min(8192, bufLength - off);
outF.writeFrames(xfadeBuf, off, len);
off += len;
progOff += len;
// .... progress ....
setProgression((float) progOff / (float) progLen);
}
// .... progress ....
setProgression((float) progOff / (float) progLen);
// ---------- x-fade end ----------
} else {
inF.seekFrame(lastCut);
}
chunkLength = stopCut - lastCut;
if (i + 1 != numCuts) {
bufLength = (int) Math.min(chunkLength, maxXFade);
} else {
bufLength = 0; // no xfade after the last cut
}
n = chunkLength - bufLength;
framesRead = 0;
// write to output
while (threadRunning && (framesRead < n)) {
len = (int) Math.min(8192, n - framesRead);
inF .readFrames (inBuf, 0, len);
outF.writeFrames(inBuf, 0, len);
progOff += len;
framesRead += len;
// .... progress ....
setProgression((float) progOff / (float) progLen);
}
// read to x-fade buf
while (threadRunning && (framesRead < chunkLength)) {
len = (int) Math.min(8192, chunkLength - framesRead);
inF.readFrames(xfadeBuf, (int) (framesRead - n), len);
framesRead += len;
}
} // for i = 0 to numCuts-1
} // if not MODE_FWD
// .... check running ....
if (!threadRunning) break topLevel;
// ---- clean up ----
setProgression(1.0f);
inF.close();
inF = null;
inStream = null;
outF.close();
outF = null;
// ---- Finish ----
} catch (IOException e1) {
setError(e1);
} catch (OutOfMemoryError e2) {
inStream = null;
outStream = null;
inBuf = null;
fftBuf1 = null;
fftBuf2 = null;
fftBuf3 = null;
timeBuf = null;
win = null;
xcorrs = null;
energies = null;
convBuf1 = null;
System.gc();
setError(new Exception(ERR_MEMORY));
}
// ---- cleanup (topLevel) ----
if (inF != null) {
inF.cleanUp();
}
if (outF != null) {
outF.cleanUp();
}
} // process()
// -------- private methods --------
/**
* 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();
inLengthMillis = AudioFileDescr.samplesToMillis(stream, stream.length);
recalcApprox();
} catch (IOException e1) {
inLengthMillis = -1.0;
}
}
// calc approximate # of tiles according to input length, min+max spacing
protected void recalcApprox() {
int minNum, maxNum, meanNum;
double d1, d2;
if (inLengthMillis >= 0) {
d1 = Param.transform(pr.para[PR_MAXSPACING], Param.ABS_MS, null, null).value;
d2 = Param.transform(pr.para[PR_MINSPACING], Param.ABS_MS, null, null).value;
minNum = (int) Math.ceil(inLengthMillis / d1);
maxNum = (int) Math.ceil(inLengthMillis / d2);
meanNum = (int) Math.ceil(inLengthMillis / ((d1 + d2) / 2));
gui.stringToJTextField("\u2300" + meanNum + " (" + minNum + "\u2013" + maxNum + ")", GG_CUTINFO);
} else {
gui.stringToJTextField("", GG_CUTINFO);
}
}
}