/* * ChangeGainDlg.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: * 07-Jan-05 uses indicateOutputWrite() * 07-Jun-05 printMarkers feature * 25-Aug-08 64bit frames savvy */ 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.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.Marker; import de.sciss.io.Region; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.EOFException; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** * Processing module for querying or changing a sound files volume. */ public class ChangeGainDlg 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_GAINTYPE = 2; private static final int PR_GAIN = 0; // pr.para 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 prText[] = { "", "" }; private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE }; private static final int prIntg[] = { 0, 0, GAIN_UNITY }; private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_GAINTYPE }; private static final Param prPara[] = { null }; private static final String prParaName[] = { PRN_GAIN }; 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_GAINTYPE = GG_OFF_CHOICE + PR_GAINTYPE; private static final int GG_GAIN = GG_OFF_PARAMFIELD + PR_GAIN; private static final int GG_INPUTFORMAT = GG_OFF_OTHER + 0; private static final int GG_FINDPEAK = GG_OFF_OTHER + 1; private boolean peakKnown = false; // true nach "FindPeak"; false nach neuer Input-Wahl private Param peakGain; // (abs amp) public boolean threadJustFind = false; // true = find peak; false = change gain private boolean printMarkers = false; private static PropertyArray static_pr = null; private static Presets static_presets = null; // -------- public methods -------- public ChangeGainDlg() { super("Change Gain"); 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.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; JLabel ggInputFormat; JButton ggFindPeak; Component[] ggGain; PathField[] ggInputs; gui = new GUISupport(); con = gui.getGridBagConstraints(); con.insets = new Insets( 1, 2, 1, 2 ); PathListener pathL = new PathListener() { public void pathChanged( PathEvent e ) { int ID = gui.getItemID( e ); switch( ID ) { case GG_INPUTFILE: clearInput(); break; } } }; ActionListener al = new ActionListener() { public void actionPerformed( ActionEvent e ) { int ID = gui.getItemID( e ); switch( ID ) { case GG_FINDPEAK: threadJustFind = true; printMarkers = (e.getModifiers() & (ActionEvent.META_MASK | ActionEvent.ALT_MASK)) != 0; clearInput(); start(); } } }; // -------- Input-Gadgets -------- con.fill = GridBagConstraints.BOTH; con.gridwidth = GridBagConstraints.REMAINDER; gui.addLabel(new GroupLabel("Waveform Input", 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("File:", SwingConstants.RIGHT)); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; gui.addPathField( ggInputFile, GG_INPUTFILE, pathL ); con.fill = GridBagConstraints.HORIZONTAL; ggFindPeak = new JButton( "Info" ); con.gridwidth = 1; con.weightx = 0.1; gui.addButton( ggFindPeak, GG_FINDPEAK, al ); ggInputFormat = new JLabel(); // JTextField(); ggInputFormat.setBorder ( BorderFactory.createEmptyBorder ( 0, 6, 0, 6 ) ); // ggInputFormat.setEditable(false); // ggInputFormat.setFocusable(false); // ggInputFormat.setBackground( null ); con.gridwidth = GridBagConstraints.REMAINDER; con.weightx = 0.9; // gui.addTextField( ggInputFormat, GG_INPUTFORMAT, al ); gui.addGadget(ggInputFormat, GG_INPUTFORMAT); // -------- Output-Gadgets -------- gui.addLabel( new GroupLabel( "Output", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_NONE )); 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$F0Gain$E" ); con.gridwidth = 1; con.weightx = 0.1; gui.addLabel(new JLabel("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); 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); initGUI(this, FLAGS_PRESETS | FLAGS_PROGBAR, gui); } public void fillGUI() { super.fillGUI(); super.fillGUI(gui); clearInput(); } public void fillPropertyArray() { super.fillPropertyArray(); super.fillPropertyArray(gui); } /** * Set new input file */ public void clearInput() { peakKnown = false; // gui.stringToJTextField("", GG_INPUTFORMAT); JLabel gg = (JLabel) gui.getItemObj(GG_INPUTFORMAT); gg.setText(""); } // -------- Processor Interface -------- protected void process() { int len; long progOff, progLen; float f1; boolean b1; // io AudioFile inF = null; AudioFile outF = null; AudioFileDescr inStream = null; AudioFileDescr outStream = null; int chanNum; float[][] inBuf = null; float[] convBuf1; // Synthesize float gain; // gain abs amp Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz // Smp Init long inLength; long framesRead; float maxAmp; double energy, power; int maxChan; long maxFrame; PathField ggOutput; boolean justFind, printMrks; boolean needsRead, needsWrite; long dispDelta, dispTime; topLevel: try { justFind = threadJustFind; // "Find Peak"-JButton acts like a JCheckBox, threadJustFind = false; // we have to "turn it off" ourself printMrks = printMarkers; printMarkers = false; // ---- open input ---- inF = AudioFile.openAsRead( new File( pr.text[ PR_INPUTFILE ])); inStream = inF.getDescr(); if( printMrks ) printMarkers( inStream ); chanNum = inStream.channels; inLength = (int) inStream.length; // this helps to prevent errors from empty files! if( chanNum * inLength < 1 ) throw new EOFException( ERR_EMPTY ); inBuf = new float[ chanNum ][ 8192 ]; needsRead = justFind || (!peakKnown && (pr.intg[ PR_GAINTYPE ] == GAIN_UNITY)); needsWrite = !justFind; progLen = (needsRead ? inLength : 0L) + (needsWrite ? inLength*2 : 0L); progOff = 0L; dispDelta = 10000; // first prelim. display after 10 sec. //dispDelta = 1000; // first prelim. display after 10 sec. dispTime = System.currentTimeMillis() + dispDelta; // ---- open output ---- if( needsWrite ) { 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; } // ---- first pass: get input gain peak ---- maxAmp = -1f; maxFrame = -1; maxChan = -1; energy = 0.0; if( needsRead ) { for( framesRead = 0; (framesRead < inLength) && threadRunning; ) { len = (int) Math.min( 8192, inLength - framesRead ); inF.readFrames( inBuf, 0, len ); for( int ch = 0; ch < chanNum; ch++ ) { convBuf1 = inBuf[ ch ]; for( int i = 0; i < len; i++ ) { f1 = convBuf1[ i ]; f1 *= f1; energy += f1; if( f1 >= maxAmp ) { final long frame = framesRead + i + 1; final int ch2 = ch + 1; if( f1 > maxAmp ) { maxAmp = f1; maxFrame= frame; maxChan = ch2; } else { if( maxFrame != frame ) { maxFrame = -Math.abs( maxFrame ); } if( maxChan != ch2 ) { maxChan = -Math.abs( maxChan ); } } } } } framesRead += len; progOff += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); // ---- update display after increasing time deltas and after last chunk ---- b1 = framesRead == inLength; if( (System.currentTimeMillis() > dispTime) || b1 ) { // inDur = AudioFileDescr.samplesToMillis( inStream, framesRead ) / 1000; // * chanNum power = energy / (framesRead * chanNum); // / inDur showPeak( inStream, Math.sqrt( maxAmp ), maxFrame, maxChan, power, b1 ); dispDelta <<= 1; dispTime += dispDelta; } } // .... check running .... if( !threadRunning ) break topLevel; peakGain = new Param( Math.sqrt( maxAmp ), Param.ABS_AMP ); peakKnown = true; if( needsWrite ) indicateOutputWrite(); } // ---- second pass: adjust gain ---- //long t1, t2; if( needsWrite ) { inF.seekFrame( 0 ); if( pr.intg[ PR_GAINTYPE ] == GAIN_ABSOLUTE ) { gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ampRef, null )).value; } else { gain = (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, new Param( 1.0 / peakGain.value, peakGain.unit ), null )).value; } if( peakKnown ) { maxAmp = (float) peakGain.value * (float) peakGain.value; } // ---- process loop ---- //t1 = System.currentTimeMillis(); for( framesRead = 0; (framesRead < inLength) && threadRunning; ) { len = (int) Math.min( 8192, inLength - framesRead ); inF.readFrames( inBuf, 0, len ); if( !peakKnown ) { for( int ch = 0; ch < chanNum; ch++ ) { convBuf1 = inBuf[ ch ]; for( int i = 0; i < len; i++ ) { f1 = convBuf1[ i ]; f1 *= f1; energy += f1; if( f1 >= maxAmp ) { final long frame = framesRead + i + 1; final int ch2 = ch + 1; if( f1 > maxAmp ) { maxAmp = f1; maxFrame= frame; maxChan = ch2; } else { if( maxFrame != frame ) { maxFrame = -Math.abs( maxFrame ); } if( maxChan != ch2 ) { maxChan = -Math.abs( maxChan ); } } } } } // ---- update display after increasing time deltas and after last chunk ---- b1 = framesRead == inLength; if( (System.currentTimeMillis() > dispTime) || b1 ) { // inDur = AudioFileDescr.samplesToMillis( inStream, framesRead ) / 1000; // * chanNum power = energy / (framesRead * chanNum); // / inDur showPeak( inStream, Math.sqrt( maxAmp ), maxFrame, maxChan, power, b1 ); dispDelta <<= 1; dispTime += dispDelta; } } // if !peakKnown framesRead += len; progOff += len; // ---- adjust gain + write ---- for( int ch = 0; ch < chanNum; ch++ ) { Util.mult( inBuf[ ch ], 0, len, gain ); } outF.writeFrames( inBuf, 0, len ); // framesWritten += len; progOff += len; // .... progress .... setProgression( (float) progOff / (float) progLen ); } // .... check running .... if( !threadRunning ) break topLevel; //t2 = System.currentTimeMillis(); //System.err.println( "took "+(t2-t1)+" millis." ); if( !peakKnown ) { peakGain = new Param( Math.sqrt( maxAmp ), Param.ABS_AMP ); peakKnown = true; } maxAmp *= gain * gain; // now maxInputAmp = maxOutputAmp outF.close(); outF = null; } // ---- Finish ---- inF.close(); inF = null; inStream = null; // inform about clipping if( !justFind && (maxAmp > 1.0f) && (pr.intg[ PR_GAINTYPE ] == GAIN_ABSOLUTE) ) { handleClipping( (float) Math.sqrt( maxAmp )); } } catch( IOException e1 ) { setError( e1 ); } catch( OutOfMemoryError e2 ) { inStream = null; outStream = null; inBuf = 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 -------- private static void printMarkers( AudioFileDescr afd ) { List markers = (List) afd.getProperty( AudioFileDescr.KEY_MARKERS ); List regions = (List) afd.getProperty( AudioFileDescr.KEY_REGIONS ); final List empty = new ArrayList(); Marker mark; Region region; if( markers == null ) markers = empty; if( regions == null ) regions = empty; System.out.println( "File: " + afd.file.getName() + "\n" + " # of Markers : " + markers.size()+ "; # of Regions : " + regions.size() ); System.out.println( "\nMarker names: " ); for( int i = 0; i < markers.size(); i++ ) { mark = (Marker) markers.get( i ); System.out.println( mark.name ); } System.out.println( "\nMarker times in sample frames: " ); for( int i = 0; i < markers.size(); i++ ) { mark = (Marker) markers.get( i ); System.out.println( mark.pos ); } System.out.println( "\nMarker times in seconds: " ); for( int i = 0; i < markers.size(); i++ ) { mark = (Marker) markers.get( i ); System.out.println( AudioFileDescr.samplesToMillis( afd, mark.pos ) / 1000 ); } System.out.println( "\nRegion names: " ); for( int i = 0; i < regions.size(); i++ ) { region = (Region) regions.get( i ); System.out.println( region.name ); } // System.out.println( "\nRegion times in sample frames: " ); // for( int i = 0; i < markers.size(); i++ ) { // mark = (Marker) markers.get( i ); // System.out.println( mark.pos ); // } // // System.out.println( "\nRegion times in seconds: " ); // for( int i = 0; i < markers.size(); i++ ) { // mark = (Marker) markers.get( i ); // System.out.println( AudioFileDescr.sampleToMillis( afd, mark.pos ) / 1000 ); // } } /* * Maximalen Ausschlag im InputFormat-Gadget anzeigen * * @param inStream re Sound * @param maxAmp der Peak * @param maxFrame Zeitpunkt des Peaks (+1) * @param maxChan Kanal des Peaks (+1) * @param power gemessene Durchschnitts-Leistung * @param complete false wenn noch nicht zu Ende berechnet */ private void showPeak( AudioFileDescr inStream, double maxAmp, long maxFrame, int maxChan, double power, boolean complete ) { String chanTxt; double absTime; int min; boolean b; absTime = (AudioFileDescr.samplesToMillis( inStream, Math.abs( maxFrame ) - 1 ) + 0.5) / 1000; min = (int) (absTime / 60); absTime %= 60; b = (maxChan < 0) || (inStream.channels == 1); chanTxt = b ? "" : (inStream.channels == 2 ? (maxChan == 1 ? "[L]" : "[R]") : ("[" + maxChan + "]")); // System.out.println( "maxAmp "+maxAmp+"; maxFrame "+maxFrame+"; maxChan "+maxChan+"; power "+power+"; complete "+complete ); // if( maxAmp > 0.0 ) { // power *= 1000.0 / (maxAmp * maxAmp); // "normalized" // } Double ampDB = 20 * Math.log(maxAmp) / Constants.ln10; Double rmsDB = 10 * Math.log(power ) / Constants.ln10; Integer cmpJComboBox = complete ? 1 : -1; Object[] msgArgs = { ampDB, rmsDB, cmpJComboBox, min, absTime, maxFrame}; String msgPtrn = "{2,choice,-1#[\u2026|0#}" + "Max amp {0,number,#,##0.0} dBFS @" + "{3,number,##0}:{4,number,00.000}" + "{5,choice,-1#+|0#}" + chanTxt + "; RMS {1,number,##,##0.0} dB" + "{2,choice,-1#\u2026]|0#}"; MessageFormat msgForm = new MessageFormat( msgPtrn ); msgForm.setLocale( Locale.US ); msgForm.applyPattern( msgPtrn ); // gui.stringToJTextField(msgForm.format(msgArgs), GG_INPUTFORMAT); final JLabel ggInfo = (JLabel) gui.getItemObj(GG_INPUTFORMAT); ggInfo.setText(msgForm.format(msgArgs)); } } // class ChangeGainDlg