/* * ControlRoomFrame.java * Eisenkraut * * Copyright (c) 2004-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.eisenkraut.gui; import de.sciss.app.AbstractApplication; import de.sciss.app.AbstractWindow; import de.sciss.app.Application; import de.sciss.app.Document; import de.sciss.app.DocumentHandler; import de.sciss.app.DocumentListener; import de.sciss.app.DynamicListening; import de.sciss.common.AppWindow; import de.sciss.common.BasicWindowHandler; import de.sciss.eisenkraut.Main; import de.sciss.eisenkraut.io.AudioBoxConfig; import de.sciss.eisenkraut.io.RoutingConfig; import de.sciss.eisenkraut.net.SuperColliderClient; import de.sciss.eisenkraut.net.SuperColliderPlayer; import de.sciss.eisenkraut.session.Session; import de.sciss.eisenkraut.util.PrefsUtil; import de.sciss.gui.PeakMeterPanel; import de.sciss.gui.PrefComboBox; import de.sciss.gui.SpringPanel; import de.sciss.gui.StringItem; import de.sciss.jcollider.Constants; import de.sciss.jcollider.Group; import de.sciss.jcollider.NodeWatcher; import de.sciss.jcollider.Server; import de.sciss.jcollider.ServerEvent; import de.sciss.jcollider.ServerListener; import de.sciss.jcollider.ServerOptions; import de.sciss.net.OSCBundle; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Point2D; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; //import de.sciss.eisenkraut.math.MathUtil; /** * TODO: could use an explicit GroupAnySync for lmm, which would go into SuperColliderClient * so sc-client whould be able to pause master synths according to the sync */ public class ControlRoomFrame extends AppWindow implements DynamicListening, Constants, ServerListener, SuperColliderClient.Listener, DocumentListener // , TransportListener, MeterListener { private final PrefComboBox ggOutputConfig; private final PrefComboBox ggAudioBox; private final Preferences audioPrefs; protected final SuperColliderClient superCollider; private Group grpMeters; private final PeakMeterPanel pmg; private RoutingConfig oCfg; private final SpringPanel b1; protected final VolumeFader ggVolume; private final Map<Session, SuperColliderPlayer> mapPlayers = new HashMap<Session, SuperColliderPlayer>(); // key = Session, value = SuperColliderPlayer private final PeakMeterManager lmm; private final ActionListener audioBoxListener; private boolean isListening = false; public ControlRoomFrame() { super( PALETTE ); final Application app = AbstractApplication.getApplication(); superCollider = SuperColliderClient.getInstance(); lmm = new PeakMeterManager( superCollider.getMeterManager() ); setTitle( app.getResourceString( "paletteCtrlRoom" )); setResizable( false ); final Container cp = getContentPane(); final JPanel b2 = new JPanel( new BorderLayout() ); // Box.createHorizontalBox(); audioPrefs = app.getUserPrefs().node( PrefsUtil.NODE_AUDIO ); final Object comboProto = "XXXXXXXX"; b1 = new SpringPanel(2, 4, 2, 4); lmm.setDynamicComponent(b1); ggVolume = new VolumeFader(); ggOutputConfig = new PrefComboBox(); ggOutputConfig.putClientProperty( "JComboBox.isSquare", Boolean.TRUE ); ggAudioBox = new PrefComboBox(); ggAudioBox.putClientProperty( "JComboBox.isSquare", Boolean.TRUE ); ggOutputConfig.setPrototypeDisplayValue( comboProto ); ggAudioBox.setPrototypeDisplayValue( comboProto ); refillConfigs(); ggOutputConfig.setPreferences( audioPrefs, PrefsUtil.KEY_OUTPUTCONFIG ); ggAudioBox.setPreferences( audioPrefs, PrefsUtil.KEY_AUDIOBOX ); audioBoxListener = new ActionListener() { public void actionPerformed( ActionEvent e ) { final Server server = superCollider.getServer(); if( (server != null) && server.isRunning() ) { final JOptionPane op = new JOptionPane( getResourceString( "optionDlgAudioBoxReboot" ), JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION ); if( BasicWindowHandler.showDialog( op, getWindow(), null ) == 0 ) { superCollider.reboot(); } } } }; ggVolume.addChangeListener( new ChangeListener() { public void stateChanged( ChangeEvent e ) { superCollider.setVolume( ControlRoomFrame.this, ggVolume.getVolumeLinear() ); } }); final JToggleButton ggLimiter = new JToggleButton("Limiter"); ggLimiter.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e ) { superCollider.setLimiter( ggLimiter.isSelected() ); } }); if( superCollider.getLimiter() ) ggLimiter.setSelected(true); // setSelectedIndex( 1 ); pmg = new PeakMeterPanel(); pmg.setBorder(true); pmg.setCaption(true); oCfg = superCollider.getOutputConfig(); rebuildMeters(); b2.add(pmg, BorderLayout.WEST); b2.add(ggVolume, BorderLayout.EAST); b1.gridAdd(ggLimiter, 0, 0, -1, 1); b1.gridAdd(b2, 0, 1, -1, 1); b1.gridAdd(ggOutputConfig, 0, 2, -1, 1); b1.gridAdd(ggAudioBox, 0, 3, -1, 1); b1.makeCompactGrid(); cp.add(b1, BorderLayout.CENTER); // AbstractWindowHandler.setDeepFont( b1 ); // ---- listeners ----- addListener(new AbstractWindow.Adapter() { public void windowOpened(AbstractWindow.Event e) { startListening(); } public void windowClosing(AbstractWindow.Event e) { setVisible(false); dispose(); } }); // DISPOSE_ON_CLOSE doesn't work: the BasicFrame doesn't catch // the visible preferences. instead we hide and then dispose the window! // setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); updateVolume(); setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE ); // window listener see above! init(); app.addComponent( Main.COMP_CTRLROOM, this ); } protected boolean autoUpdatePrefs() { return true; } protected boolean restoreVisibility() { return true; } protected Point2D getPreferredLocation() { return new Point2D.Float(0.95f, 0.2f); } public void dispose() { AbstractApplication.getApplication().removeComponent(Main.COMP_CTRLROOM); lmm.dispose(); if (grpMeters != null) { try { grpMeters.free(); } catch (IOException e1) { printError("dispose", e1); } grpMeters = null; } stopListening(); pmg.dispose(); super.dispose(); } private void updateVolume() { ggVolume.setVolumeLinear( superCollider.getVolume() ); } private void startMeters() { final Server s = superCollider.getServer(); final ServerOptions so = superCollider.getServerOptions(); final int numOutputBusChannels; final int[] channels; final Group mg = superCollider.getMasterGroup(); if( (s == null) || (oCfg == null) || (mg == null) ) return; channels = new int[ oCfg.mapping.length ]; numOutputBusChannels = so.getNumOutputBusChannels(); for( int ch = 0; ch < channels.length; ch++ ) { if( oCfg.mapping[ ch ] < numOutputBusChannels ) { channels[ ch ] = oCfg.mapping[ ch ]; } else { channels[ ch ] = -1; } } try { if( grpMeters == null ) { grpMeters = Group.basicNew( s ); final OSCBundle bndl = new OSCBundle(); bndl.addPacket( grpMeters.addBeforeMsg( mg )); grpMeters.setName( "CtrlRmMeters" ); NodeWatcher.newFrom( s ).register( grpMeters ); s.sendBundle( bndl ); } lmm.setGroup( grpMeters ); lmm.setInputs( s, channels ); } catch( IOException e1 ) { printError( "startMeters", e1 ); } } private void stopMeters() { lmm.clearInputs(); } private static void printError( String name, Throwable t ) { System.err.println( name + " : " + t.getClass().getName() + " : " + t.getLocalizedMessage() ); } private void rebuildMeters() { oCfg = superCollider.getOutputConfig(); if( oCfg != null ) { pmg.setNumChannels( oCfg.numChannels ); } else { pmg.setNumChannels( 0 ); } b1.makeCompactGrid(); pack(); lmm.setView( pmg ); } // @synchronization must be in event thread private void registerTaskSyncs() { if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException(); final DocumentHandler dh = AbstractApplication.getApplication().getDocumentHandler(); Document doc; SuperColliderPlayer p; lmm.clearTaskSyncs(); for (int i = 0; i < dh.getDocumentCount(); i++) { doc = dh.getDocument(i); if (doc instanceof Session) { Session docS = (Session) doc; p = superCollider.getPlayerForDocument(docS); if (p != null) { lmm.addTaskSync(p.getOutputSync()); mapPlayers.put(docS, p); } } } } // @synchronization must be in event thread private void unregisterTaskSyncs() { if( !EventQueue.isDispatchThread() ) throw new IllegalMonitorStateException(); lmm.clearTaskSyncs(); mapPlayers.clear(); } // ------------- ServerListener interface ------------- public void serverAction( ServerEvent e ) { switch( e.getID() ) { case ServerEvent.STOPPED: grpMeters = null; stopMeters(); unregisterTaskSyncs(); break; case ServerEvent.RUNNING: registerTaskSyncs(); startMeters(); break; default: break; } } // ------------- SuperCollderClient.Listener interface ------------- public void clientAction( SuperColliderClient.Event e ) { switch( e.getID() ) { case SuperColliderClient.Event.OUTPUTCONFIG: stopMeters(); rebuildMeters(); startMeters(); break; case SuperColliderClient.Event.VOLUME: if( e.getSource() != this ) { updateVolume(); } break; default: break; } } // ---------------- DocumentListener interface ---------------- public void documentAdded( de.sciss.app.DocumentEvent e ) { if( e.getDocument() instanceof Session ) { final Session doc = (Session) e.getDocument(); final SuperColliderPlayer p = superCollider.getPlayerForDocument( doc ); if( (p != null) && !mapPlayers.containsKey( doc )) { lmm.addTaskSync( p.getOutputSync() ); mapPlayers.put( doc, p ); } } } public void documentRemoved(de.sciss.app.DocumentEvent e) { Document doc = e.getDocument(); if (doc instanceof Session) { Session docS = (Session) doc; final SuperColliderPlayer p = mapPlayers.remove(doc); if (p != null) { lmm.removeTaskSync(p.getOutputSync()); } } } public void documentFocussed( de.sciss.app.DocumentEvent e ) { /* empty */ } // ---------------- DynamicListening interface ---------------- public void startListening() { isListening = true; superCollider.addServerListener( this ); superCollider.addClientListener( this ); ggAudioBox.addActionListener( audioBoxListener ); AbstractApplication.getApplication().getDocumentHandler().addDocumentListener( this ); registerTaskSyncs(); startMeters(); } public void stopListening() { isListening = false; stopMeters(); unregisterTaskSyncs(); AbstractApplication.getApplication().getDocumentHandler().removeDocumentListener( this ); ggAudioBox.removeActionListener( audioBoxListener ); superCollider.removeClientListener( this ); superCollider.removeServerListener( this ); } private void refillConfigs() { refillIOConfigs(); refillAudioBoxes(); } public void refillIOConfigs() { final Preferences childPrefs; final String[] cfgIDs; final Set<StringItem> cfgItems; try { if( isListening ) { ggOutputConfig.stopListening(); } childPrefs = audioPrefs.node( PrefsUtil.NODE_OUTPUTCONFIGS ); cfgIDs = childPrefs.childrenNames(); cfgItems = new TreeSet<StringItem>( StringItem.valueComparator ); for (String cfgID : cfgIDs) { cfgItems.add(new StringItem(cfgID, childPrefs.node(cfgID).get(RoutingConfig.KEY_NAME, cfgID))); } ggOutputConfig.removeAllItems(); for (StringItem cfgItem : cfgItems) { ggOutputConfig.addItem(cfgItem); } } catch( BackingStoreException e1 ) { System.err.println( e1.getClass().getName() + " : " + e1.getLocalizedMessage() ); } finally { if( isListening ) { ggOutputConfig.startListening(); } } } public void refillAudioBoxes() { final Preferences childPrefs; final String[] cfgIDs; final Set<StringItem> cfgItems; Preferences childPrefs2; try { if (isListening) { ggAudioBox.stopListening(); ggAudioBox.removeActionListener(audioBoxListener); } childPrefs = audioPrefs.node(PrefsUtil.NODE_AUDIOBOXES); cfgIDs = childPrefs.childrenNames(); cfgItems = new TreeSet<StringItem>(StringItem.valueComparator); for (String cfgID : cfgIDs) { childPrefs2 = childPrefs.node(cfgID); if (childPrefs2.getBoolean(AudioBoxConfig.KEY_ACTIVE, false)) { cfgItems.add(new StringItem(cfgID, childPrefs2.get(AudioBoxConfig.KEY_NAME, cfgID))); } } ggAudioBox.removeAllItems(); for (Object cfgItem : cfgItems) { ggAudioBox.addItem(cfgItem); } } catch (BackingStoreException e1) { System.err.println(e1.getClass().getName() + " : " + e1.getLocalizedMessage()); } finally { if (isListening) { ggAudioBox.startListening(); // add ActionListener _after_ startListening() so no superfluous // event is caught (resulting in the reboot-question-dialog) ggAudioBox.addActionListener(audioBoxListener); } } } protected static String getResourceString(String key) { return AbstractApplication.getApplication().getResourceString(key); } }