/*
* SpliceDlg.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:
* 25-Jun-06 created
*/
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.util.Constants;
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 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 for splitting
* up a file into several splices.
* use Concat to re-glue these splices.
*/
public class SpliceDlg
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_INITIALSKIP = 0; // pr.para
private static final int PR_SPLICELENGTH = 1;
private static final int PR_SKIPLENGTH = 2;
private static final int PR_AUTONUM = 3;
private static final int PR_FINALSKIP = 4;
private static final int PR_OUTPUTTYPE = 0; // pr.intg
private static final int PR_OUTPUTRES = 1;
private static final int PR_AUTOSCALE = 0; // pr.bool
private static final int PR_SEPARATEFILES = 1;
private static final String PRN_INPUTFILE = "InputFile";
private static final String PRN_OUTPUTFILE = "OutputFile";
private static final String PRN_INITIALSKIP = "InitialSkip";
private static final String PRN_SPLICELENGTH = "SpliceLen";
private static final String PRN_SKIPLENGTH = "SkipLen";
private static final String PRN_AUTONUM = "AutoNum";
private static final String PRN_FINALSKIP = "FinalSkip";
private static final String PRN_OUTPUTTYPE = "OutputType";
private static final String PRN_OUTPUTRES = "OutputReso";
private static final String PRN_AUTOSCALE = "AutoScale";
private static final String PRN_SEPARATEFILES = "SeparateFiles";
private static final String prText[] = { "", "" };
private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE };
private static final int prIntg[] = { 0, 0 };
private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES };
private static final Param prPara[] = { null, null, null, null, null };
private static final String prParaName[] = { PRN_INITIALSKIP, PRN_SPLICELENGTH, PRN_SKIPLENGTH, PRN_AUTONUM, PRN_FINALSKIP };
private static final boolean prBool[] = { false, true };
private static final String prBoolName[] = { PRN_AUTOSCALE, PRN_SEPARATEFILES };
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_INITIALSKIP = GG_OFF_PARAMFIELD + PR_INITIALSKIP;
private static final int GG_SPLICELENGTH = GG_OFF_PARAMFIELD + PR_SPLICELENGTH;
private static final int GG_SKIPLENGTH = GG_OFF_PARAMFIELD + PR_SKIPLENGTH;
private static final int GG_AUTONUM = GG_OFF_PARAMFIELD + PR_AUTONUM;
private static final int GG_FINALSKIP = GG_OFF_PARAMFIELD + PR_FINALSKIP;
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_AUTOSCALE = GG_OFF_CHECKBOX + PR_AUTOSCALE;
private static final int GG_SEPARATEFILES = GG_OFF_CHECKBOX + PR_SEPARATEFILES;
// private static final int GG_CURRENTINFO = GG_OFF_OTHER + 0;
private static PropertyArray static_pr = null;
private static Presets static_presets = null;
// -------- public methods --------
/**
* !! setVisible() bleibt dem Aufrufer ueberlassen
*/
public SpliceDlg()
{
super("Slice");
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.para[ PR_INITIALSKIP ] = new Param( 0.0, Param.ABS_MS );
static_pr.para[ PR_SPLICELENGTH ] = new Param( 1000.0, Param.ABS_MS );
static_pr.para[ PR_SKIPLENGTH ] = new Param( 1000.0, Param.ABS_MS );
static_pr.para[ PR_AUTONUM ] = new Param( 2, Param.NONE );
static_pr.para[ PR_FINALSKIP ] = new Param( 0.0, Param.ABS_MS );
static_pr.paraName = prParaName;
// 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();
// -------- Misc init --------
// -------- build GUI --------
GridBagConstraints con;
PathField ggInputFile, ggOutputFile;
PathField[] ggInputs;
ParamField ggParam, ggSpliceLength;
ParamSpace[] spcOffset, spcLength;
ParamSpace spc;
JCheckBox ggCheck;
gui = new GUISupport();
con = gui.getGridBagConstraints();
con.insets = new Insets( 1, 2, 1, 2 );
ItemListener il = new ItemListener() {
public void itemStateChanged( ItemEvent e )
{
final boolean b = ((JCheckBox) e.getSource()).isSelected();
pr.bool[ PR_AUTOSCALE ] = b;
gui.getItemObj( GG_AUTONUM ).setEnabled( b );
gui.getItemObj( GG_FINALSKIP ).setEnabled( b );
}
};
PathListener pathL = new PathListener() {
public void pathChanged( PathEvent e )
{
int ID = gui.getItemID( e );
switch( ID ) {
case GG_INPUTFILE:
pr.text[ ID - GG_OFF_PATHFIELD ] = ((PathField) e.getSource()).getPath().getPath();
setInput( pr.text[ ID - GG_OFF_PATHFIELD ]);
// analyseFileNames();
break;
}
}
};
// -------- Input-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 );
// ggCurrentInfo = new JTextField();
// ggCurrentInfo.setEditable( false );
// ggCurrentInfo.setBackground( null );
// con.weightx = 0.1;
// con.gridwidth = 1;
// gui.addLabel( new JLabel( "Current input", SwingConstants.RIGHT ));
// con.gridwidth = GridBagConstraints.REMAINDER;
// con.weightx = 0.9;
// gui.addTextField( ggCurrentInfo, GG_CURRENTINFO, al );
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$F0Cut$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 );
ggCheck = new JCheckBox();
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Separate files", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addCheckbox( ggCheck, GG_SEPARATEFILES, il );
// -------- Settings-Gadgets --------
gui.addLabel( new GroupLabel( "Slice Settings", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE ));
spcLength = new ParamSpace[ 3 ];
spcLength[0] = Constants.spaces[ Constants.absMsSpace ];
spcLength[1] = Constants.spaces[ Constants.absBeatsSpace ];
spcLength[2] = Constants.spaces[ Constants.ratioTimeSpace ];
spcOffset = new ParamSpace[ 3 ];
spc = new ParamSpace( Constants.spaces[ Constants.absMsSpace ]);
// spc.min = Double.NEGATIVE_INFINITY;
spc = new ParamSpace( Double.NEGATIVE_INFINITY, spc.max, spc.inc, spc.unit );
spcOffset[0] = spc; // Constants.spaces[ Constants.offsetMsSpace ];
spc = new ParamSpace( Constants.spaces[ Constants.absBeatsSpace ]);
// spc.min = Double.NEGATIVE_INFINITY;
spc = new ParamSpace( Double.NEGATIVE_INFINITY, spc.max, spc.inc, spc.unit );
spcOffset[1] = spc; // Constants.spaces[ Constants.offsetBeatsSpace ];
// spc = new ParamSpace( Constants.spaces[ Constants.fTimeSpace ]);
// spc.min = Double.NEGATIVE_INFINITY;
spc = new ParamSpace( Constants.spaces[ Constants.ratioTimeSpace ]);
// spc.min = Double.NEGATIVE_INFINITY;
spc = new ParamSpace( Double.NEGATIVE_INFINITY, spc.max, spc.inc, spc.unit );
spcOffset[2] = spc; // Constants.spaces[ Constants.factorTimeSpace ];
con.fill = GridBagConstraints.BOTH;
con.gridwidth = GridBagConstraints.REMAINDER;
ggParam = new ParamField( spcLength );
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Slice Length", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggParam, GG_SPLICELENGTH, null );
ggSpliceLength = new ParamField( spcLength );
con.weightx = 0.1;
gui.addLabel( new JLabel( "Initial Skip", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggSpliceLength, GG_INITIALSKIP, null );
ggParam = new ParamField( spcOffset );
// ggParam.setReference( ggSpliceLength );
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Skip Length", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggParam, GG_SKIPLENGTH, null );
ggParam = new ParamField( spcLength );
con.weightx = 0.1;
gui.addLabel( new JLabel( "Final Skip", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggParam, GG_FINALSKIP, null );
ggParam.setEnabled( false ); // XXX funzt niet???
ggCheck = new JCheckBox();
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Automatic rescale", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addCheckbox( ggCheck, GG_AUTOSCALE, il );
ggParam = new ParamField( new ParamSpace( 1, Double.POSITIVE_INFINITY, 1, Param.NONE ));
con.weightx = 0.1;
gui.addLabel( new JLabel( "# of Slices", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggParam, GG_AUTONUM, null );
ggParam.setEnabled( false ); // XXX funzt niet???
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()
{
long progOff, progLen;
AudioFile inF = null;
AudioFile outF = null;
AudioFileDescr inDescr;
AudioFileDescr outDescr;
float[][] inBuf;
long numSlices, initialSkip, finalSkip, sliceLen, sliceSkip, inLength, off, stop;
int len, idx, inBufLen;
double d1, d2, d3;
Param ref;
boolean dirty, separate;
String str, sepBeg = null, sepEnd = null;
PathField ggOutput;
topLevel: try {
// ---- open input, output; init ----
inF = AudioFile.openAsRead( new File( pr.text[ PR_INPUTFILE ]));
inDescr = inF.getDescr();
inLength = inDescr.length;
if( inDescr.channels * inLength <= 0 ) throw new EOFException( ERR_EMPTY );
ref = new Param( AudioFileDescr.samplesToMillis( inDescr, inLength ), Param.ABS_MS );
d1 = AudioFileDescr.millisToSamples( inDescr, Param.transform( pr.para[ PR_SPLICELENGTH ], Param.ABS_MS, ref, null ).value);
d2 = AudioFileDescr.millisToSamples( inDescr, Param.transform( pr.para[ PR_SKIPLENGTH ], Param.ABS_MS, ref, null ).value);
initialSkip = (long) (AudioFileDescr.millisToSamples( inDescr, Param.transform( pr.para[ PR_INITIALSKIP ], Param.ABS_MS, ref, null ).value) + 0.5);
finalSkip = (long) (AudioFileDescr.millisToSamples( inDescr, Param.transform( pr.para[ PR_FINALSKIP ], Param.ABS_MS, ref, null ).value) + 0.5);
if( pr.bool[ PR_AUTOSCALE ]) {
numSlices = (long) (pr.para[ PR_AUTONUM ].value + 0.5);
d2 = Math.max( d2, -d1 + 1 );
d3 = Math.max( 0L, inLength - initialSkip - finalSkip ) / (d1 * numSlices + (d2 * numSlices - 1));
sliceLen = (long) (d1 * d3 + 0.5);
sliceSkip = (long) (d2 * d3 + 0.5);
} else {
sliceLen = (long) (d1 + 0.5);
sliceSkip = (long) (d2 + 0.5);
if( sliceLen + sliceSkip == 0L ) {
sliceSkip = -sliceLen + 1;
}
if( sliceLen + sliceSkip < 0 ) {
// initialSkip + (sliceLen + sliceSkip) * numSlices <! 0
numSlices = (-initialSkip - 1) / (sliceLen + sliceSkip) + 2;
} else {
// initialSkip + (sliceLen + sliceSkip) * numSlices >=! length
numSlices = (inDescr.length - initialSkip - 1) / (sliceLen + sliceSkip) + 1;
}
}
separate = pr.bool[ PR_SEPARATEFILES ];
// output
ggOutput = (PathField) gui.getItemObj( GG_OUTPUTFILE );
if( ggOutput == null ) throw new IOException( ERR_MISSINGPROP );
outDescr = new AudioFileDescr( inDescr );
ggOutput.fillStream( outDescr );
if( separate ) {
str = outDescr.file.getName();
idx = str.lastIndexOf( '.' );
if( idx == -1 ) idx = str.length();
sepBeg = str.substring( 0, idx );
sepEnd = str.substring( idx );
} else {
outF = AudioFile.openAsWrite( outDescr );
}
// .... check running ....
if( !threadRunning ) break topLevel;
inBufLen = (int) Math.min( 8192, sliceLen );
inBuf = new float[ inDescr.channels ][ inBufLen ];
progOff = 0;
progLen = numSlices * sliceLen;
off = initialSkip;
dirty = false;
for( long n = 0; (n < numSlices) && threadRunning; n++ ) {
if( separate ) {
outDescr.file = new File( outDescr.file.getParentFile(), sepBeg + String.valueOf( n + 1 ) + sepEnd );
outF = AudioFile.openAsWrite( outDescr );
}
stop = off + sliceLen;
if( off < 0 ) { // this shouldn't theoretically happen; but it does ... ;-|
if( dirty ) {
Util.clear( inBuf );
dirty = false;
}
do {
len = (int) Math.min( inBufLen, -off );
outF.writeFrames( inBuf, 0, len );
off += len;
progOff = progOff + len;
// .... progress ....
setProgression( (float) progOff / (float) progLen );
// .... check running ....
if( !threadRunning ) break topLevel;
} while( off < 0 );
}
if( off < inLength ) {
dirty = true;
inF.seekFrame( off );
len = (int) Math.min( inBufLen, Math.min( stop - off, inLength - off ));
do {
inF.readFrames( inBuf, 0, len );
outF.writeFrames( inBuf, 0, len );
off += len;
progOff = progOff + len;
// .... progress ....
setProgression( (float) progOff / (float) progLen );
// .... check running ....
if( !threadRunning ) break topLevel;
len = (int) Math.min( inBufLen, Math.min( stop - off, inLength - off ));
} while( len > 0 );
}
if( off < stop ) {
if( dirty ) {
Util.clear( inBuf );
dirty = false;
}
do {
len = (int) Math.min( inBufLen, stop - off );
outF.writeFrames( inBuf, 0, len );
off += len;
progOff = progOff + len;
// .... progress ....
setProgression( (float) progOff / (float) progLen );
// .... check running ....
if( !threadRunning ) break topLevel;
} while( off < stop );
}
off = off + sliceSkip;
if( separate ) {
outF.close();
outF = null;
}
}
// .... check running ....
if( !threadRunning ) break topLevel;
if( !separate ) {
outF.close();
outF = null;
}
}
catch( IOException e1 ) {
setError( e1 );
}
catch( OutOfMemoryError e2 ) {
outDescr = null;
inBuf = null;
inDescr = null;
System.gc();
setError( new Exception( ERR_MEMORY ));
}
// ---- cleanup (topLevel) ----
if( outF != null ) {
outF.cleanUp();
}
if( inF != null ) {
inF.cleanUp();
}
} // process()
// -------- private methods --------
/*
* Set new input file
*/
protected void setInput( String fname )
{
final AudioFile f;
final AudioFileDescr stream;
final Param ref;
// ---- Header lesen ----
try {
f = AudioFile.openAsRead( new File( fname ));
stream = f.getDescr();
f.close();
ref = new Param( AudioFileDescr.samplesToMillis( stream, stream.length ), Param.ABS_MS );
((ParamField) gui.getItemObj( GG_INITIALSKIP )).setReference( ref );
((ParamField) gui.getItemObj( GG_FINALSKIP )).setReference( ref );
((ParamField) gui.getItemObj( GG_SPLICELENGTH )).setReference( ref );
((ParamField) gui.getItemObj( GG_SKIPLENGTH )).setReference( ref );
} catch( IOException ignored) {}
}
// /**
// * Maske zur Berechnung der Chunk Filenamen erstellen
// *
// * gibt Zahl der Files zurueck oder Null bei Fehler
// */
// protected int analyseFileNames()
// {
// String s1 = pr.text[ PR_INPUTFILE ];
// String s2 = pr.text[ PR_INPUTFILE2 ];
// int len1 = s1.length();
// int len2 = s2.length();
// int i, j, numStart, numEnd, numMin, numMax, num;
// char[] numPad;
// DecimalFormat numForm;
//
// // Mechanismus: erste Abweichung links->rechts + erste Abweichung rechts->links
// // finden, dann Bereich auf evtl. benachbarte numerische Charaktere ausdehnen
// // und als Integer interpretieren
//
// for( numStart = 0; numStart < Math.min( len1, len2 ); numStart++ ) {
// if( s1.charAt( numStart ) != s2.charAt( numStart )) break;
// }
// for( ; numStart > 0; numStart-- ) {
// if( !Character.isDigit( s1.charAt( numStart - 1 )) ||
// !Character.isDigit( s2.charAt( numStart - 1 ))) break;
// }
// for( numEnd = 0; numEnd < Math.min( len1, len2 ); numEnd++ ) {
// if( s1.charAt( len1 - numEnd - 1 ) != s2.charAt( len2 - numEnd - 1 )) break;
// }
// for( ; numEnd > 0; numEnd-- ) {
// if( !Character.isDigit( s1.charAt( len1 - numEnd )) ||
// !Character.isDigit( s2.charAt( len2 - numEnd ))) break;
// }
////System.out.println( "numStart "+numStart+"; numEnd "+numEnd+"; len1 "+len1+"; len2 "+len2 );
//
// try {
// i = Integer.parseInt( s1.substring( numStart, len1 - numEnd ));
// j = Integer.parseInt( s2.substring( numStart, len2 - numEnd ));
// numMin = Math.min( i, j );
// numMax = Math.max( i, j );
// num = numMax - numMin + 1;
// gui.stringToJTextField( num + " files detected.", GG_CURRENTINFO );
//
// fileNameArgs[1] = s1.substring( 0, numStart );
// fileNameArgs[2] = s1.substring( len1 - numEnd );
// fileNameArgs[3] = new Integer( numMin );
// i = Math.min( len1, len2 ) - numEnd - numStart;
// numPad = new char[ i + Math.max( len1, len2 ) - Math.min( len1, len2 )];
// for( j = 0; j < i; j++ ) numPad[j] = '0';
// fileNameArgs[4] = new String( numPad );
//
// return num;
//
// } catch( NumberFormatException e1 ) {
// numMin = -1;
// numMax = -1;
// gui.stringToJTextField( "Filename analysis failed!", GG_CURRENTINFO );
// return 0;
// }
// }
// /*
// * Generiert Filenamen aus mit analyseFilenames() erstellter Maske
// *
// * ID laueft von 0 bis numFiles-1
// */
// protected String synthesizeFileName( int ID )
// {
// String s = String.valueOf( ID + ((Integer) fileNameArgs[3]).intValue() );
// fileNameArgs[0] = ((String) fileNameArgs[4]).substring( s.length() ) + s;
//
// return( fileNameForm.format( FILENAMEPTRN, fileNameArgs ));
// }
}