/* * MakeLoopDlg.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: * 29-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.io.AudioFile; import de.sciss.io.AudioFileDescr; import javax.swing.*; import java.awt.*; 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. * * @todo handle markers */ public class MakeLoopDlg 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_GAIN = 0; // pr.para private static final int PR_INITIALSKIP = 1; // pr.para private static final int PR_FADELENGTH = 2; private static final int PR_FINALSKIP = 3; private static final int PR_OUTPUTTYPE = 0; // pr.intg private static final int PR_OUTPUTRES = 1; private static final int PR_FADEPOS = 2; private static final int PR_FADESHAPE = 3; private static final int PR_FADETYPE = 4; private static final int PR_GAINTYPE = 5; // 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_FADEPOS = "FadePos"; private static final String PRN_FADELENGTH = "FadeLen"; 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_FADESHAPE = "FadeShape"; private static final String PRN_FADETYPE = "FadeType"; // private static final String PRN_AUTOSCALE = "AutoScale"; // private static final String PRN_SEPARATEFILES = "SeparateFiles"; private static final String[] POS_NAMES = { "End of Loop \\ Pre Loop /", "Begin of Loop \\ Post Loop /" }; private static final int POS_PRE = 0; private static final int POS_POST = 1; private static final String[] SHAPE_NAMES = { "Normal", "Fast in", "Slow in", "Easy in Easy out" }; private static final int SHAPE_NORMAL = 0; // private static final int SHAPE_FASTIN = 1; // private static final int SHAPE_SLOWIN = 2; // private static final int SHAPE_EASY = 3; private static final String[] TYPE_NAMES = { "Equal Energy", "Equal Power" }; // private static final int TYPE_ENERGY = 0; private static final int TYPE_POWER = 1; private static final String prText[] = { "", "" }; private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE }; private static final int prIntg[] = { 0, 0, POS_PRE, SHAPE_NORMAL, TYPE_POWER, GAIN_UNITY }; private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_FADEPOS, PRN_FADESHAPE, PRN_FADETYPE, PRN_GAINTYPE }; private static final Param prPara[] = { null, null, null, null }; private static final String prParaName[] = { PRN_GAIN, PRN_INITIALSKIP, PRN_FADELENGTH, 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_GAIN = GG_OFF_PARAMFIELD + PR_GAIN; private static final int GG_INITIALSKIP = GG_OFF_PARAMFIELD + PR_INITIALSKIP; private static final int GG_FADELENGTH = GG_OFF_PARAMFIELD + PR_FADELENGTH; 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_FADEPOS = GG_OFF_CHOICE + PR_FADEPOS; private static final int GG_FADESHAPE = GG_OFF_CHOICE + PR_FADESHAPE; private static final int GG_FADETYPE = GG_OFF_CHOICE + PR_FADETYPE; private static final int GG_GAINTYPE = GG_OFF_CHOICE + PR_GAINTYPE; // 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 MakeLoopDlg() { super( "Make Loop" ); 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( 1000.0, Param.ABS_MS ); static_pr.para[ PR_FADELENGTH ] = new Param( 1000.0, Param.ABS_MS ); 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 ); fillDefaultGain( static_pr.para, PR_GAIN ); 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; Component[] ggGain; PathField[] ggInputs; ParamField ggParam; ParamSpace[] spcOffset, spcLength; ParamSpace spc; JComboBox ggCombo; gui = new GUISupport(); con = gui.getGridBagConstraints(); con.insets = new Insets( 1, 2, 1, 2 ); // -------- 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, 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; ggOutputFile.deriveFrom( ggInputs, "$D0$F0Loop$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, null ); // -------- Settings-Gadgets -------- gui.addLabel( new GroupLabel( "Loop 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( "Crossfade Length", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggParam, GG_FADELENGTH, null ); ggParam = 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( ggParam, GG_INITIALSKIP, null ); ggCombo = new JComboBox(); for( int i = 0; i < POS_NAMES.length; i++ ) { ggCombo.addItem( POS_NAMES[ i ]); } con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Crossfade Position", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addChoice( ggCombo, GG_FADEPOS, 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 ); ggCombo = new JComboBox(); ggCombo.setEnabled( false ); for( int i = 0; i < SHAPE_NAMES.length; i++ ) { ggCombo.addItem( SHAPE_NAMES[ i ]); } con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Crossfade Shape", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addChoice( ggCombo, GG_FADESHAPE, null ); ggCombo = new JComboBox(); for( int i = 0; i < TYPE_NAMES.length; i++ ) { ggCombo.addItem( TYPE_NAMES[ i ]); } con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Crossfade Type", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addChoice( ggCombo, GG_FADETYPE, null ); 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, fadeBuf; float[] convBuf1; float f1, f2; float maxAmp = 0f; double d1; AudioFile outF2; AudioFile tempF = null; float gain; long fadeLen, initialSkip, finalSkip, inLength, fadeInOff, fadeOutOff, copyOff, copyLen, framesWritten; int len, inChans; Param ref; boolean postPos, eqP; PathField ggOutput; topLevel: try { // ---- open input, output; init ---- inF = AudioFile.openAsRead( new File( pr.text[ PR_INPUTFILE ])); inDescr = inF.getDescr(); inChans = inDescr.channels; inLength = inDescr.length; if( inChans * inLength <= 0 ) throw new EOFException( ERR_EMPTY ); ref = new Param( AudioFileDescr.samplesToMillis( inDescr, inLength ), Param.ABS_MS ); fadeLen = (long) (AudioFileDescr.millisToSamples( inDescr, Param.transform( pr.para[ PR_FADELENGTH ], Param.ABS_MS, ref, null ).value) + 0.5); 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); postPos = pr.intg[ PR_FADEPOS ] == POS_POST; // true; // pr.intg[ PR_FADEPPOS ] == POS_POST; eqP = pr.intg[ PR_FADETYPE] == TYPE_POWER; // pr.intg[ PR_FADEPTYPE ] == TYPE_POWER; // constrain initialSkip = Math.min( initialSkip, inLength ); finalSkip = Math.min( finalSkip, inLength - initialSkip ); fadeLen = Math.min( Math.min( postPos ? finalSkip : initialSkip, fadeLen ), inLength - finalSkip - initialSkip ); copyOff = postPos ? initialSkip + fadeLen : initialSkip; copyLen = inLength - finalSkip - initialSkip - fadeLen; fadeInOff = postPos ? initialSkip : initialSkip - fadeLen; fadeOutOff = postPos ? inLength - finalSkip : inLength - finalSkip - fadeLen; //System.err.println( "copyOff = "+copyOff+"; fadeInOff = "+fadeInOff+"; fadeOutOff = "+fadeOutOff+"; copyLen = "+copyLen ); // output ggOutput = (PathField) gui.getItemObj( GG_OUTPUTFILE ); if( ggOutput == null ) throw new IOException( ERR_MISSINGPROP ); outDescr = new AudioFileDescr( inDescr ); ggOutput.fillStream( outDescr ); outF = AudioFile.openAsWrite( outDescr ); // normalization requires temp files if( pr.intg[ PR_GAINTYPE ] == GAIN_UNITY ) { tempF = createTempFile( inDescr ); outF2 = tempF; gain = 1.0f; } else { ref = new Param( 1.0, Param.ABS_AMP ); gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ref, null )).value; outF2 = outF; } // .... check running .... if( !threadRunning ) break topLevel; // .... check running .... if( !threadRunning ) break topLevel; inBuf = new float[ inChans ][ 8192 ]; fadeBuf = new float[ inChans ][ (int) Math.min( fadeLen, 8192 )]; progOff = 0; progLen = copyLen + fadeLen; for( int i = 0; (i < 2) && threadRunning; i++ ) { framesWritten = 0; if( (i == 0) == postPos ) { // -------- dem fade -------- inF.seekFrame( copyOff ); while( (framesWritten < copyLen) && threadRunning ) { len = (int) Math.min( copyLen - framesWritten, 8192 ); // inF.copyFrames( outF, len ); inF.readFrames( inBuf, 0, len ); if( gain != 1.0f ) { for( int ch = 0; ch < inChans; ch++ ) { Util.mult( inBuf[ ch ], 0, len, gain ); } } outF2.writeFrames( inBuf, 0, len ); for( int ch = 0; ch < inChans; ch++ ) { convBuf1 = inBuf[ ch ]; for( int j = 0; j < len; j++ ) { f1 = Math.abs( convBuf1[ j ]); if( f1 > maxAmp ) { maxAmp = f1; } } } framesWritten += len; progOff += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } } else { // -------- dem copy -------- while( (framesWritten < fadeLen) && threadRunning ) { len = (int) Math.min( fadeLen - framesWritten, 8192 ); inF.seekFrame( fadeOutOff + framesWritten ); inF.readFrames( fadeBuf, 0, len ); inF.seekFrame( fadeInOff + framesWritten ); inF.readFrames( inBuf, 0, len ); if( eqP ) { for( int j = 0; j < len; j++ ) { d1 = (double) (j + framesWritten) / fadeLen; f1 = (float) Math.sqrt( d1 ) * gain; f2 = (float) Math.sqrt( 1.0 - d1 ) * gain; for( int ch = 0; ch < inChans; ch++ ) { inBuf[ ch ][ j ] = inBuf[ ch ][ j ] * f1 + fadeBuf[ ch ][ j ] * f2; } } } else { for( int j = 0; j < len; j++ ) { d1 = (double) (j + framesWritten) / fadeLen; f1 = (float) d1 * gain; f2 = (float) (1.0 - d1) * gain; for( int ch = 0; ch < inChans; ch++ ) { inBuf[ ch ][ j ] = inBuf[ ch ][ j ] * f1 + fadeBuf[ ch ][ j ] * f2; } } } outF2.writeFrames( inBuf, 0, len ); for( int ch = 0; ch < inChans; ch++ ) { convBuf1 = inBuf[ ch ]; for( int j = 0; j < len; j++ ) { f1 = Math.abs( convBuf1[ j ]); if( f1 > maxAmp ) { maxAmp = f1; } } } framesWritten += len; progOff += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } } } // .... check running .... if( !threadRunning ) break topLevel; // ---- norm ---- if( tempF != null ) { ref = new Param( 1.0 / maxAmp, Param.ABS_AMP ); gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ref, null )).value; normalizeAudioFile( tempF, outF, inBuf, gain, 1.0f ); maxAmp *= gain; deleteTempFile( tempF ); } // done outF.close(); outF = null; // ---- Finish ---- //System.err.println( "maxAmp "+maxAmp ); // inform about clipping/ low level handleClipping( maxAmp ); } catch( IOException e1 ) { setError( e1 ); } // ---- cleanup (topLevel) ---- if( outF != null ) { outF.cleanUp(); } if( inF != null ) { inF.cleanUp(); } } // process() }