/*
* ModulePanel.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 removed deprecated sound file methods ;
* new method indicateOutputWrite()
* 29-May-05 temp file creation / deletion
* 24-Jun-06 renamed from ProcessWindow to DocumentFrame
* 31-Aug-06 fixed weird race condition constructor init bug
*/
package de.sciss.fscape.session;
import de.sciss.app.BasicEvent;
import de.sciss.app.EventManager;
import de.sciss.common.ProcessingThread;
import de.sciss.fscape.Application;
import de.sciss.fscape.gui.EnvIcon;
import de.sciss.fscape.gui.GUISupport;
import de.sciss.fscape.gui.MarginBorderLayout;
import de.sciss.fscape.gui.ParamField;
import de.sciss.fscape.gui.PathField;
import de.sciss.fscape.gui.ProcessPanel;
import de.sciss.fscape.gui.ProgressPanel;
import de.sciss.fscape.io.FloatFile;
import de.sciss.fscape.io.GenericFile;
import de.sciss.fscape.proc.Processor;
import de.sciss.fscape.proc.ProcessorAdapter;
import de.sciss.fscape.proc.ProcessorEvent;
import de.sciss.fscape.proc.ProcessorListener;
import de.sciss.fscape.prop.BasicProperties;
import de.sciss.fscape.prop.Presets;
import de.sciss.fscape.prop.PropertyArray;
import de.sciss.fscape.util.Constants;
import de.sciss.fscape.util.Param;
import de.sciss.gui.GUIUtil;
import de.sciss.gui.MenuAction;
import de.sciss.gui.MenuItem;
import de.sciss.gui.MenuRoot;
import de.sciss.gui.ProgressComponent;
import de.sciss.io.AudioFile;
import de.sciss.io.AudioFileDescr;
import de.sciss.io.IOUtil;
import de.sciss.util.Flag;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.prefs.Preferences;
/**
* Superclass of all processing windows. This handles thread and progress bar
* management and has some utility methods such as sound file normalization.
*/
public abstract class ModulePanel
extends JPanel
implements Processor, EventManager.Processor, ProgressComponent {
// -------- public variables --------
public static final String PACKAGE = "de.sciss.fscape.gui";
public static final String PROP_CLASS = "Class";
// -------- private variables --------
protected static final int FLAGS_TOOLBAR = 0x01;
protected static final int FLAGS_PRESETS = 0x02; // 0x03; // (0x02 + FLAGS_TOOLBAR)
protected static final int FLAGS_PROGBAR = 0x04;
protected static final int FLAGS_NORESIZE = 0x08;
protected static final int FLAGS_NOPRESETLOAD = 0x10;
protected static final int FLAGS_PROGBARASYNC = 0x24; // (0x20 + FLAGS_PROGBAR)
protected static final int GGTYPE_GAIN = 0;
protected static final int GAIN_UNITY = 0;
protected static final int GAIN_ABSOLUTE = 1;
protected static final String PRN_GAINTYPE = "GainType";
protected static final String PRN_GAIN = "Gain";
protected static final int GG_OFF_CHECKBOX = 0x000000;
protected static final int GG_OFF_CHOICE = 0x000100;
protected static final int GG_OFF_PARAMFIELD= 0x000200;
protected static final int GG_OFF_TEXTFIELD = 0x000300;
protected static final int GG_OFF_PATHFIELD = 0x000400;
protected static final int GG_OFF_FONTFIELD = 0x000500;
protected static final int GG_OFF_COLORCHOICE=0x000600;
protected static final int GG_OFF_ENVICON = 0x000700;
protected static final int GG_OFF_OTHER = 0x000800;
private ProcessPanel pp = null;
private ProgressPanel pProgress = null;
protected GUISupport gui;
private MenuAction actionDeletePreset = null;
/*
* Subclassen muessen dieses pr ueberschreiben und super.static_pr in superPr eintragen!
* presets mussen sie mit ihrem static_presets ueberschreiben
*/
protected PropertyArray pr;
protected Presets presets;
public static final String ERR_CORRUPTED = "Internal data corrupted. Please report bug!";
protected static final String ERR_MEMORY = "FScape ran out of memory";
protected static final String ERR_NOPROPERTIES = "There are no properties...";
protected static final String TXT_OBSCURE = "> Obscure?! ";
protected static final String ERR_CLASS = "This chosen file was created\nby a different module:\n";
protected static final String TXT_SIGH = ">-Sigh-";
protected static final String ERR_MISSINGPROP = "Bug! Missing property!";
protected static final String ERR_EMPTY = "File is empty";
protected static final String ERR_FRAMESYNC = "Bug! Frame sync lost!";
protected static final String ERR_COMPLEX = "Real and imaginary file must\nhave same # of channels";
/*
* Processor-Interface
*/
protected boolean threadRunning = false;
protected boolean threadPausing = false;
private float progress = 0.0f;
private final EventManager elm = new EventManager( this );
private int listenerCount = 0;
private Exception threadError = null;
private boolean clipping;
private float maxAmp;
/*
* Gadget goodies
*/
private ParamField ggGain = null;
private JComboBox ggGainType = null;
private static final Color COLOR_NORM = new Color( 0xFF, 0xFF, 0x00, 0x2F );
private final ModulePanel enc_this = this;
private final List<AudioFile> collTempFiles = new ArrayList<AudioFile>(); // Elements = AudioFile instances
// private final ActionClose actionClose;
// private final ActionSave actionSave;
// private final ActionSaveAs actionSaveAs;
private final Session doc;
// private final BasicApplication app;
// private final AbstractWindow.Adapter winListener;
// private final ActionShowWindow actionShowWindow;
private final JLabel lbWriteProtected;
private boolean writeProtected = false;
private boolean wpHaveWarned = false;
private final ProcessingThread.Listener closeAfterSaveListener;
private final String procTitle;
private boolean disposed = false;
// -------- public --------
public ModulePanel(String procTitle) {
// super(new BorderLayout(0, 6));
super(new MarginBorderLayout(0, 6, new Insets(0, 4, 4, 4)));
// this has no effect??
// setBorder(BorderFactory.createEmptyBorder(0, 4, 4, 4)); // top left bottom right
// setBorder(BorderFactory.createMatteBorder(20, 24, 24, 24, Color.red)); // top left bottom right
this.procTitle = procTitle;
this.doc = new Session();
doc.setFrame(this);
final MenuRoot mr;
// -------- Basic Listeners --------
closeAfterSaveListener = new ProcessingThread.Listener() {
public void processStarted( ProcessingThread.Event e ) {}
public void processStopped( ProcessingThread.Event e )
{
if( e.isDone() ) {
documentClosed();
}
}
};
// winListener = new AbstractWindow.Adapter() {
// public void windowClosing( AbstractWindow.Event e ) {
// actionClose.perform();
// }
//
// public void windowActivated( AbstractWindow.Event e )
// {
// // need to check 'disposed' to avoid runtime exception in doc handler if document was just closed
// if( !disposed ) {
// app.getDocumentHandler().setActiveDocument( enc_this, doc );
// app.getMenuFactory().setSelectedWindow( actionShowWindow );
// ((BasicWindowHandler) app.getWindowHandler()).setMenuBarBorrower( enc_this );
// }
// }
// };
// this.addListener( winListener );
lbWriteProtected = new JLabel();
// --- Actions ---
// actionClose = new ActionClose();
// actionSave = new ActionSave();
// actionSaveAs = new ActionSaveAs( false, false );
//
// actionShowWindow = new ActionShowWindow();
// setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE );
// ---- menus and actions ----
// mr = app.getMenuBarRoot();
//
// mr.putMimic( "file.close", this, actionClose );
// mr.putMimic( "file.save", this, actionSave );
// mr.putMimic( "file.saveAs", this, actionSaveAs );
//
// mr.putMimic( "edit.undo", this, doc.getUndoManager().getUndoAction() );
// mr.putMimic( "edit.redo", this, doc.getUndoManager().getRedoAction() );
//
// mr.putMimic( "help.module", this, new ActionHelp( procTitle + " " + getResourceString( "menuHelp" ), null ));
}
public String getModuleName() { return procTitle; }
// hallelujah this was a weird bug, calling back subclass methods in the constructor
// is a really bad idea. for preliminary fix, subclass must now call init2 after
// return from super in the constructor !!!
protected void init2()
{
buildGUI();
// AbstractWindowHandler.setDeepFont( getContentPane() );
// app.getMenuFactory().addToWindowMenu( actionShowWindow ); // MUST BE BEFORE INIT()!!
// init();
// updateTitle();
}
protected void buildGUI()
{
}
protected boolean restoreVisibility()
{
return false;
}
protected boolean alwaysPackSize()
{
return false;
}
protected boolean autoUpdatePrefs()
{
return true;
}
protected void fillDefaultAudioDescr( int[] intg, int typeIdx )
{
fillDefaultAudioDescr( intg, typeIdx, -1, -1 );
}
protected void fillDefaultAudioDescr( int[] intg, int typeIdx, int resIdx )
{
fillDefaultAudioDescr( intg, typeIdx, resIdx, -1 );
}
protected void fillDefaultAudioDescr( int[] intg, int typeIdx, int resIdx, int rateIdx )
{
final Preferences prefs = Application.userPrefs;
if( typeIdx >= 0 ) {
final int typ = GenericFile.getType( prefs.get( "audioFileType", "" ));
for( int idx = 0; idx < GenericFile.TYPES_SOUND.length; idx++ ) {
if( GenericFile.TYPES_SOUND[ idx ] == typ ) {
intg[ typeIdx ] = idx;
break;
}
}
}
if( resIdx >= 0 ) {
final int idx = PathField.getSoundResIdx( prefs.get( "audioFileRes", "" ));
if( idx >= 0 ) {
intg[ resIdx ] = idx;
}
}
if( rateIdx >= 0 ) {
final int idx = PathField.getSoundRateIdx( prefs.get( "audioFileRate", "" ));
if( idx >= 0 ) {
intg[ rateIdx ] = idx;
}
}
}
protected void fillDefaultGain( Param[] para, int gainIdx )
{
para[ gainIdx ] = getDefaultGain();
}
protected Param getDefaultGain()
{
final Preferences prefs = Application.userPrefs;
final de.sciss.util.Param gainP = de.sciss.util.Param.fromPrefs( prefs, "headroom", null );
if( gainP != null ) {
return new Param( gainP.val, Param.DECIBEL_AMP );
} else {
return null;
}
}
public Session getDocument()
{
return doc;
}
// /**
// * Recreates the main frame's title bar
// * after a sessions name changed (clear/load/save as session)
// */
// public void updateTitle()
// {
// final File f = doc.getFile();
// final String name = doc.getName();
// final Icon icn;
//
// writeProtected = false;
//
// if( f != null ) {
// try {
// writeProtected |= !f.canWrite() || ((f.getParentFile() != null) && !f.getParentFile().canWrite());
// } catch( SecurityException e ) {}
// }
//
// if( writeProtected ) {
// icn = GUIUtil.getNoWriteIcon();
// if( lbWriteProtected.getIcon() != icn ) {
// lbWriteProtected.setIcon( icn );
// }
// } else if( lbWriteProtected.getIcon() != null ) {
// lbWriteProtected.setIcon( null );
// }
//
//final String title = procTitle + (doc.isDirty() ? " - \u2022" : " - " ) + name;
//setTitle( title );
// actionShowWindow.putValue( Action.NAME, title );
//
// actionSave.setEnabled( !writeProtected );
// setDirty( doc.isDirty() );
//
// if( writeProtected && !wpHaveWarned && doc.isDirty() ) {
// JOptionPane.showMessageDialog( getWindow(), getResourceString( "warnWriteProtected" ),
// getResourceString( "msgDlgWarn" ), JOptionPane.WARNING_MESSAGE, null );
// wpHaveWarned = true;
// }
// }
private String createPresetMenuID( String name )
{
return( "preset_" + name ); // warning: don't use period
}
private MenuItem createPresetMenuItem( String name )
{
return new MenuItem( createPresetMenuID( name ), new ActionRecallPreset( name, null ));
}
/**
* build GUI
* - call once before setVisible() !
* - invokes loading of default preset !
*
* @param concrete subclass
* @param flags FLAGS_...
* @param c die "Innereien" (i.d.R. ein GUISupport Panel)
*/
protected void initGUI(ModulePanel concrete, int flags, Component c) {
// final Container cp = getContentPane();
//
// cp.setLayout( new BorderLayout( 0, 2 ));
final JPanel cp = this;
// -------- Toolbar --------
if ((flags & FLAGS_TOOLBAR) != 0) {
JPanel toolBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 2));
// if( (flags & FLAGS_PRESETS) != 0 ) {
// actionDeletePreset = new ActionDeletePreset("Delete Preset", null);
// final List presetNames = getPresets().presetNames();
// final MenuGroup mg = getPresetMenu();
// mg.add(this, new MenuItem("store", new ActionAddPreset("Add Preset", null)));
// mg.add( this, new MenuItem( "delete", actionDeletePreset ));
// mg.addSeparator( this );
// for( int i = 0; i < presetNames.size(); i++ ) {
// final String pstName = presetNames.get( i ).toString();
// mg.add( this, createPresetMenuItem( pstName ));
// }
// presetNames.remove( Presets.DEFAULT );
// actionDeletePreset.setEnabled( !presetNames.isEmpty() );
// }
cp.add(toolBar, BorderLayout.NORTH );
}
// -------- internals --------
cp.add(c, BorderLayout.CENTER);
if ((flags & FLAGS_NOPRESETLOAD) == 0) {
loadPreset(Presets.DEFAULT);
}
// -------- Close/Process Gadgets --------
if ((flags & FLAGS_PROGBAR) != 0) {
pProgress = new ProgressPanel();
pp = new ProcessPanel(((flags & FLAGS_PROGBARASYNC) == FLAGS_PROGBARASYNC ?
ProcessPanel.TYPE_ASYNC : 0), pProgress, this);
pp.addProcessorListener(new ProcessorAdapter() {
public void processorStopped(ProcessorEvent e) {
if (isVisible()) {
Exception procErr = getError();
if (procErr != null) {
displayError(procErr, getTitle());
}
if (clipping) {
clippingDlg();
}
}
}
});
cp.add(pp, BorderLayout.SOUTH);
}
// cp.setBorder(BorderFactory.createEmptyBorder(10, 14, 14, 14)); // top left bottom right
}
public ProcessingThread closeDocument(boolean force, Flag wasClosed) {
if (!force) {
final ProcessingThread pt = confirmUnsaved(getResourceString("menuClose"), wasClosed);
if (pt != null) {
pt.addListener(closeAfterSaveListener);
return pt;
}
}
if (wasClosed.isSet()) {
documentClosed();
}
return null;
}
protected void documentClosed() {
if (!disposed) {
disposed = true; // important to avoid "too late window messages" to be processed; fucking swing doesn't kill them despite listener being removed
// this.removeListener( winListener );
// app.getDocumentHandler().removeDocument( this, doc ); // invokes doc.dispose() and hence this.dispose()
// dispose();
Application.documentHandler.close(getDocument());
}
}
public void dispose()
{
// app.getMenuFactory().removeFromWindowMenu( actionShowWindow );
// super.dispose();
// final Window w = SwingUtilities.getWindowAncestor(this);
// if (w != null) w.dispose();
}
protected String getResourceString( String key )
{
return key;
}
/*
* Checks if there are unsaved changes to
* the session. If so, displays a confirmation
* dialog. Invokes Save/Save As depending
* on user selection. IF the doc was not dirty,
* or if "Cancel" or
* "Don't save" was chosen, the
* method returns <code>null</code> and the
* <code>confirmed</code> flag reflects whether
* the document should be closed. If a saving
* process should be started, that process is
* returned. Note that the <code>ProcessingThread</code>
* in this case has not yet been started, as to
* allow interested objects to install a listener
* first. So it's their job to call the <code>start</code>
* method!
*
* @param actionName name of the action that
* threatens the session
* @param confirmed a flag that will be set to <code>true</code> if
* the doc is allowed to be closed
* (doc was not dirty or user chose "Don't save"),
* otherwise <code>false</code> (save process
* initiated or user chose "Cancel").
* @return a saving process yet to be started or <code>null</code>
* if the doc needn't/shouldn't be saved
*
* @see de.sciss.eisenkraut.util.ProcessingThread#start
*/
private ProcessingThread confirmUnsaved( String actionName, Flag confirmed )
{
if( !confirmAbortProc( actionName )) return null;
if( !doc.isDirty() ) {
confirmed.set( true );
return null;
}
final String[] options = { getResourceString( "buttonSave" ),
getResourceString( "buttonCancel" ),
getResourceString( "buttonDontSave" ) };
int choice;
File f = doc.getFile();
String name;
name = doc.getName();
choice = JOptionPane.showOptionDialog(getComponent(), procTitle + " (" + name + ") :\n" + getResourceString( "optionDlgUnsaved" ),
actionName, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null,
options, options[1] );
switch( choice ) {
case JOptionPane.CLOSED_OPTION:
case 1: // cancel
confirmed.set( false );
return null;
case 2: // don't save
confirmed.set( true );
return null;
case 0:
confirmed.set( false );
// if( (f == null) || writeProtected ) {
// f = actionSaveAs.query( f, false, false, null );
// }
// if( f != null ) {
//confirmed.set( actionSave.perform( actionSave.getValue( Action.NAME ).toString(), f, false, false ));
// return null;
// }
return null;
default:
assert false : choice;
return null;
}
}
public boolean isRunning() { return pp.getState() != ProcessPanel.STATE_STOPPED; }
private boolean confirmAbortProc(String actionName) {
if (pp.getState() == ProcessPanel.STATE_STOPPED) return true;
int choice;
String name;
name = doc.getName();
choice = JOptionPane.showOptionDialog(getComponent(), name + " :\n" + getResourceString("optionDlgAbortProc"),
actionName, JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
null, null);
switch (choice) {
case JOptionPane.CLOSED_OPTION:
case JOptionPane.NO_OPTION: // cancel
return false;
case JOptionPane.YES_OPTION: // abort
for (int i = 0; i < 20; i++) {
pp.stop();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// ignore
}
if (pp.getState() == ProcessPanel.STATE_STOPPED) return true;
}
return false;
default:
assert false : choice;
return false;
}
}
/**
* Besorgt die Parameter des Dialogs in Form eines PropertyArray Objekts
*/
public PropertyArray getPropertyArray()
{
return pr;
}
/**
* Liefert die Presets
*/
protected Presets getPresets()
{
return presets;
}
/*
* laedt Werte eines Presets ins GUI u. PropertyArray
*/
protected boolean loadPreset( String name )
{
PropertyArray pa;
Properties preset;
boolean visible = isVisible();
boolean success = false;
try {
pa = getPropertyArray();
preset = getPresets().getPreset(name);
pa.fromProperties(false, preset);
fillGUI();
success = true;
} catch (Exception e99) {
if( visible ) {
displayError( e99, getTitle() );
}
}
if( success ) {
presetsChanged();
}
return success;
}
/*
* Stores the current GUI values in a new preset
* of the given name.
*/
protected boolean addPreset( String name )
{
// fillPropertyArray();
// final PropertyArray pa = getPropertyArray();
// final Properties preset = pa.toProperties( false );
//
// if( !preset.isEmpty() ) { // erfolgreich?
// final Presets pst = getPresets();
// final boolean overwrite = pst.containsPreset( name );
// if( pst.setPreset( name, preset ) != null ) {
// if( !overwrite ) { // create new menu entry
// final List presetNames = pst.presetNames();
// final int idx = presetNames.indexOf( name ) + 3; // store / remove / sep
//// getPresetMenu()
// final MenuGroup mg = getPresetMenu();
// if( mg != null ) mg.add( this, createPresetMenuItem( name ), idx );
// if( actionDeletePreset != null ) actionDeletePreset.setEnabled( true );
// }
// presetsChanged();
// return true;
//
// } else {
// if( isVisible() ) displayError( new IllegalStateException( ERR_CORRUPTED ), getTitle() );
// }
// } else {
// if( isVisible() ) {
// JOptionPane.showMessageDialog( getWindow(), ERR_NOPROPERTIES );
// }
// }
return false;
}
/*
* Deletes a preset
*/
protected boolean deletePreset( String name )
{
// final Presets pst = getPresets();
// if( pst.removePreset( name ) != null ) {
// final MenuGroup mg = getPresetMenu();
// if( mg != null ) mg.remove( this, mg.get( this, createPresetMenuID( name )));
// if( actionDeletePreset != null ) {
// final List presetNames = pst.presetNames();
// presetNames.remove( Presets.DEFAULT );
// if( presetNames.isEmpty() ) actionDeletePreset.setEnabled( false );
// }
// presetsChanged();
// return storePresetFile();
// } else {
// if( isVisible() ) displayError( new IllegalStateException( ERR_CORRUPTED ), getTitle() );
// return false;
// }
return false;
}
private boolean storePresetFile()
{
return false;
// boolean success = false;
//saveLoop: do {
// try {
// getPresets().store( true ); // save presets to harddisk
// success = true;
// break saveLoop;
// } catch( IOException e1 ) {
// int result = JOptionPane.showConfirmDialog( getWindow(),
// app.getResourceString( "errSavingPresets" ) + ":\n" +
// e1.getMessage() + "\n" + app.getResourceString( "optionDlgRetry" ),
// app.getResourceString( "optionDlgConfirm" ), JOptionPane.YES_NO_OPTION );
// if( result != 0 ) break saveLoop; // do not retry
// }
// } while( true );
// return success;
}
/**
* Settings laden
*
* @return true on success
*/
public boolean loadFile(File f) {
PropertyArray pa;
BasicProperties preset;
boolean visible = isVisible();
String str;
boolean success = false;
// if( visible ) GUISupport.sendComponentAsleep( this );
try {
pa = getPropertyArray();
preset = new BasicProperties( null, f );
try {
preset.load();
str = preset.getProperty( PROP_CLASS );
if( (str != null) && getClass().getName().endsWith( str )) {
pa.fromProperties( false, preset );
fillGUI();
success = true;
} else {
if( visible ) {
JOptionPane.showMessageDialog(getComponent(), ERR_CLASS + str );
}
}
} catch( IOException e1 ) {
if( visible ) displayError( e1, getTitle() );
}
// if( visible ) GUISupport.wakeComponent( this );
} catch( Exception e99 ) {
if( visible ) {
displayError( e99, getTitle() );
// GUISupport.wakeComponent( this );
}
}
if (success) {
fileChanged(f);
}
return success;
}
/*
* Settings speichern
*
* @return true on success
*/
public boolean saveFile(File f) {
PropertyArray pa;
BasicProperties preset;
boolean visible = isVisible();
boolean success = false;
// if( visible ) GUISupport.sendComponentAsleep( this );
try {
fillPropertyArray();
pa = getPropertyArray();
preset = new BasicProperties( null, f );
pa.toProperties( false, preset );
if( !preset.isEmpty() ) { // erfolgreich?
preset.setProperty( PROP_CLASS, getClass().getName() );
try {
preset.store( true );
success = true;
} catch( IOException e1 ) {
if( visible ) displayError( e1, getTitle() );
}
} else {
if( visible ) {
JOptionPane.showMessageDialog( getComponent(), ERR_NOPROPERTIES );
}
}
// if( visible ) GUISupport.wakeComponent( this );
} catch( Exception e99 ) {
if( visible ) {
displayError( e99, getTitle() );
// GUISupport.wakeComponent( this );
}
}
if (success) {
fileChanged(f);
}
return success;
}
private void fileChanged(File f) {
doc.setFile(f);
// updateTitle();
}
private void presetsChanged()
{
}
/**
* Transfer values from prop-array to GUI
* subclasses must override and invoke super.fillGUI() !
*/
public void fillGUI()
{
// nothing here yet
}
public boolean isThreadRunning()
{
return threadRunning;
}
private void setCheckBoxQuiet( JCheckBox cb, boolean selected )
{
final ActionListener[] al = cb.getActionListeners();
for( int i = 0; i < al.length; i++ ) {
cb.removeActionListener( al[ i ]);
}
try {
cb.setSelected( selected );
} finally {
for( int i = 0; i < al.length; i++ ) {
cb.addActionListener( al[ i ]);
}
}
}
private void setComboBoxQuiet( JComboBox cb, int idx )
{
final ActionListener[] al = cb.getActionListeners();
for( int i = 0; i < al.length; i++ ) {
cb.removeActionListener( al[ i ]);
}
try {
cb.setSelectedIndex( idx );
} finally {
for( int i = 0; i < al.length; i++ ) {
cb.addActionListener( al[ i ]);
}
}
}
/**
* Transfer values from prop-array to GUI
* subclasses can use this to make things easy
* VORAUSSETZUNG: Gadget-IDs in GUI stimmen mit denen in PropertyArray plus GG_OFF_...
* ueberein
*/
public void fillGUI( GUISupport g )
{
PropertyArray pa = getPropertyArray();
int i;
Component c;
try {
for( i = 0; i < pa.bool.length; i++ ) {
c = g.getItemObj( i + GG_OFF_CHECKBOX );
if( c != null ) {
this.setCheckBoxQuiet( (JCheckBox) c, pa.bool[ i ]);
}
}
for( i = 0; i < pa.intg.length; i++ ) {
c = g.getItemObj( i + GG_OFF_CHOICE );
if( (c != null) && (((JComboBox) c).getItemCount() > pa.intg[ i ]) ) {
this.setComboBoxQuiet( (JComboBox) c, pa.intg[ i ]);
}
}
for( i = 0; i < pa.para.length; i++ ) {
c = g.getItemObj( i + GG_OFF_PARAMFIELD );
if( c != null ) {
((ParamField) c).setParam( pa.para[ i ]);
}
}
for( i = 0; i < pa.text.length; i++ ) {
c = g.getItemObj( i + GG_OFF_TEXTFIELD );
if( c != null ) {
((JTextField) c).setText( pa.text[ i ]);
} else {
c = g.getItemObj( i + GG_OFF_PATHFIELD );
if( c != null ) {
((PathField) c).setPath( new File( pa.text[ i ]));
}
}
}
for( i = 0; i < pa.envl.length; i++ ) {
c = g.getItemObj( i + GG_OFF_ENVICON );
if( c != null ) {
((EnvIcon) c).setEnv( pa.envl[ i ]);
}
}
}
catch( ClassCastException e1 ) {
displayError( e1, getTitle() );
}
reflectPropertyChanges();
}
/**
* Transfer values from GUI to prop-array
* subclasses must override and invoke super.fillPropertyArray() !
*/
// protected void fillPropertyArray()
public void fillPropertyArray()
{
// nothing here yet
}
/**
* Transfer values from prop-array to GUI
* subclasses can use this to make things easy
* VORAUSSETZUNG: Gadget-IDs in GUI stimmen mit denen in PropertyArray plus GG_OFF_...
* ueberein
*/
protected void fillPropertyArray( GUISupport g )
{
PropertyArray pa = getPropertyArray();
int i;
Component c;
try {
for( i = 0; i < pa.bool.length; i++ ) {
c = g.getItemObj( i + GG_OFF_CHECKBOX );
if( c != null ) {
pa.bool[ i ] = ((JCheckBox) c).isSelected();
}
}
for( i = 0; i < pa.intg.length; i++ ) {
c = g.getItemObj( i + GG_OFF_CHOICE );
if( c != null ) {
pa.intg[ i ] = ((JComboBox) c).getSelectedIndex();
}
}
for( i = 0; i < pa.para.length; i++ ) {
c = g.getItemObj( i + GG_OFF_PARAMFIELD );
if( c != null ) {
pa.para[ i ] = ((ParamField) c).getParam();
}
}
for( i = 0; i < pa.text.length; i++ ) {
c = g.getItemObj( i + GG_OFF_TEXTFIELD );
if( c != null ) {
pa.text[ i ] = ((JTextField) c).getText();
} else {
c = g.getItemObj( i + GG_OFF_PATHFIELD );
if( c != null ) {
pa.text[ i ] = ((PathField) c).getPath().getPath();
}
}
}
for( i = 0; i < pa.envl.length; i++ ) {
c = g.getItemObj( i + GG_OFF_ENVICON );
if( c != null ) {
pa.envl[ i ] = ((EnvIcon) c).getEnv();
}
}
}
catch( ClassCastException e1 ) {
displayError( e1, getTitle() );
}
}
protected String getTitle() { return procTitle; }
/*
* Standard-Gadgets erstellen
*
* @param type GGTYPE_...
* @return Array mit den Gadgets. GGTYPE_GAIN: c[0]=ParamField,c[1]=JComboBox( GAIN_...)
*/
protected Component[] createGadgets(int type) {
final Component c[];
switch (type) {
case GGTYPE_GAIN:
final ParamField gg1 = new ParamField(Constants.spaces[Constants.decibelAmpSpace]);
final JComboBox gg2 = new JComboBox();
gg2.addItem("normalized");
gg2.addItem("immediate");
ggGain = gg1;
ggGainType = gg2;
c = new Component[]{gg1, gg2};
gg2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
switch (gg2.getSelectedIndex()) {
case GAIN_ABSOLUTE:
gg1.setParam(new Param(0.0, Param.DECIBEL_AMP));
break;
case GAIN_UNITY:
gg1.setParam(getDefaultGain());
break;
default:
break;
}
}
});
break;
default:
throw new IllegalArgumentException(String.valueOf(type));
}
return c;
}
/*
* Dialog mit Clipping-Info und Option zum Anpassen des Gain-Feldes
* vorbereiten; aus process() aufzurufen
*/
protected void handleClipping( float mxAmp )
{
clipping = ((mxAmp < 0.707f) || (mxAmp > 1.0f));
if( !clipping ) return;
int gainType = (ggGainType == null ? GAIN_ABSOLUTE : ggGainType.getSelectedIndex() );
clipping = clipping && (gainType != GAIN_UNITY); // user might want low amp or clipping
maxAmp = mxAmp;
}
/*
* Dialog mit Clipping-Info und Option zum Anpassen des Gain-Feldes
* (Anpassung wird automatisch vorgenommen)
*/
protected void clippingDlg()
{
double newAmp = 1.0 / ((double) maxAmp * 1.0115794543); // 0.1 dB headroom
Param p, pa, ref;
// ConfirmDlg confirm;
Double ampCorrect = new Double( 20 * Math.log( newAmp ) / Constants.ln10 );
Object[] msgArgs = { ampCorrect };
String msgPtrn = "The output {0,choice,-1#is clipped|0#volume is suboptimal}!" +
"\nShall the gain be adjusted by {0,number,#,##0.0} dB?";
MessageFormat msgForm = new MessageFormat( msgPtrn );
int i;
msgForm.setLocale( Locale.US );
msgForm.applyPattern( msgPtrn );
if( ggGain != null ) {
i = JOptionPane.showConfirmDialog(getComponent(), msgForm.format( msgArgs ), "Confirm", JOptionPane.YES_NO_OPTION );
if( i == 0 ) {
p = ggGain.getParam();
ref = new Param( 1.0, Param.ABS_AMP );
pa = Param.transform( p, Param.ABS_AMP, ref, null );
p = Param.transform( new Param( pa.value * newAmp, pa.unit ), p.unit, ref, null );
ggGain.setParam( p );
}
} else {
JOptionPane.showMessageDialog(getComponent(), msgForm.format(msgArgs));
}
}
/**
* Sets graphical indicator
* when module is writing to output file
* (e.g. when normalizing)
* so the user may pre-listen to
* updates of the file
*/
protected void indicateOutputWrite()
{
pp.setPaint( COLOR_NORM );
}
/**
* Float-File in AudioFile schreiben und Gain veraendern dabei
*
* @param srcF entweder eindimensional (Daten interleaved) oder per Channel
* @param destF Ziel
* @param buf Puffer
* @param gain gain
* @param progEnd fuer setProgression()
* @return true, wenn abgeschlossen, sonst false (bei threadRunning == false)
*/
protected boolean normalizeAudioFile( FloatFile srcF[], AudioFile destF, float buf[][], float gain, float progEnd )
throws IOException
{
AudioFileDescr stream = destF.getDescr();
int outChanNum = stream.channels;
int outLength;
int framesWritten;
int bufSize = buf[ 0 ].length;
float progOff = getProgression();
float progWeight = progEnd - progOff;
float[] bufSub, tempBuf;
int i, j, ch, len;
indicateOutputWrite();
for( ch = 0; ch < srcF.length; ch++ ) {
srcF[ ch ].seekFloat( 0 );
}
framesWritten = 0;
if( srcF.length == outChanNum ) {
outLength = (int) srcF[ 0 ].getSize();
while( (framesWritten < outLength) && threadRunning ) {
len = Math.min( bufSize, outLength - framesWritten );
for( ch = 0; ch < outChanNum; ch++ ) {
bufSub = buf[ ch ];
srcF[ ch ].readFloats( bufSub, 0, len );
for( i = 0; i < len; i++ ) {
bufSub[ i ] *= gain;
}
}
destF.writeFrames( buf, 0, len );
framesWritten += len;
// .... progress ....
setProgression( (float) framesWritten / (float) outLength * progWeight + progOff );
}
} else if( srcF.length == 1 ) {
outLength = (int) (srcF[ 0 ].getSize() / outChanNum);
framesWritten = 0;
tempBuf = new float[ bufSize * outChanNum ];
while( (framesWritten < outLength) && threadRunning ) {
len = Math.min( bufSize, outLength - framesWritten );
srcF[ 0 ].readFloats( tempBuf, 0, len * outChanNum );
for( ch = 0; ch < outChanNum; ch++ ) {
bufSub = buf[ ch ];
for( i = 0, j = ch; i < len; i++, j += outChanNum ) {
bufSub[ i ] = tempBuf[ j ] * gain;
}
}
destF.writeFrames( buf, 0, len );
framesWritten += len;
// .... progress ....
setProgression( (float) framesWritten / (float) outLength * progWeight + progOff );
}
} else {
System.err.println( "DocumentFrame.normalizeAudioFile : illegal floatfile channel #" );
}
return( threadRunning );
}
protected boolean normalizeAudioFile( AudioFile srcF, AudioFile destF, float buf[][], float gain, float progEnd )
throws IOException
{
final int outChanNum = destF.getDescr().channels;
final long outLength = srcF.getFrameNum();
final int bufSize = buf[ 0 ].length;
final float progOff = getProgression();
final float progWeight = progEnd - progOff;
long framesWritten;
int i, ch, len;
float[] convBuf1;
indicateOutputWrite();
srcF.seekFrame( 0 );
for( framesWritten = 0; (framesWritten < outLength) && threadRunning; ) {
len = (int) Math.min( bufSize, outLength - framesWritten );
srcF.readFrames( buf, 0, len );
for( ch = 0; ch < outChanNum; ch++ ) {
convBuf1 = buf[ ch ];
for( i = 0; i < len; i++ ) {
convBuf1[ i ] *= gain;
}
}
destF.writeFrames( buf, 0, len );
framesWritten += len;
// .... progress ....
setProgression( (float) framesWritten / (float) outLength * progWeight + progOff );
}
return( threadRunning );
}
protected AudioFile createTempFile( AudioFileDescr template )
throws IOException
{
return createTempFile( template.channels, template.rate );
}
protected AudioFile createTempFile(int numChannels, double rate)
throws IOException {
final AudioFileDescr afd = new AudioFileDescr();
AudioFile af;
afd.type = AudioFileDescr.TYPE_AIFF;
afd.channels = numChannels;
afd.rate = rate;
afd.bitsPerSample = 32;
afd.sampleFormat = AudioFileDescr.FORMAT_FLOAT;
afd.file = IOUtil.createTempFile("fsc", ".aif");
af = AudioFile.openAsWrite(afd);
collTempFiles.add(af);
return af;
}
protected void deleteTempFile(AudioFile af) {
collTempFiles.remove(af);
af.cleanUp();
af.getDescr().file.delete();
}
private void deleteAllTempFiles() {
while (!collTempFiles.isEmpty()) {
deleteTempFile(collTempFiles.get(0));
}
}
// -------- Processor Interface --------
/**
* Output synthetisieren
* E N T E R T H E H E A R T O F T H E D R A G O N ! ! ! ==========================================
*/
public void run()
{
fillPropertyArray(); // retrieve latest values
// System.gc(); // the algorithm may need a lot of memory
setProgression( 0.0f );
deleteAllTempFiles();
setError( null );
clipping = false;
resume();
elm.dispatchEvent( new ProcessorEvent( this, ProcessorEvent.STARTED,
System.currentTimeMillis(), this ));
try {
process();
}
catch( OutOfMemoryError e1 ) {
setError( new Exception( ERR_MEMORY ));
}
catch( Exception e2 ) {
setError( e2 );
}
finally {
deleteAllTempFiles();
stop();
elm.dispatchEvent( new ProcessorEvent( this, ProcessorEvent.STOPPED,
System.currentTimeMillis(), this ));
}
} // run()
/*
* Supclassiz ovaride dis wan
*/
protected abstract void process();
public void start()
{
pp.start();
}
public void pause()
{
threadRunning = true;
threadPausing = true;
}
public void resume()
{
synchronized( this ) {
threadRunning = true;
threadPausing = false;
notify();
}
}
public void stop()
{
synchronized( this ) {
threadRunning = false;
threadPausing = false;
notify();
}
}
public void addProcessorListener( ProcessorListener li )
{
elm.addListener( li );
listenerCount++;
}
public void removeProcessorListener( ProcessorListener li )
{
elm.removeListener( li );
listenerCount--;
}
public float getProgression()
{
return progress;
}
public Exception getError()
{
return threadError;
}
public void setError( Exception e )
{
threadError = e;
}
// ---------- EventManager.Processor interface ----------
public void processEvent( BasicEvent e )
{
ProcessorListener li;
for( int i = 0; i < elm.countListeners(); i++ ) {
li = (ProcessorListener) elm.getListener( i );
switch( e.getID() ) {
case ProcessorEvent.PROGRESS:
li.processorProgress( (ProcessorEvent) e );
break;
case ProcessorEvent.STARTED:
li.processorStarted( (ProcessorEvent) e );
break;
case ProcessorEvent.STOPPED:
li.processorStopped( (ProcessorEvent) e );
break;
case ProcessorEvent.PAUSED:
li.processorPaused( (ProcessorEvent) e );
break;
case ProcessorEvent.RESUMED:
li.processorResumed( (ProcessorEvent) e );
break;
default:
assert false : e.getID();
}
} // for( i = 0; i < elm.countListeners(); i++ )
}
protected ProcessPanel getProcessPanel()
{
return pp;
}
/*
* Subclasses should override & invoke this
* ; use to enable/disable gadgets after e.g. checkbox switches
*/
protected void reflectPropertyChanges()
{
// nothing
}
// ---------------- ProgressComponent interface ----------------
public void addCancelListener( ActionListener l )
{
pProgress.addCancelListener( l );
}
public void removeCancelListener( ActionListener l )
{
pProgress.removeCancelListener( l );
}
public Component getComponent()
{
return this; // getWindow();
}
public void resetProgression()
{
}
public void setProgression( float p )
{
progress = p;
if( listenerCount > 0 ) { // avoid overhead when noone is listening
elm.dispatchEvent( new ProcessorEvent( this, ProcessorEvent.PROGRESS,
System.currentTimeMillis(), this ));
}
// .... check pause ....
if( threadPausing ) {
try {
synchronized( this ) {
elm.dispatchEvent( new ProcessorEvent( this, ProcessorEvent.PAUSED,
System.currentTimeMillis(), this ));
this.wait();
}
} catch( InterruptedException ignored) {}
elm.dispatchEvent( new ProcessorEvent( this, ProcessorEvent.RESUMED,
System.currentTimeMillis(), this ));
}
}
public void finishProgression( int result )
{
}
public void setProgressionText( String text )
{
}
public void showMessage( int type, String text )
{
}
public void displayError( Exception e, String processName )
{
GUIUtil.displayError(getComponent(), e, processName);
}
// -------- internal classes --------
// private class ActionShowWindow
// extends MenuAction // SyncedMenuAction
// {
// protected ActionShowWindow()
// {
// super( null, null );
// }
//
// public void actionPerformed( ActionEvent e )
// {
// enc_this.setVisible( true );
// enc_this.toFront();
// }
// }
// // action for the Save-Session menu item
// protected class ActionClose
// extends MenuAction
// {
// public void actionPerformed( ActionEvent e )
// {
// perform();
// }
//
// public void perform()
// {
// final ProcessingThread pt = closeDocument( false, new Flag( false ));
// if( pt != null ) pt.start();
// }
// }
// private class ActionHelp
// extends MenuAction
// {
// protected ActionHelp( String name, KeyStroke acc )
// {
// super( name, acc );
// }
//
// public void actionPerformed( ActionEvent e )
// {
// try {
// final String className = enc_this.getClass().getName();
// HelpFrame.openViewerAndLoadHelpFile( className.substring( className.lastIndexOf( '.' ) + 1 ));
// } catch( Exception e1 ) {
// GUIUtil.displayError( getWindow(), e1, getTitle() );
// }
// }
// }
// private class ActionAddPreset
// extends MenuAction
// {
// protected ActionAddPreset( String name, KeyStroke acc )
// {
// super( name, acc );
// }
//
// public void actionPerformed( ActionEvent e )
// {
// if( threadRunning ) return; // not while processing
//
// String name = JOptionPane.showInputDialog( getWindow(), app.getResourceString( "procWinEnterPresetName" ));
// if( name != null && name.length() > 0 ) {
// name = name.replace( '.', ' ' ); // period not allowed, as we use it as menu node id
// if( name.equals( Presets.DEFAULT )) {
// JOptionPane.showMessageDialog( getWindow(), app.getResourceString( "procWinDefaultPreset" ));
// return;
// }
//
// for( Iterator iter = getPresets().presetNames().iterator(); iter.hasNext(); ) {
// if( name.equals( iter.next() )) {
// if( JOptionPane.showConfirmDialog( getWindow(), name + ":\n"+
// app.getResourceString( "procWinOverwritePreset" ),
// app.getResourceString( "optionDlgConfirm" ),
// JOptionPane.YES_NO_OPTION ) != 0 ) return; // abort
// }
// }
//
// addPreset( name );
// }
// }
// }
//
// private class ActionDeletePreset
// extends MenuAction
// {
// protected ActionDeletePreset( String name, KeyStroke acc )
// {
// super( name, acc );
// }
//
// public void actionPerformed( ActionEvent e )
// {
// if( threadRunning ) return; // not while processing
//
// final List presetNames = getPresets().presetNames();
// presetNames.remove( Presets.DEFAULT );
// final JList list = new JList( presetNames.toArray() );
// final JScrollPane scroll = new JScrollPane( list );
// final JOptionPane op = new JOptionPane( scroll, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION );
// final int result = BasicWindowHandler.showDialog( op, getComponent(), app.getResourceString( "procWinChooseDelPreset" ));
// if( result == JOptionPane.OK_OPTION ) {
// final Object[] selNames = list.getSelectedValues();
// for( int i = 0; i < selNames.length; i++ ) {
// deletePreset( selNames[ i ].toString() );
// }
// }
// }
// }
private class ActionRecallPreset
extends MenuAction
{
protected ActionRecallPreset( String name, KeyStroke acc )
{
super( name, acc );
}
public void actionPerformed( ActionEvent e )
{
if( threadRunning ) return; // not while processing
loadPreset( getValue( NAME ).toString() );
}
}
}