/* * DrMurkeDlg.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.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.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.EOFException; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class DrMurkeDlg extends ModulePanel { // -------- private variables -------- private static final boolean verbose = false; // Properties (defaults) private static final int PR_INPUTFILE = 0; // pr.text private static final int PR_CTRLFILE = 1; private static final int PR_OUTPUTFILE = 2; 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_MODE = 3; private static final int PR_CHANNELUP = 4; private static final int PR_CHANNELDOWN = 5; private static final int PR_SPACINGTYPE = 6; private static final int PR_THRESHUP = 0; // pr.para private static final int PR_THRESHDOWN = 1; private static final int PR_DURUP = 2; private static final int PR_DURDOWN = 3; private static final int PR_ATTACK = 4; private static final int PR_RELEASE = 5; private static final int PR_GAIN = 6; private static final int PR_SPACING = 7; private static final String PRN_INPUTFILE = "InputFile"; private static final String PRN_CTRLFILE = "CtrlFile"; 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_CHANNELUP = "ChannelUp"; private static final String PRN_CHANNELDOWN = "ChannelDown"; private static final String PRN_SPACINGTYPE = "SpacingType"; private static final String PRN_THRESHUP = "ThreshUp"; private static final String PRN_THRESHDOWN = "ThreshDown"; private static final String PRN_DURUP = "DurUp"; private static final String PRN_DURDOWN = "DurDown"; private static final String PRN_ATTACK = "Attack"; private static final String PRN_RELEASE = "Release"; private static final String PRN_SPACING = "Spacing"; private static final String[] MODE_NAMES = { "Keep Upper Chunks", "Keep Down Chunks" }; private static final int MODE_UPPER = 0; private static final int MODE_DOWN = 1; private static final String[] CHANNEL_NAMES = { "Take Maximum", "Take Minimum" }; private static final int CHANNEL_MAX = 0; private static final int CHANNEL_MIN = 1; private static final String[] SPACING_NAMES = { "Fixed Spacing:", "Original Spacing" }; private static final int SPACING_FIXED = 0; private static final int SPACING_ORIGINAL= 1; private static final String prText[] = { "", "", "" }; private static final String prTextName[] = { PRN_INPUTFILE, PRN_CTRLFILE, PRN_OUTPUTFILE }; private static final int prIntg[] = { 0, 0, GAIN_UNITY, MODE_UPPER, CHANNEL_MIN, CHANNEL_MAX, SPACING_FIXED }; private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_GAINTYPE, PRN_MODE, PRN_CHANNELUP, PRN_CHANNELDOWN, PRN_SPACINGTYPE }; private static final Param prPara[] = { null, null, null, null, null, null, null, null }; private static final String prParaName[] = { PRN_THRESHUP, PRN_THRESHDOWN, PRN_DURUP, PRN_DURDOWN, PRN_ATTACK, PRN_RELEASE, PRN_GAIN, PRN_SPACING }; private static final int GG_INPUTFILE = GG_OFF_PATHFIELD + PR_INPUTFILE; private static final int GG_CTRLFILE = GG_OFF_PATHFIELD + PR_CTRLFILE; 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_MODE = GG_OFF_CHOICE + PR_MODE; private static final int GG_CHANNELUP = GG_OFF_CHOICE + PR_CHANNELUP; private static final int GG_CHANNELDOWN = GG_OFF_CHOICE + PR_CHANNELDOWN; private static final int GG_SPACINGTYPE = GG_OFF_CHOICE + PR_SPACINGTYPE; private static final int GG_THRESHUP = GG_OFF_PARAMFIELD + PR_THRESHUP; private static final int GG_THRESHDOWN = GG_OFF_PARAMFIELD + PR_THRESHDOWN; private static final int GG_DURUP = GG_OFF_PARAMFIELD + PR_DURUP; private static final int GG_DURDOWN = GG_OFF_PARAMFIELD + PR_DURDOWN; private static final int GG_ATTACK = GG_OFF_PARAMFIELD + PR_ATTACK; private static final int GG_RELEASE = GG_OFF_PARAMFIELD + PR_RELEASE; private static final int GG_GAIN = GG_OFF_PARAMFIELD + PR_GAIN; private static final int GG_SPACING = GG_OFF_PARAMFIELD + PR_SPACING; private static PropertyArray static_pr = null; private static Presets static_presets = null; // -------- public methods -------- /** * !! setVisible() bleibt dem Aufrufer ueberlassen */ public DrMurkeDlg() { super( "Dr Murke" ); 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_THRESHUP ] = new Param( 30.0, Param.FACTOR_AMP ); static_pr.para[ PR_THRESHDOWN ] = new Param( 20.0, Param.FACTOR_AMP ); static_pr.para[ PR_DURUP ] = new Param( 100.0, Param.ABS_MS ); static_pr.para[ PR_DURDOWN ] = new Param( 10.0, Param.ABS_MS ); static_pr.para[ PR_ATTACK ] = new Param( 10.0, Param.ABS_MS ); static_pr.para[ PR_RELEASE ] = new Param( 1000.0, Param.ABS_MS ); static_pr.para[ PR_SPACING ] = new Param( 1000.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(); // -------- build GUI -------- final GridBagConstraints con; final PathField ggInputFile, ggCtrlFile, ggOutputFile; final PathField[] ggInputs; final ParamField ggDurUp, ggDurDown, ggThreshUp, ggThreshDown, ggAttack, ggRelease, ggSpacing; final ParamSpace[] spcDur, spcThresh; final Component[] ggGain; final JComboBox ggMode, ggChannelUp, ggChannelDown, ggSpacingType; gui = new GUISupport(); con = gui.getGridBagConstraints(); con.insets = new Insets( 1, 2, 1, 2 ); final ItemListener il = new ItemListener() { public void itemStateChanged( ItemEvent e ) { int ID = gui.getItemID( e ); switch( ID ) { case GG_SPACINGTYPE: pr.intg[ ID - GG_OFF_CHOICE ] = ((JComboBox) e.getSource()).getSelectedIndex(); reflectPropertyChanges(); 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, null ); ggCtrlFile = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD, "Select control file" ); ggCtrlFile.handleTypes( GenericFile.TYPES_SOUND ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel( new JLabel( "Control file", SwingConstants.RIGHT )); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggCtrlFile, GG_CTRLFILE, 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$F0Mur$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 -------- con.fill = GridBagConstraints.BOTH; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel( new GroupLabel( "Segmentation Settings", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); spcThresh = new ParamSpace[] { Constants.spaces[ Constants.ratioAmpSpace ], Constants.spaces[ Constants.decibelAmpSpace ] }; spcDur = new ParamSpace[] { Constants.spaces[ Constants.absMsSpace ] }; ggThreshUp = new ParamField( spcThresh ); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Up Thresh:", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggThreshUp, GG_THRESHUP, null ); ggDurUp = new ParamField( spcDur ); con.weightx = 0.1; gui.addLabel( new JLabel( "Min. Up Duration:", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addParamField( ggDurUp, GG_DURUP, null ); ggThreshDown = new ParamField( spcThresh ); con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Down Thresh:", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addParamField( ggThreshDown, GG_THRESHDOWN, null ); ggDurDown = new ParamField( spcDur ); con.weightx = 0.1; gui.addLabel( new JLabel( "Min. Down Duration:", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addParamField( ggDurDown, GG_DURDOWN, null ); ggChannelUp = new JComboBox(); for( int i = 0; i < CHANNEL_NAMES.length; i++ ) { ggChannelUp.addItem( CHANNEL_NAMES[ i ]); } con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Multi-channel Up Treatment:", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addChoice( ggChannelUp, GG_CHANNELUP, null ); ggAttack = new ParamField( spcDur ); con.weightx = 0.1; gui.addLabel( new JLabel( "Attack:", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addParamField( ggAttack, GG_ATTACK, null ); ggChannelDown = new JComboBox(); for( int i = 0; i < CHANNEL_NAMES.length; i++ ) { ggChannelDown.addItem( CHANNEL_NAMES[ i ]); } con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Multi-channel Down Treatment:", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addChoice( ggChannelDown, GG_CHANNELDOWN, null ); ggRelease = new ParamField( spcDur ); con.weightx = 0.1; gui.addLabel( new JLabel( "Release:", SwingConstants.RIGHT )); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addParamField( ggRelease, GG_RELEASE, null ); ggMode = new JComboBox(); for( int i = 0; i < MODE_NAMES.length; i++ ) { ggMode.addItem( MODE_NAMES[ i ]); } con.weightx = 0.1; con.gridwidth = 1; gui.addLabel( new JLabel( "Mode:", SwingConstants.RIGHT )); con.weightx = 0.4; gui.addChoice( ggMode, GG_MODE, null ); ggSpacingType = new JComboBox(); for( int i = 0; i < SPACING_NAMES.length; i++ ) { ggSpacingType.addItem( SPACING_NAMES[ i ]); } con.weightx = 0.1; con.gridwidth = 1; gui.addChoice( ggSpacingType, GG_SPACINGTYPE, il ); ggSpacing = new ParamField( spcDur ); con.weightx = 0.4; con.gridwidth = GridBagConstraints.REMAINDER; gui.addParamField( ggSpacing, GG_SPACING, 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() { // io AudioFile inF = null; AudioFile ctrlF = null; AudioFile outF = null; float gain = 1.0f; // gain abs amp final Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz float maxAmp = 0.0f; topLevel: try { // ---- open input, output; init ---- // input inF = AudioFile.openAsRead( new File( pr.text[ PR_INPUTFILE ])); final AudioFileDescr inStream = inF.getDescr(); final int inChanNum = inStream.channels; final long inLength = inStream.length; // this helps to prevent errors from empty files! if( (inLength < 1) || (inChanNum < 1) ) throw new EOFException( ERR_EMPTY ); ctrlF = AudioFile.openAsRead( new File( pr.text[ PR_CTRLFILE ])); final AudioFileDescr ctrlStream = ctrlF.getDescr(); final int ctrlChanNum = ctrlStream.channels; final long ctrlLength = ctrlStream.length; // this helps to prevent errors from empty files! if( (ctrlLength < 1) || (ctrlChanNum < 1) ) throw new EOFException( ERR_EMPTY ); // if( ctrlLength > inLength ) throw new IOException( ERR_LENGTH ); final double ctrlRate = (double) ctrlLength / (double) inLength; // ---- open output ---- final PathField ggOutput = (PathField) gui.getItemObj( GG_OUTPUTFILE ); if( ggOutput == null ) throw new IOException( ERR_MISSINGPROP ); final AudioFileDescr outStream = new AudioFileDescr( inStream ); ggOutput.fillStream( outStream ); outF = AudioFile.openAsWrite( outStream ); // .... check running .... if( !threadRunning ) break topLevel; final AudioFile tmpF; final boolean normalized = pr.intg[ PR_GAINTYPE ] == GAIN_UNITY; if( normalized ) { tmpF = createTempFile( outStream ); } else { gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ampRef, null )).value; tmpF = null; } // .... check running .... if( !threadRunning ) break topLevel; long progOff = 0L; final long progLen = ctrlLength * (normalized ? 2 : 1); final float[][] inBuf = new float[ inChanNum ][ 8192 ]; final float[][] outBuf = new float[ inChanNum ][ 8192 ]; final float[][] ctrlBuf = new float[ ctrlChanNum ][ 8192 ]; final float[] ctrlFrame = new float[ ctrlChanNum ]; final float upThresh = (float) (Param.transform( pr.para[ PR_THRESHUP ], Param.ABS_AMP, ampRef, null )).value; final float dnThresh = (float) (Param.transform( pr.para[ PR_THRESHDOWN ], Param.ABS_AMP, ampRef, null )).value; final long upCount = Math.max( 1L, (long) (AudioFileDescr.millisToSamples( inStream, pr.para[ PR_DURUP ].value) * ctrlRate + 0.5) ); final long dnCount = Math.max( 1L, (long) (AudioFileDescr.millisToSamples( inStream, pr.para[ PR_DURDOWN ].value) * ctrlRate + 0.5) ); final long fadeInFrames = (long) (AudioFileDescr.millisToSamples( inStream, pr.para[ PR_ATTACK ].value) + 0.5); final long fadeOutFrames = (long) (AudioFileDescr.millisToSamples( inStream, pr.para[ PR_RELEASE ].value) + 0.5); final ThreshFunc upFun; if( pr.intg[ PR_CHANNELUP ] == CHANNEL_MAX ) { upFun = new AboveFunc( new MaxFunc(), dnThresh ); } else { upFun = new AboveFunc( new MinFunc(), dnThresh ); } final ThreshFunc dnFun; if( pr.intg[ PR_CHANNELDOWN ] == CHANNEL_MAX ) { dnFun = new BelowFunc( new MaxFunc(), upThresh ); } else { dnFun = new BelowFunc( new MinFunc(), upThresh ); } final Track upTrack = new Track( "up", dnCount, upFun, pr.intg[ PR_MODE ] == MODE_UPPER ); final Track dnTrack = new Track( "dn", upCount, dnFun, pr.intg[ PR_MODE ] == MODE_DOWN ); upTrack.other = dnTrack; dnTrack.other = upTrack; Track current = dnTrack.write ? upTrack : dnTrack; if( verbose ) System.out.println( "--> initial = " + current.name ); final List<Fade> fades = new ArrayList<Fade>(); // long framesRead = 0L; long ctrlRead = 0L; long framesWritten = 0L; // long framesSkipped = 0L; long writeStart = 0L; long gagaFrame = 0L; Fade currentFade = null; final boolean spcOrig = pr.intg[ PR_SPACINGTYPE ] == SPACING_ORIGINAL; final long spcFrames = spcOrig ? 0L : (long) (AudioFileDescr.millisToSamples( inStream, pr.para[ PR_SPACING ].value) + 0.5); //final long spacing = fadeOutFrames; // 0L; // this could be another parameter one day... while( threadRunning && ctrlRead < ctrlLength ) { final int len = (int) Math.min( 8192, ctrlLength - ctrlRead ); ctrlF.readFrames( ctrlBuf, 0, len ); for( int i = 0; i < len; i++ ) { for( int ch = 0; ch < ctrlChanNum; ch++ ) { ctrlFrame[ ch ] = ctrlBuf[ ch ][ i ]; } final boolean inTrack = current.fun.thresh( ctrlFrame ); // boolean advance = current.write; // final boolean oldWrite = current.write; final long ctrlPos = ctrlRead + i; final long inFramePos = (long) (ctrlPos / ctrlRate + 0.5); if( inTrack ) { current.offCount = 0L; // begin frames-outside-thresh ("off"-track) counting again from zero if( verbose ) System.out.println( "@" + ctrlPos + " : inTrack = " + inTrack ); } else { if( current.offCount == 0L ) { current.offFrame = inFramePos; // store beginning pos of going off-track } current.offCount++; if( verbose ) System.out.println( "@" + ctrlPos + " : inTrack = " + inTrack + ", offCount = " + current.offCount + " (" + current.maxCount + ")" ); if( current.offCount >= current.maxCount ) { // enough off-track frames to switch track if( current.write ) { // processTo( inFrame1 ); currentFade.out( current.offFrame, fadeOutFrames ); fades.add( currentFade ); // fades.add( new Fade( inF, inBuf, inFrame1, inFrame1 - framesSkipped, 1f, 0f, fadeOutFrames )); // advance = false; // gagaFrame += inFramePos - currentFade.inFadeInOffset + spacing; gagaFrame += inFramePos - writeStart + spcFrames; currentFade = null; final long len20 = Math.max( 0L, gagaFrame - fadeInFrames - framesWritten ); final long len2 = spcOrig ? Math.min( inLength - framesWritten, len20 ) : len20; if( verbose ) System.out.println( "--> write( " + framesWritten + ", " + len2 + " ; gagaFrame = " + gagaFrame ); maxAmp = writeFrames( tmpF == null ? outF : tmpF, framesWritten, len2, outBuf, fades, maxAmp, normalized, gain ); framesWritten += len2; } else { // writeStart = framesWritten; // inFramePos; // final long inFrame1 = Math.max( 0L, (long) (current.offFrame / ctrlRate + 0.5) - fadeInFrames ); final long inOffset = Math.max( 0L, current.offFrame - fadeInFrames ); final long fadeIn2 = Math.min( fadeInFrames, current.offFrame - inOffset ); writeStart = inOffset + fadeIn2; if( spcOrig ) { final long tmp = inOffset + fadeIn2; if( tmp < gagaFrame ) throw new IllegalArgumentException( "Ooops. Assertion" ); gagaFrame = tmp; } currentFade = new Fade( inF, inBuf, inOffset, gagaFrame - fadeIn2, fadeIn2 ); // advance = true; } current = current.other; current.offCount = 0L; if( verbose ) System.out.println( "--> current = " + current.name ); } } } ctrlRead += len; progOff += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; ctrlF.close(); ctrlF = null; long maxNumFrames = 0L; if( currentFade != null ) fades.add( currentFade ); if( spcOrig ) { maxNumFrames = inLength - framesWritten; } else { for( int j = 0; j < fades.size(); j++ ) { final Fade f = (Fade) fades.get( j ); maxNumFrames = Math.max( maxNumFrames, f.remaining( framesWritten )); } } if( verbose ) System.out.println( "--> final write( " + framesWritten + ", " + maxNumFrames ); maxAmp = writeFrames( tmpF == null ? outF : tmpF, framesWritten, maxNumFrames, outBuf, fades, maxAmp, normalized, gain ); inF.close(); inF = null; // adjust gain if( normalized ) { gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, new Param( 1.0 / maxAmp, Param.ABS_AMP ), null )).value; normalizeAudioFile( tmpF, outF, inBuf, gain, 1.0f ); deleteTempFile( tmpF ); } setProgression( 1f ); // .... check running .... if( !threadRunning ) break topLevel; // ---- Finish ---- outF.close(); outF = null; // setProgression( 1.0f ); // inform about clipping/ low level maxAmp *= gain; handleClipping( maxAmp ); } catch( IOException e1 ) { setError( e1 ); } // ---- cleanup (topLevel) ---- if( outF != null ) { outF.cleanUp(); } if( inF != null ) { inF.cleanUp(); } if( ctrlF != null ) { ctrlF.cleanUp(); } } // process() // -------- private methods -------- private float writeFrames( AudioFile outF, long pos, long numFrames, float[][] outBuf, List fades, float maxAmp, boolean normalize, float gain ) throws IOException { final long stop = pos + numFrames; while( pos < stop ) { final int len2 = (int) Math.min( 8192, stop - pos ); Util.clear( outBuf ); for( int j = fades.size() - 1; j >= 0; j-- ) { final Fade f = (Fade) fades.get( j ); if( f.remaining( pos ) == 0L ) { fades.remove( j ); } else { f.process( outBuf, pos, 0, len2 ); } } maxAmp = Math.max( maxAmp, Util.maxAbs( outBuf, 0, len2 )); if( !normalize ) { Util.mult( outBuf, 0, len2, gain ); } outF.writeFrames( outBuf, 0, len2 ); pos += len2; } return maxAmp; } private interface CollapseFunc { float collapse(float[] chan); } private interface ThreshFunc { boolean thresh(float[] chan); } private static class MinFunc implements CollapseFunc { public float collapse( float[] chan ) { float v = chan[ 0 ]; for( int i = 0; i < chan.length; i++ ) v = Math.min( v, chan[ i ]); return v; } } private static class MaxFunc implements CollapseFunc { public float collapse( float[] chan ) { float v = chan[ 0 ]; for( int i = 0; i < chan.length; i++ ) v = Math.max( v, chan[ i ]); return v; } } private static class AboveFunc implements ThreshFunc { private final CollapseFunc collapse; private final float thresh; public AboveFunc( CollapseFunc collapse, float thresh ) { this.collapse = collapse; this.thresh = thresh; } public boolean thresh( float[] chan ) { return collapse.collapse( chan ) >= thresh; } } private static class BelowFunc implements ThreshFunc { private final CollapseFunc collapse; private final float thresh; public BelowFunc( CollapseFunc collapse, float thresh ) { this.collapse = collapse; this.thresh = thresh; } public boolean thresh( float[] chan ) { return collapse.collapse( chan ) < thresh; } } private static class Track { final String name; final long maxCount; final ThreshFunc fun; final boolean write; long offCount = 0L; long offFrame; Track other; public Track( String name, long maxCount, ThreshFunc fun, boolean write ) { this.name = name; this.maxCount = maxCount; this.fun = fun; this.write = write; } } private static class Fade { private final AudioFile af; private final float[][] inBuf; private final long inOffset; // this is the absolute offset in the input file private long fadeOutOffset = -1L; // this is the relative offset of the fadeout begin from the chunk start private final long outOffset; private final long fadeInFrames; private long fadeOutFrames; private long fadeOutStop; // private long processed = 0L; private final long fileNumFrames; // private final long maxChunkLen; public Fade( AudioFile af, float[][] inBuf, long inOffset, long outOffset, long numFrames ) throws IOException { this.af = af; this.inBuf = inBuf; this.inOffset = inOffset; this.outOffset = outOffset; this.fadeInFrames = numFrames; fileNumFrames = af.getFrameNum(); // maxChunkLen = af.getFrameNum() - inOffset; } public void out( long inPos, long numFrames ) { if( fadeOutOffset >= 0 ) throw new IllegalStateException( "Duplicate call" ); if( inPos < inOffset ) throw new IllegalArgumentException( "" + inOffset + " -> " + inPos ); fadeOutOffset = inPos - inOffset; fadeOutFrames = numFrames; fadeOutStop = Math.min( fileNumFrames - inOffset, fadeOutOffset + fadeOutFrames ); } public long remaining( long outPos ) throws IOException { return Math.max( 0L, (fadeOutOffset == -1L ? (fileNumFrames - inOffset) : fadeOutStop) - (outPos - outOffset) ); } public void process(float[][] outBuf, long outPos, int off, int len) throws IOException { if (verbose) System.out.println("process : " + outPos + " / " + off + " / " + len); fadeIn(outBuf, outPos, off, len); fadeOut(outBuf, outPos, off, len); } private void fadeIn( float[][] outBuf, long outPos, int off, int len ) throws IOException { final long start = outPos - outOffset; final long add = Math.max( 0L, -start ); if( add >= len ) return; final int addi = (int) add; // final long pos1 = pos + addi; final int off1 = off + addi; final int len1 = len - addi; final long start1 = start + addi; final long stop = fadeOutOffset == -1L ? start1 + len1 : Math.min( fadeOutOffset, start1 + len1 ); if( stop <= start1 ) return; final int len2 = (int) (stop - start1); final long inPos = inOffset + start1; final long add1 = Math.max( 0L, -inPos ); if( add >= len2 ) return; final int addi1 = (int) add1; final int off2 = off1 + addi1; final int len3 = len2 - addi1; final long start2 = start1 + addi1; final long inPos1 = inPos + addi1; //System.out.println( " start " + start + ", add " + add + ", off1 " + off1 + ", len1 " + len1 + ", start1 " + start1 + ", len2 " + len2 + ", inPos " + inPos ); if( af.getFramePosition() != inPos1 ) af.seekFrame( inPos1 ); af.readFrames( inBuf, 0, len3 ); for( int i = 0, j = off2; i < len3; i++, j++ ) { final float f = Math.max( 0f, Math.min( 1f, (float) (start2 + i) / (float) fadeInFrames )); for( int ch = 0; ch < outBuf.length; ch++ ) { outBuf[ ch ][ j ] += inBuf[ ch ][ i ] * f; } } } private void fadeOut( float[][] outBuf, long outPos, int off, int len ) throws IOException { if( fadeOutOffset == -1L ) return; final long start = outPos - (outOffset + fadeOutOffset); final long add = Math.max( 0L, -start ); if( add >= len ) return; final int addi = (int) add; // final long pos1 = pos + addi; final int off1 = off + addi; final long start1 = start + addi; final long inPos = Math.min( fileNumFrames, inOffset + fadeOutOffset + start1 ); final int len1 = (int) Math.min( len - addi, fileNumFrames - inPos ); if( af.getFramePosition() != inPos ) af.seekFrame( inPos ); af.readFrames( inBuf, 0, len1 ); for( int i = 0, j = off1; i < len1; i++, j++ ) { final float f = Math.max( 0f, Math.min( 1f, 1f - ((float) (start1 + i) / (float) fadeOutFrames) )); for( int ch = 0; ch < outBuf.length; ch++ ) { outBuf[ ch ][ j ] += inBuf[ ch ][ i ] * f; } } } } protected void reflectPropertyChanges() { super.reflectPropertyChanges(); Component c; c = gui.getItemObj(GG_SPACING); if (c != null) { c.setEnabled(pr.intg[PR_SPACINGTYPE] == SPACING_FIXED); } } }