/*
* SonagramExportDlg.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:
* 20-Dec-08 created
*/
package de.sciss.fscape.gui;
import de.sciss.fscape.io.GenericFile;
import de.sciss.fscape.io.ImageFile;
import de.sciss.fscape.io.ImageStream;
import de.sciss.fscape.prop.Presets;
import de.sciss.fscape.prop.PropertyArray;
import de.sciss.fscape.session.ModulePanel;
import de.sciss.fscape.spect.ConstQ;
import de.sciss.fscape.util.Constants;
import de.sciss.fscape.util.MathUtil;
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 approaching a file (fit input)
* throw evolution using a genetic algorithm.
*
* @todo image resolution (8-bit vs 16-bit) is not saved
*/
public class SonagramExportDlg
extends ModulePanel {
// -------- private variables --------
// Properties (defaults)
private static final int PR_OUTPUTFILE = 0; // pr.text
private static final int PR_INPUTFILE = 1;
private static final int PR_MAXFFTSIZE = 0; // pr.intg
private static final int PR_MINFREQ = 0; // pr.para
private static final int PR_MAXFREQ = 1;
private static final int PR_BANDSPEROCT = 2;
private static final int PR_TIMERES = 3;
private static final int PR_SIGNALCEIL = 4;
private static final int PR_NOISEFLOOR = 5;
// private static final int PR_READMARKERS = 0; // pr.bool
private static final String PRN_INPUTFILE = "InputFile";
private static final String PRN_OUTPUTFILE = "OutputFile";
private static final String PRN_MAXFFTSIZE = "MaxFFTSize";
private static final String PRN_MINFREQ = "MinFreq";
private static final String PRN_MAXFREQ = "MaxFreq";
private static final String PRN_BANDSPEROCT = "BandsPerOct";
private static final String PRN_TIMERES = "TimeReso";
private static final String PRN_SIGNALCEIL = "SignalCeil";
private static final String PRN_NOISEFLOOR = "NoiseFloor";
private static final String prText[] = { "", "" };
private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE };
private static final int prIntg[] = { 5 /* 8192 */ };
private static final String prIntgName[] = { PRN_MAXFFTSIZE };
private static final Param prPara[] = { null, null, null, null, null, null };
private static final String prParaName[] = { PRN_MINFREQ, PRN_MAXFREQ, PRN_BANDSPEROCT, PRN_TIMERES, PRN_SIGNALCEIL, PRN_NOISEFLOOR };
// private static final boolean prBool[] = { false };
// private static final String prBoolName[] = { PRN_READMARKERS };
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_MAXFFTSIZE = GG_OFF_CHOICE + PR_MAXFFTSIZE;
private static final int GG_MINFREQ = GG_OFF_PARAMFIELD + PR_MINFREQ;
private static final int GG_MAXFREQ = GG_OFF_PARAMFIELD + PR_MAXFREQ;
private static final int GG_BANDSPEROCT = GG_OFF_PARAMFIELD + PR_BANDSPEROCT;
private static final int GG_TIMERES = GG_OFF_PARAMFIELD + PR_TIMERES;
private static final int GG_SIGNALCEIL = GG_OFF_PARAMFIELD + PR_SIGNALCEIL;
private static final int GG_NOISEFLOOR = GG_OFF_PARAMFIELD + PR_NOISEFLOOR;
private static PropertyArray static_pr = null;
private static Presets static_presets = null;
private static final String ERR_MONO = "Audio file must be monophonic";
// -------- public methods --------
/**
* !! setVisible() bleibt dem Aufrufer ueberlassen
*/
public SonagramExportDlg()
{
super( "Sonogram Export" );
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_MINFREQ ] = new Param( 32.0, Param.ABS_HZ );
static_pr.para[ PR_MAXFREQ ] = new Param( 18000.0, Param.ABS_HZ );
static_pr.para[ PR_BANDSPEROCT ] = new Param( 12.0, Param.NONE );
static_pr.para[ PR_TIMERES ] = new Param( 20.0, Param.ABS_MS );
static_pr.para[ PR_SIGNALCEIL ] = new Param( 0.0, Param.DECIBEL_AMP );
static_pr.para[ PR_NOISEFLOOR ] = new Param( -96.0, Param.DECIBEL_AMP );
static_pr.paraName = prParaName;
// static_pr.superPr = DocumentFrame.static_pr;
}
// default preset
if( static_presets == null ) {
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, ggOutputFile;
final PathField[] ggInputs;
final ParamField ggMinFreq, ggMaxFreq, ggBandsPerOct, ggTimeRes;
final ParamField ggSignalCeil, ggNoiseFloor;
final JComboBox ggMaxFFTSize;
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_READMARKERS:
// pr.bool[ ID - GG_OFF_CHECKBOX ] = ((JCheckBox) e.getSource()).isSelected();
// 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 sound file" );
ggInputFile.handleTypes( GenericFile.TYPES_SOUND );
con.gridwidth = 1;
con.weightx = 0.1;
gui.addLabel( new JLabel( "Audio input", 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 image file" );
ggOutputFile.handleTypes( GenericFile.TYPES_IMAGE );
ggInputs = new PathField[ 1 ];
ggInputs[ 0 ] = ggInputFile;
ggOutputFile.deriveFrom( ggInputs, "$D0$F0Sono$E" );
con.gridwidth = 1;
con.weightx = 0.1;
gui.addLabel( new JLabel( "Image output", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
con.weightx = 0.9;
gui.addPathField( ggOutputFile, GG_OUTPUTFILE, null );
// -------- Plot Settings --------
gui.addLabel( new GroupLabel( "Settings", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE ));
ggMinFreq = new ParamField( Constants.spaces[ Constants.absHzSpace ]);
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Lowest Frequency:", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggMinFreq, GG_MINFREQ, null );
ggBandsPerOct = new ParamField( new ParamSpace( 1, /* 96 */ 32768, 1, Param.NONE ));
con.weightx = 0.1;
gui.addLabel( new JLabel( "Bands Per Octave:", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggBandsPerOct, GG_BANDSPEROCT, null );
ggMaxFreq = new ParamField( Constants.spaces[ Constants.absHzSpace ]);
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Highest Frequency:", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggMaxFreq, GG_MAXFREQ, null );
ggTimeRes = new ParamField( Constants.spaces[ Constants.absMsSpace ]);
con.weightx = 0.1;
gui.addLabel( new JLabel( "Max. Time Resolution:", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggTimeRes, GG_TIMERES, null );
ggSignalCeil = new ParamField( Constants.spaces[ Constants.decibelAmpSpace ]);
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Signal Ceiling:", SwingConstants.RIGHT ));
con.weightx = 0.4;
gui.addParamField( ggSignalCeil, GG_SIGNALCEIL, null );
ggMaxFFTSize = new JComboBox();
for( int i = 256; i <= 32768; i <<= 1 ) {
ggMaxFFTSize.addItem( String.valueOf( i ));
}
con.weightx = 0.1;
gui.addLabel( new JLabel( "Max. FFT Size:", SwingConstants.RIGHT ));
con.weightx = 0.4;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addChoice( ggMaxFFTSize, GG_MAXFFTSIZE, null );
ggNoiseFloor = new ParamField( Constants.spaces[ Constants.decibelAmpSpace ]);
con.weightx = 0.1;
con.gridwidth = 1;
gui.addLabel( new JLabel( "Noise Floor:", SwingConstants.RIGHT ));
con.weightx = 0.4;
// con.gridwidth = GridBagConstraints.REMAINDER;
gui.addParamField( ggNoiseFloor, GG_NOISEFLOOR, 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;
final long progLen;
AudioFile inF = null;
final AudioFileDescr inDescr;
final int inChanNum;
final long inLength;
// final Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz
final PathField ggOutput;
int chunkLen;
final ConstQ constQ;
final float boost = 1f; // 1000f;
final double minFreq = pr.para[ PR_MINFREQ ].value;
final double maxFreq = pr.para[ PR_MAXFREQ ].value;
final double timeRes = pr.para[ PR_TIMERES ].value;
final int bandsPerOct = (int) pr.para[ PR_BANDSPEROCT ].value;
final int maxFFTSize = 256 << pr.intg[ PR_MAXFFTSIZE ];
final boolean color = false;
final int bitsPerSmp;
ImageFile outF = null;
final ImageStream imgStream;
final byte[] row;
final int overlapSize;
final int width, height;
final int inBufSize;
final float[][] inBuf;
final int fftSize;
final int stepSize;
final int numKernels;
final float[] kernel;
// final float[] hsb = new float[ 3 ];
final double signalCeil = pr.para[ PR_SIGNALCEIL ].value; // (Param.transform( pr.para[ PR_SIGNALCEIL ], Param.ABS_AMP, ampRef, null )).value;
final double noiseFloor = pr.para[ PR_NOISEFLOOR ].value; // (Param.transform( pr.para[ PR_NOISEFLOOR ], Param.ABS_AMP, ampRef, null )).value;
final double dynamic = signalCeil - noiseFloor;
int rgb;
int winSize, inOff;
long framesRead;
// float brightness;
topLevel: try {
// ---- open input, output; init ----
// ptrn input
inF = AudioFile.openAsRead( new File( pr.text[ PR_INPUTFILE ]));
inDescr = inF.getDescr();
inChanNum = inDescr.channels;
inLength = inDescr.length;
// this helps to prevent errors from empty files!
if( (inLength < 1) || (inChanNum < 1) ) throw new EOFException( ERR_EMPTY );
if( inChanNum != 1 ) throw new EOFException( ERR_MONO );
// .... check running ....
if( !threadRunning ) break topLevel;
// if( inChanNum > 1 ) {
// System.out.println( "WARNING: Multichannel input. Using mono mix for mosaic correlation!" );
// }
// ---- further inits ----
constQ = new ConstQ();
constQ.setSampleRate( inDescr.rate );
constQ.setMinFreq( (float) minFreq );
constQ.setMaxFreq( (float) maxFreq );
constQ.setBandsPerOct( bandsPerOct );
constQ.setMaxFFTSize( maxFFTSize );
constQ.setMaxTimeRes( (float) timeRes );
constQ.createKernels();
fftSize = constQ.getFFTSize();
numKernels = constQ.getNumKernels();
winSize = fftSize; // << 1;
stepSize = (int) (AudioFileDescr.millisToSamples( inDescr, timeRes ) + 0.5);
overlapSize = fftSize - stepSize;
height = (int) ((inLength + stepSize - 1) / stepSize);
width = numKernels;
//System.out.println( "w " + width + "; h " + height + "; winSize " + winSize + "; inLength " + inLength );
ggOutput = (PathField) gui.getItemObj( GG_OUTPUTFILE );
if( ggOutput == null ) throw new IOException( ERR_MISSINGPROP );
outF = new ImageFile( pr.text[ PR_OUTPUTFILE ], GenericFile.MODE_OUTPUT | ggOutput.getType() );
imgStream = new ImageStream();
imgStream.bitsPerSmp = 8; // ??? fillStream might not work correctly?
ggOutput.fillStream( imgStream );
imgStream.width = width;
imgStream.height = height;
imgStream.smpPerPixel = /* color ? 3 :*/ 1;
bitsPerSmp = imgStream.bitsPerSmp;
outF.initWriter( imgStream );
row = outF.allocRow();
inBufSize = Math.max( 8192, fftSize );
inBuf = new float[ inChanNum ][ inBufSize ];
kernel = new float[ numKernels ];
progLen = height;
progOff = 0;
// ----==================== processing loop ====================----
framesRead = 0;
inOff = 0;
//final java.util.Random rnd = new java.util.Random();
for( int y = 0; y < height; y++ ) {
if( inOff < 0 ) {
inF.seekFrame( Math.min( inF.getFrameNum(), inF.getFramePosition() - inOff ));
inOff = 0;
}
// read
chunkLen = (int) Math.min( inLength - framesRead, winSize - inOff );
//System.out.println( "readFrames " + inOff + " -> " + chunkLen );
inF.readFrames( inBuf, inOff, chunkLen );
if( (inOff + chunkLen) < winSize ) {
Util.clear( inBuf, inOff + chunkLen, winSize - (inOff + chunkLen) );
}
// transform
constQ.transform( inBuf[ 0 ], 0, winSize, kernel, 0 );
for( int x = 0; x < width; x++ ) {
kernel[ x ] = (float) ((Math.min( signalCeil, (Math.max( noiseFloor,
MathUtil.linearToDB( kernel[ x ] * boost )))) - noiseFloor) / dynamic);
// kernel[ x ] = rnd.nextFloat();
}
if( color ) {
throw new IllegalStateException( "Color not yet implemented" );
// if( bitsPerSmp == 8 ) {
// for( int x = 0; x < width; x++ ) {
//
// }
// } else {
// for( int x = 0; x < width; x++ ) {
//
// }
// }
} else {
if( bitsPerSmp == 8 ) {
for( int x = 0; x < width; x++ ) {
row[ x ] = (byte) (kernel[ x ] * 0xFF + 0.5f);
}
} else {
for( int x = 0, cnt = 0; x < width; x++ ) {
rgb = (int) (kernel[ x ] * 0xFFFF + 0.5f);
row[ cnt++ ] = (byte) (rgb >> 8);
row[ cnt++ ] = (byte) rgb;
}
}
}
outF.writeRow( row );
// handle overlap
//System.out.println( "inBuf : " + inBuf[0].length + "; stepSize = " + stepSize + "; overlap = " + overlapSize );
if( overlapSize > 0 ) Util.copy( inBuf, stepSize, inBuf, 0, overlapSize );
inOff = overlapSize;
framesRead += chunkLen;
progOff++;
setProgression( (float) progOff / (float) progLen );
// .... check running ....
if( !threadRunning ) break topLevel;
} // for x
inF.close();
inF = null;
outF.close();
outF = null;
}
catch( IOException e1 ) {
setError( e1 );
}
catch( OutOfMemoryError e2 ) {
setError( new Exception( ERR_MEMORY ));
}
// ---- cleanup (topLevel) ----
if( outF != null ) outF.cleanUp();
if( inF != null ) inF.cleanUp();
} // process()
}