--- jlscp/trunk/src/org/linuxsampler/lscp/Client.java 2005/10/10 14:55:44 784 +++ jlscp/trunk/src/org/linuxsampler/lscp/Client.java 2008/12/24 16:55:54 1817 @@ -1,7 +1,7 @@ /* * jlscp - a java LinuxSampler control protocol API * - * Copyright (C) 2005 Grigor Kirilov Iliev + * Copyright (C) 2005-2008 Grigor Iliev * * This file is part of jlscp. * @@ -23,25 +23,26 @@ package org.linuxsampler.lscp; import java.io.IOException; +import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; -import java.net.UnknownHostException; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; -import static org.linuxsampler.lscp.Parser.*; import org.linuxsampler.lscp.event.*; +import static org.linuxsampler.lscp.Parser.*; + /** * This class is the abstraction representing a client endpoint for communication with LinuxSampler - * instance. Since it implements all commands specified in the LSCP protocol v1.0, for more + * instance. Since it implements all commands specified in the LSCP protocol v1.3, for more * information look at the - * LSCP specification. + * LSCP specification. * *

The following code establishes connection to LinuxSampler instance and gets the * LinuxSampler version: @@ -68,23 +69,29 @@ private String address; private int port; private Socket sock = null; - private int soTimeout = 10000; + private int soTimeout = 20000; private LscpInputStream in = null; private LscpOutputStream out = null; private EventThread eventThread; + private boolean printOnlyMode = false; + class EventThread extends Thread { + private Vector queue = new Vector(); private boolean terminate = false; EventThread() { super("LSCP-Event-Thread"); } + @Override public void run() { while(!mustTerminate()) { - try { processNotifications(); } - catch(Exception x) { + try { + processQueue(); + processNotifications(); + } catch(Exception x) { getLogger().log(Level.FINE, x.getMessage(), x); } try { synchronized(this) { wait(100); } } @@ -102,6 +109,22 @@ terminate = true; this.notifyAll(); } + + public synchronized void + scheduleNotification(String s) { queue.add(s); } + + private void + processQueue() { + String[] notifications = popAllNotifications(); + for(String n : notifications) fireEvent(n); + } + + private synchronized String[] + popAllNotifications() { + String[] notifications = queue.toArray(new String[queue.size()]); + queue.removeAllElements(); + return notifications; + } } /** @@ -133,6 +156,108 @@ } /** + * Creates a new instance of Client. + * @param printOnlyMode Determines whether the client will be in print-only mode. + */ + public + Client(boolean printOnlyMode) { + if(printOnlyMode) setPrintOnlyMode(true); + } + + private boolean extendedCharacterEscaping = true; + + /** + * Sets whether strings sent to LinuxSampler should be more aggressively escaped. + */ + public synchronized void + setExtendedCharacterEscaping(boolean b) { extendedCharacterEscaping = b; } + + /** + * Determines whether strings sent to LinuxSampler should be more aggressively escaped. + */ + public synchronized boolean + getExtendedCharacterEscaping() { return extendedCharacterEscaping; } + + /** + * @see java.net.Socket#setSoTimeout + */ + public synchronized void + setSoTimeout(int timeout) { + soTimeout = timeout; + + try { if(sock != null) sock.setSoTimeout(timeout); } + catch(Exception x) { getLogger().log(Level.INFO, "Unable to set timeout", x); } + } + + private String + toEscapedText(String s) { + s = toEscapedString(s); + return conv(s); + } + + private String + toEscapedFsEntry(String s) { + s = toEscapedFileName(s); + return conv(s); + } + + /** + * Applies an extended character escaping to the specified string if needed. + */ + private String + conv(String s) { + return getExtendedCharacterEscaping() ? toExtendedEscapeSequence(s) : s; + } + + /** + * Determines whether the client is in print-only mode. + * Print-only mode means that the client will just print all + * LSCP commands to the specified output stream or to the standard output stream + * (java.lang.System.out) if no output stream is specified, + * without taking any further actions. Thus, in print-only mode all returned + * values by Client's methods are meaningless and should be discarded. + * @return true if the client is in + * print-only mode, false otherwise. + * @see #setPrintOnlyModeOutputStream + */ + public synchronized boolean + getPrintOnlyMode() { return printOnlyMode; } + + /** + * Sets the print-only mode. Note that in print-only mode all returned + * values by Client's methods are meaningless and should be discarded. + * The default output stream in print-only mode is java.lang.System.out. + * @param b If true all LSCP commands will be sent + * to the specified output stream or to the standard output stream + * (java.lang.System.out) if no output stream is specified, + * and no further actions will be taken. + * @throws IllegalStateException If the client is connected. + * @see #setPrintOnlyModeOutputStream + */ + public synchronized void + setPrintOnlyMode(boolean b) { + if(printOnlyMode == b) return; + if(isConnected()) throw new IllegalStateException(); + + printOnlyMode = b; + if(b) out = new LscpOutputStream(System.out); + } + + /** + * Sets the output stream to be used in print-only mode. + * @param out The output stream to be used in print-only mode. + * @throws IllegalStateException If the client is not in print-only mode. + * @throws IllegalArgumentException if out is null. + * @see #setPrintOnlyMode + */ + public synchronized void + setPrintOnlyModeOutputStream(OutputStream out) { + if(!getPrintOnlyMode()) throw new IllegalStateException("Not in print-only mode"); + if(out == null) throw new IllegalArgumentException("out must be non-null"); + this.out = new LscpOutputStream(out); + } + + /** * Specifies the jlscp version. * @return The jlscp version. */ @@ -180,6 +305,7 @@ public synchronized void connect() throws LscpException { if(sock != null) disconnect(); + if(getPrintOnlyMode()) return; // Initializing LSCP event thread if(eventThread.isAlive()) { @@ -252,12 +378,33 @@ if(hasSubscriptions()) eventThread.start(); if(!llM.isEmpty()) subscribe("MISCELLANEOUS"); + if(!llAODC.isEmpty()) subscribe("AUDIO_OUTPUT_DEVICE_COUNT"); + if(!llAODI.isEmpty()) subscribe("AUDIO_OUTPUT_DEVICE_INFO"); + if(!llMIDC.isEmpty()) subscribe("MIDI_INPUT_DEVICE_COUNT"); + if(!llMIDI.isEmpty()) subscribe("MIDI_INPUT_DEVICE_INFO"); if(!llBF.isEmpty()) subscribe("BUFFER_FILL"); if(!llCC.isEmpty()) subscribe("CHANNEL_COUNT"); if(!llCI.isEmpty()) subscribe("CHANNEL_INFO"); + if(!llFSC.isEmpty()) subscribe("FX_SEND_COUNT"); + if(!llFSI.isEmpty()) subscribe("FX_SEND_INFO"); if(!llSC.isEmpty()) subscribe("STREAM_COUNT"); if(!llVC.isEmpty()) subscribe("VOICE_COUNT"); + if(!llTSC.isEmpty()) subscribe("TOTAL_STREAM_COUNT"); if(!llTVC.isEmpty()) subscribe("TOTAL_VOICE_COUNT"); + if(!llMIMC.isEmpty()) subscribe("MIDI_INSTRUMENT_MAP_COUNT"); + if(!llMIMI.isEmpty()) subscribe("MIDI_INSTRUMENT_MAP_INFO"); + if(!llMIC.isEmpty()) subscribe("MIDI_INSTRUMENT_COUNT"); + if(!llMII.isEmpty()) subscribe("MIDI_INSTRUMENT_INFO"); + if(!llDMD.isEmpty()) subscribe("DEVICE_MIDI"); + if(!llCMD.isEmpty()) subscribe("CHANNEL_MIDI"); + if(!llID.isEmpty()) { + subscribe("DB_INSTRUMENT_DIRECTORY_COUNT"); + subscribe("DB_INSTRUMENT_DIRECTORY_INFO"); + subscribe("DB_INSTRUMENT_COUNT"); + subscribe("DB_INSTRUMENT_INFO"); + subscribe("DB_INSTRUMENTS_JOB_INFO"); + } + if(!llGI.isEmpty()) subscribe("GLOBAL_INFO"); } /** @@ -265,6 +412,7 @@ */ public synchronized void disconnect() { + if(getPrintOnlyMode()) return; try { if(sock != null) sock.close(); } catch(Exception x) { getLogger().log(Level.FINE, x.getMessage(), x); } sock = null; @@ -292,6 +440,8 @@ */ private void verifyConnection() throws IOException { + if(getPrintOnlyMode()) return; + if(!isConnected()) throw new IOException(LscpI18n.getLogMsg("Client.notConnected!")); } @@ -301,13 +451,15 @@ String s; for(;;) { s = in.readLine(); - if(s.startsWith("NOTIFY:")) fireEvent(s.substring("NOTIFY:".length())); + if(s.startsWith("NOTIFY:")) { + eventThread.scheduleNotification(s.substring("NOTIFY:".length())); + } else break; } return s; } - /** Processes the notifications send by LinuxSampler */ + /** Processes the notifications sent by LinuxSampler */ private synchronized void processNotifications() throws IOException, LscpException { while(in.available() > 0) { @@ -366,14 +518,41 @@ return rs; } + /** Audio output device count listeners */ + private final Vector llAODC = new Vector(); + /** Audio output device info listeners */ + private final Vector llAODI = new Vector(); private final Vector llBF = new Vector(); private final Vector llCC = new Vector(); private final Vector llCI = new Vector(); + private final Vector llFSC = new Vector(); + private final Vector llFSI = new Vector(); private final Vector llM = new Vector(); + /** MIDI input device count listeners */ + private final Vector llMIDC = new Vector(); + /** MIDI input device info listeners */ + private final Vector llMIDI = new Vector(); private final Vector llSC = new Vector(); private final Vector llVC = new Vector(); + private final Vector llTSC = new Vector(); private final Vector llTVC = new Vector(); + /** MIDI instrument map count listeners */ + private final Vector llMIMC = new Vector(); + /** MIDI instrument map info listeners */ + private final Vector llMIMI = new Vector(); + /** MIDI instrument count listeners */ + private final Vector llMIC = + new Vector(); + /** MIDI instrument info listeners */ + private final Vector llMII = + new Vector(); + private final Vector llDMD = new Vector(); + private final Vector llCMD = new Vector(); + private final Vector llID = new Vector(); + private final Vector llGI = new Vector(); + + /** * Determines whether there is at least one subscription for notification events. * Do not forget to check for additional listeners if the LSCP specification @@ -383,18 +562,175 @@ */ private boolean hasSubscriptions() { - return !llBF.isEmpty() || - !llCC.isEmpty() || - !llCI.isEmpty() || - !llM.isEmpty() || - !llSC.isEmpty() || - !llVC.isEmpty() || - !llTVC.isEmpty(); + return !llAODC.isEmpty() || + !llAODI.isEmpty() || + !llBF.isEmpty() || + !llCC.isEmpty() || + !llCI.isEmpty() || + !llFSC.isEmpty() || + !llFSI.isEmpty() || + !llM.isEmpty() || + !llMIDC.isEmpty() || + !llMIDI.isEmpty() || + !llSC.isEmpty() || + !llVC.isEmpty() || + !llTSC.isEmpty() || + !llTVC.isEmpty() || + !llMIMC.isEmpty() || + !llMIMI.isEmpty() || + !llMIC.isEmpty() || + !llMII.isEmpty() || + !llDMD.isEmpty() || + !llCMD.isEmpty() || + !llID.isEmpty() || + !llGI.isEmpty(); } - private void + private synchronized void + fireDeviceMidiDataEvent(String s) { + try { + String[] list = parseList(s, ' '); + if(list.length != 5) { + getLogger().warning("Unknown DEVICE_MIDI format"); + return; + } + + int dev = parseInt(list[0]); + int port = parseInt(list[1]); + + MidiDataEvent.Type type = parseMidiDataType(list[2]); + if(type == null) return; + + int note = parseInt(list[3]); + int velocity = parseInt(list[4]); + + DeviceMidiDataEvent e = new DeviceMidiDataEvent(this, type, note, velocity); + e.setDeviceId(dev); + e.setPortId(port); + for(DeviceMidiDataListener l : llDMD) l.midiDataArrived(e); + } catch(LscpException x) { + getLogger().log ( + Level.WARNING, LscpI18n.getLogMsg("CommandFailed!"), x + ); + } + } + + private synchronized void + fireChannelMidiDataEvent(String s) { + try { + String[] list = parseList(s, ' '); + if(list.length != 4) { + getLogger().warning("Unknown CHANNEL_MIDI format"); + return; + } + + int channel = parseInt(list[0]); + + MidiDataEvent.Type type = parseMidiDataType(list[1]); + if(type == null) return; + + int note = parseInt(list[2]); + int velocity = parseInt(list[3]); + + ChannelMidiDataEvent e = new ChannelMidiDataEvent(this, type, note, velocity); + e.setChannelId(channel); + for(ChannelMidiDataListener l : llCMD) l.midiDataArrived(e); + } catch(LscpException x) { + getLogger().log ( + Level.WARNING, LscpI18n.getLogMsg("CommandFailed!"), x + ); + } + } + + private MidiDataEvent.Type + parseMidiDataType(String s) { + if("NOTE_ON".equals(s)) return MidiDataEvent.Type.NOTE_ON; + if("NOTE_OFF".equals(s)) return MidiDataEvent.Type.NOTE_OFF; + + getLogger().warning("Unknown MIDI data type: " + s); + return null; + } + + private synchronized void fireEvent(String s) { - if(s.startsWith("CHANNEL_COUNT:")) { + // Sort by priority + + if(s.startsWith("CHANNEL_MIDI:")) { + s = s.substring("CHANNEL_MIDI:".length()); + fireChannelMidiDataEvent(s); + } else if(s.startsWith("DEVICE_MIDI:")) { + s = s.substring("DEVICE_MIDI:".length()); + fireDeviceMidiDataEvent(s); + } else if(s.startsWith("DB_INSTRUMENT_DIRECTORY_COUNT:")) { + s = s.substring("DB_INSTRUMENT_DIRECTORY_COUNT:".length()); + InstrumentsDbEvent e = new InstrumentsDbEvent(this, s); + for(InstrumentsDbListener l : llID) l.directoryCountChanged(e); + } else if(s.startsWith("DB_INSTRUMENT_DIRECTORY_INFO:")) { + InstrumentsDbEvent e; + s = s.substring("DB_INSTRUMENT_DIRECTORY_INFO:".length()); + if(s.startsWith("NAME ")) { + String[] list; + try { + s = s.substring("NAME ".length()); + list = parseEscapedStringList(s, ' '); + if(list.length != 2) throw new LscpException(); + list[1] = toNonEscapedString(list[1]); + e = new InstrumentsDbEvent(this, list[0], list[1]); + for(InstrumentsDbListener l : llID) { + l.directoryNameChanged(e); + } + } catch(LscpException x) { + getLogger().log ( + Level.WARNING, + LscpI18n.getLogMsg("CommandFailed!"), + x + ); + } + } else { + e = new InstrumentsDbEvent(this, s); + for(InstrumentsDbListener l : llID) l.directoryInfoChanged(e); + } + } else if(s.startsWith("DB_INSTRUMENT_COUNT:")) { + s = s.substring("DB_INSTRUMENT_COUNT:".length()); + InstrumentsDbEvent e = new InstrumentsDbEvent(this, s); + for(InstrumentsDbListener l : llID) l.instrumentCountChanged(e); + } else if(s.startsWith("DB_INSTRUMENT_INFO:")) { + InstrumentsDbEvent e; + s = s.substring("DB_INSTRUMENT_INFO:".length()); + if(s.startsWith("NAME ")) { + String[] list; + try { + s = s.substring("NAME ".length()); + list = parseEscapedStringList(s, ' '); + if(list.length != 2) throw new LscpException(); + list[1] = toNonEscapedString(list[1]); + e = new InstrumentsDbEvent(this, list[0], list[1]); + for(InstrumentsDbListener l : llID) { + l.instrumentNameChanged(e); + } + } catch(LscpException x) { + getLogger().log ( + Level.WARNING, + LscpI18n.getLogMsg("CommandFailed!"), + x + ); + } + } else { + e = new InstrumentsDbEvent(this, s); + for(InstrumentsDbListener l : llID) l.instrumentInfoChanged(e); + } + } else if(s.startsWith("DB_INSTRUMENTS_JOB_INFO:")) { + s = s.substring("DB_INSTRUMENTS_JOB_INFO:".length()); + try { + int i = Integer.parseInt(s); + InstrumentsDbEvent e = new InstrumentsDbEvent(this, i); + for(InstrumentsDbListener l : llID) l.jobStatusChanged(e); + } catch(NumberFormatException x) { + s = "Unknown DB_INSTRUMENTS_JOB_INFO format"; + getLogger().log(Level.WARNING, s, x); + } + + } else if(s.startsWith("CHANNEL_COUNT:")) { try { int i = Integer.parseInt(s.substring("CHANNEL_COUNT:".length())); ChannelCountEvent e = new ChannelCountEvent(this, i); @@ -407,31 +743,27 @@ } else if(s.startsWith("VOICE_COUNT:")) { try { s = s.substring("VOICE_COUNT:".length()); - int i = s.indexOf(' '); - if(i == -1) { + Integer[] i = parseIntList(s, ' '); + if(i.length != 2) { getLogger().warning("Unknown VOICE_COUNT format"); return; } - int j = Integer.parseInt(s.substring(0, i)); - i = Integer.parseInt(s.substring(i + 1)); - VoiceCountEvent e = new VoiceCountEvent(this, j, i); + VoiceCountEvent e = new VoiceCountEvent(this, i[0], i[1]); for(VoiceCountListener l : llVC) l.voiceCountChanged(e); - } catch(NumberFormatException x) { + } catch(Exception x) { getLogger().log(Level.WARNING, "Unknown VOICE_COUNT format", x); } } else if(s.startsWith("STREAM_COUNT:")) { try { s = s.substring("STREAM_COUNT:".length()); - int i = s.indexOf(' '); - if(i == -1) { + Integer[] i = parseIntList(s, ' '); + if(i.length != 2) { getLogger().warning("Unknown STREAM_COUNT format"); return; } - int j = Integer.parseInt(s.substring(0, i)); - i = Integer.parseInt(s.substring(i + 1)); - StreamCountEvent e = new StreamCountEvent(this, j, i); + StreamCountEvent e = new StreamCountEvent(this, i[0], i[1]); for(StreamCountListener l : llSC) l.streamCountChanged(e); - } catch(NumberFormatException x) { + } catch(Exception x) { getLogger().log(Level.WARNING, "Unknown STREAM_COUNT format", x); } } else if(s.startsWith("BUFFER_FILL:")) { @@ -439,7 +771,7 @@ s = s.substring("BUFFER_FILL:".length()); int i = s.indexOf(' '); if(i == -1) { - getLogger().warning("Unknown STREAM_COUNT format"); + getLogger().warning("Unknown BUFFER_FILL format"); return; } int j = Integer.parseInt(s.substring(0, i)); @@ -448,7 +780,7 @@ BufferFillEvent e = new BufferFillEvent(this, j, v); for(BufferFillListener l : llBF) l.bufferFillChanged(e); } catch(Exception x) { - getLogger().log(Level.WARNING, "Unknown STREAM_COUNT format", x); + getLogger().log(Level.WARNING, "Unknown BUFFER_FILL format", x); } } else if(s.startsWith("CHANNEL_INFO:")) { try { @@ -456,7 +788,18 @@ ChannelInfoEvent e = new ChannelInfoEvent(this, i); for(ChannelInfoListener l : llCI) l.channelInfoChanged(e); } catch(NumberFormatException x) { - getLogger().log(Level.WARNING, "Unknown STREAM_COUNT format", x); + getLogger().log(Level.WARNING, "Unknown CHANNEL_INFO format", x); + } + } else if(s.startsWith("TOTAL_STREAM_COUNT:")) { + try { + s = s.substring("TOTAL_STREAM_COUNT:".length()); + int i = Integer.parseInt(s); + TotalStreamCountEvent e = new TotalStreamCountEvent(this, i); + for(TotalStreamCountListener l : llTSC) l.totalStreamCountChanged(e); + } catch(NumberFormatException x) { + getLogger().log ( + Level.WARNING, "Unknown TOTAL_STREAM_COUNT format", x + ); } } else if(s.startsWith("TOTAL_VOICE_COUNT:")) { try { @@ -469,6 +812,144 @@ Level.WARNING, "Unknown TOTAL_VOICE_COUNT format", x ); } + } else if(s.startsWith("AUDIO_OUTPUT_DEVICE_COUNT:")) { + try { + s = s.substring("AUDIO_OUTPUT_DEVICE_COUNT:".length()); + int i = Integer.parseInt(s); + ItemCountEvent e = new ItemCountEvent(this, i); + for(ItemCountListener l : llAODC) l.itemCountChanged(e); + } catch(NumberFormatException x) { + getLogger().log ( + Level.WARNING, "Unknown AUDIO_OUTPUT_DEVICE_COUNT format", x + ); + } + } else if(s.startsWith("AUDIO_OUTPUT_DEVICE_INFO:")) { + try { + s = s.substring("AUDIO_OUTPUT_DEVICE_INFO:".length()); + int i = Integer.parseInt(s); + ItemInfoEvent e = new ItemInfoEvent(this, i); + for(ItemInfoListener l : llAODI) l.itemInfoChanged(e); + } catch(NumberFormatException x) { + getLogger().log ( + Level.WARNING, "Unknown AUDIO_OUTPUT_DEVICE_INFO format", x + ); + } + } else if(s.startsWith("MIDI_INPUT_DEVICE_COUNT:")) { + try { + s = s.substring("MIDI_INPUT_DEVICE_COUNT:".length()); + int i = Integer.parseInt(s); + ItemCountEvent e = new ItemCountEvent(this, i); + for(ItemCountListener l : llMIDC) l.itemCountChanged(e); + } catch(NumberFormatException x) { + getLogger().log ( + Level.WARNING, "Unknown MIDI_INPUT_DEVICE_COUNT format", x + ); + } + } else if(s.startsWith("MIDI_INPUT_DEVICE_INFO:")) { + try { + s = s.substring("MIDI_INPUT_DEVICE_INFO:".length()); + int i = Integer.parseInt(s); + ItemInfoEvent e = new ItemInfoEvent(this, i); + for(ItemInfoListener l : llMIDI) l.itemInfoChanged(e); + } catch(NumberFormatException x) { + getLogger().log ( + Level.WARNING, "Unknown MIDI_INPUT_DEVICE_INFO format", x + ); + } + } else if(s.startsWith("MIDI_INSTRUMENT_MAP_COUNT:")) { + try { + s = s.substring("MIDI_INSTRUMENT_MAP_COUNT:".length()); + int i = Integer.parseInt(s); + ItemCountEvent e = new ItemCountEvent(this, i); + for(ItemCountListener l : llMIMC) l.itemCountChanged(e); + } catch(NumberFormatException x) { + getLogger().log ( + Level.WARNING, "Unknown MIDI_INSTRUMENT_MAP_COUNT format", x + ); + } + } else if(s.startsWith("MIDI_INSTRUMENT_MAP_INFO:")) { + try { + s = s.substring("MIDI_INSTRUMENT_MAP_INFO:".length()); + int i = Integer.parseInt(s); + ItemInfoEvent e = new ItemInfoEvent(this, i); + for(ItemInfoListener l : llMIMI) l.itemInfoChanged(e); + } catch(NumberFormatException x) { + getLogger().log ( + Level.WARNING, "Unknown MIDI_INSTRUMENT_MAP_INFO format", x + ); + } + } else if(s.startsWith("MIDI_INSTRUMENT_COUNT:")) { + try { + s = s.substring("MIDI_INSTRUMENT_COUNT:".length()); + Integer[] i = parseIntList(s, ' '); + if(i.length != 2) { + getLogger().warning("Unknown MIDI_INSTRUMENT_COUNT format"); + return; + } + + MidiInstrumentCountEvent e = + new MidiInstrumentCountEvent(this, i[0], i[1]); + + for(MidiInstrumentCountListener l : llMIC) { + l.instrumentCountChanged(e); + } + } catch(Exception x) { + getLogger().log ( + Level.WARNING, "Unknown MIDI_INSTRUMENT_COUNT format", x + ); + } + } else if(s.startsWith("MIDI_INSTRUMENT_INFO:")) { + try { + s = s.substring("MIDI_INSTRUMENT_INFO:".length()); + Integer[] i = parseIntList(s, ' '); + if(i.length != 3) { + getLogger().warning("Unknown MIDI_INSTRUMENT_INFO format"); + return; + } + + MidiInstrumentInfoEvent e = + new MidiInstrumentInfoEvent(this, i[0], i[1], i[2]); + for(MidiInstrumentInfoListener l : llMII) { + l.instrumentInfoChanged(e); + } + } catch(Exception x) { + getLogger().log ( + Level.WARNING, "Unknown MIDI_INSTRUMENT_INFO format", x + ); + } + } else if(s.startsWith("FX_SEND_COUNT:")) { + try { + s = s.substring("FX_SEND_COUNT:".length()); + Integer[] i = parseIntList(s, ' '); + if(i.length != 2) { + getLogger().warning("Unknown FX_SEND_COUNT format"); + return; + } + + FxSendCountEvent e = new FxSendCountEvent(this, i[0], i[1]); + + for(FxSendCountListener l : llFSC) l.fxSendCountChanged(e); + } catch(Exception x) { + getLogger().log(Level.WARNING, "Unknown FX_SEND_COUNT format", x); + } + } else if(s.startsWith("FX_SEND_INFO:")) { + try { + s = s.substring("FX_SEND_INFO:".length()); + Integer[] i = parseIntList(s, ' '); + if(i.length != 2) { + getLogger().warning("Unknown FX_SEND_INFO format"); + return; + } + + FxSendInfoEvent e = new FxSendInfoEvent(this, i[0], i[1]); + for(FxSendInfoListener l : llFSI) { + l.fxSendInfoChanged(e); + } + } catch(Exception x) { + getLogger().log(Level.WARNING, "Unknown FX_SEND_INFO format", x); + } + } else if(s.startsWith("GLOBAL_INFO:")) { + handleGlobalInfoEvent(s.substring("GLOBAL_INFO:".length())); } else if(s.startsWith("MISCELLANEOUS:")) { s = s.substring("MISCELLANEOUS:".length()); MiscellaneousEvent e = new MiscellaneousEvent(this, s); @@ -477,14 +958,39 @@ } private void + handleGlobalInfoEvent(String s) { + try { + if(s.startsWith("VOLUME ")) { + float f = Float.parseFloat(s.substring("VOLUME ".length())); + GlobalInfoEvent e = new GlobalInfoEvent(this, f); + for(GlobalInfoListener l : llGI) l.volumeChanged(e); + } else if(s.startsWith("VOICES ")) { + int i = Integer.parseInt(s.substring("VOICES ".length())); + GlobalInfoEvent e = new GlobalInfoEvent(this, i, -1); + for(GlobalInfoListener l : llGI) l.voiceLimitChanged(e); + } else if(s.startsWith("STREAMS ")) { + int i = Integer.parseInt(s.substring("STREAMS ".length())); + GlobalInfoEvent e = new GlobalInfoEvent(this, -1, i); + for(GlobalInfoListener l : llGI) l.streamLimitChanged(e); + } else { + getLogger().info("Unknown GLOBAL_INFO format: " + s); + } + } catch(NumberFormatException x) { + getLogger().log(Level.WARNING, "Unknown GLOBAL_INFO format", x); + } + } + + private void subscribe(String event) { - if(!isConnected()) return; + if(!getPrintOnlyMode()) { + if(!isConnected()) return; - if(!eventThread.isAlive()) eventThread.start(); + if(!eventThread.isAlive()) eventThread.start(); + } try { out.writeLine("SUBSCRIBE " + event); - ResultSet rs = getEmptyResultSet(); + if(!getPrintOnlyMode()) getEmptyResultSet(); } catch(Exception x) { getLogger().log ( Level.WARNING, @@ -496,11 +1002,11 @@ private void unsubscribe(String event) { - if(!isConnected()) return; + if(!getPrintOnlyMode() && !isConnected()) return; try { out.writeLine("UNSUBSCRIBE " + event); - ResultSet rs = getEmptyResultSet(); + if(!getPrintOnlyMode()) getEmptyResultSet(); } catch(Exception x) { getLogger().log ( Level.WARNING, @@ -512,6 +1018,50 @@ /** * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The ItemCountListener to register. + */ + public synchronized void + addAudioDeviceCountListener(ItemCountListener l) { + if(llAODC.isEmpty()) subscribe("AUDIO_OUTPUT_DEVICE_COUNT"); + llAODC.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The ItemCountListener to remove. + */ + public synchronized void + removeAudioDeviceCountListener(ItemCountListener l) { + boolean b = llAODC.remove(l); + if(b && llAODC.isEmpty()) unsubscribe("AUDIO_OUTPUT_DEVICE_COUNT"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The ItemInfoListener to register. + */ + public synchronized void + addAudioDeviceInfoListener(ItemInfoListener l) { + if(llAODI.isEmpty()) subscribe("AUDIO_OUTPUT_DEVICE_INFO"); + llAODI.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The ItemInfoListener to remove. + */ + public synchronized void + removeAudioDeviceInfoListener(ItemInfoListener l) { + boolean b = llAODI.remove(l); + if(b && llAODI.isEmpty()) unsubscribe("AUDIO_OUTPUT_DEVICE_INFO"); + } + + /** + * Registers the specified listener for receiving event messages. * Listeners can be removed regardless of the connection state. * @param l The BufferFillListener to register. */ @@ -579,6 +1129,94 @@ /** * Registers the specified listener for receiving event messages. * Listeners can be registered regardless of the connection state. + * @param l The FxSendCountListener to register. + */ + public synchronized void + addFxSendCountListener(FxSendCountListener l) { + if(llFSC.isEmpty()) subscribe("FX_SEND_COUNT"); + llFSC.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The FxSendCountListener to remove. + */ + public synchronized void + removeFxSendCountListener(FxSendCountListener l) { + boolean b = llFSC.remove(l); + if(b && llFSC.isEmpty()) unsubscribe("FX_SEND_COUNT"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The FxSendInfoListener to register. + */ + public synchronized void + addFxSendInfoListener(FxSendInfoListener l) { + if(llFSI.isEmpty()) subscribe("FX_SEND_INFO"); + llFSI.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The FxSendInfoListener to remove. + */ + public synchronized void + removeFxSendInfoListener(FxSendInfoListener l) { + boolean b = llFSI.remove(l); + if(b && llFSI.isEmpty()) unsubscribe("FX_SEND_INFO"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The ItemCountListener to register. + */ + public synchronized void + addMidiDeviceCountListener(ItemCountListener l) { + if(llMIDC.isEmpty()) subscribe("MIDI_INPUT_DEVICE_COUNT"); + llMIDC.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The ItemCountListener to remove. + */ + public synchronized void + removeMidiDeviceCountListener(ItemCountListener l) { + boolean b = llMIDC.remove(l); + if(b && llMIDC.isEmpty()) unsubscribe("MIDI_INPUT_DEVICE_COUNT"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The ItemInfoListener to register. + */ + public synchronized void + addMidiDeviceInfoListener(ItemInfoListener l) { + if(llMIDI.isEmpty()) subscribe("MIDI_INPUT_DEVICE_INFO"); + llMIDI.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The ItemInfoListener to remove. + */ + public synchronized void + removeMidiDeviceInfoListener(ItemInfoListener l) { + boolean b = llMIDI.remove(l); + if(b && llMIDI.isEmpty()) unsubscribe("MIDI_INPUT_DEVICE_INFO"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. * @param l The MiscellaneousListener to register. */ public synchronized void @@ -645,6 +1283,28 @@ /** * Registers the specified listener for receiving event messages. * Listeners can be registered regardless of the connection state. + * @param l The TotalStreamCountListener to register. + */ + public synchronized void + addTotalStreamCountListener(TotalStreamCountListener l) { + if(llTSC.isEmpty()) subscribe("TOTAL_STREAM_COUNT"); + llTSC.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The TotalStreamCountListener to remove. + */ + public synchronized void + removeTotalStreamCountListener(TotalStreamCountListener l) { + boolean b = llTSC.remove(l); + if(b && llTSC.isEmpty()) unsubscribe("TOTAL_STREAM_COUNT"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. * @param l The TotalVoiceCountListener to register. */ public synchronized void @@ -665,6 +1325,194 @@ } /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The ItemCountListener to register. + */ + public synchronized void + addMidiInstrumentMapCountListener(ItemCountListener l) { + if(llMIMC.isEmpty()) subscribe("MIDI_INSTRUMENT_MAP_COUNT"); + llMIMC.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The ItemCountListener to remove. + */ + public synchronized void + removeMidiInstrumentMapCountListener(ItemCountListener l) { + boolean b = llMIMC.remove(l); + if(b && llMIMC.isEmpty()) unsubscribe("MIDI_INSTRUMENT_MAP_COUNT"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The ItemInfoListener to register. + */ + public synchronized void + addMidiInstrumentMapInfoListener(ItemInfoListener l) { + if(llMIMI.isEmpty()) subscribe("MIDI_INSTRUMENT_MAP_INFO"); + llMIMI.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The ItemInfoListener to remove. + */ + public synchronized void + removeMidiInstrumentMapInfoListener(ItemInfoListener l) { + boolean b = llMIMI.remove(l); + if(b && llMIMI.isEmpty()) unsubscribe("MIDI_INSTRUMENT_MAP_INFO"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The MidiInstrumentCountListener to register. + */ + public synchronized void + addMidiInstrumentCountListener(MidiInstrumentCountListener l) { + if(llMIC.isEmpty()) subscribe("MIDI_INSTRUMENT_COUNT"); + llMIC.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The MidiInstrumentCountListener to remove. + */ + public synchronized void + removeMidiInstrumentCountListener(MidiInstrumentCountListener l) { + boolean b = llMIC.remove(l); + if(b && llMIC.isEmpty()) unsubscribe("MIDI_INSTRUMENT_COUNT"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The MidiInstrumentInfoListener to register. + */ + public synchronized void + addMidiInstrumentInfoListener(MidiInstrumentInfoListener l) { + if(llMII.isEmpty()) subscribe("MIDI_INSTRUMENT_INFO"); + llMII.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The MidiInstrumentInfoListener to remove. + */ + public synchronized void + removeMidiInstrumentInfoListener(MidiInstrumentInfoListener l) { + boolean b = llMII.remove(l); + if(b && llMII.isEmpty()) unsubscribe("MIDI_INSTRUMENT_INFO"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The DeviceMidiDataListener to register. + */ + public synchronized void + addDeviceMidiDataListener(DeviceMidiDataListener l) { + if(llDMD.isEmpty()) subscribe("DEVICE_MIDI"); + llDMD.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The DeviceMidiDataListener to remove. + */ + public synchronized void + removeDeviceMidiDataListener(DeviceMidiDataListener l) { + boolean b = llDMD.remove(l); + if(b && llDMD.isEmpty()) unsubscribe("DEVICE_MIDI"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The ChannelMidiDataListener to register. + */ + public synchronized void + addChannelMidiDataListener(ChannelMidiDataListener l) { + if(llCMD.isEmpty()) subscribe("CHANNEL_MIDI"); + llCMD.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The ChannelMidiDataListener to remove. + */ + public synchronized void + removeChannelMidiDataListener(ChannelMidiDataListener l) { + boolean b = llCMD.remove(l); + if(b && llCMD.isEmpty()) unsubscribe("CHANNEL_MIDI"); + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The InstrumentsDbListener to register. + */ + public synchronized void + addInstrumentsDbListener(InstrumentsDbListener l) { + if(llID.isEmpty()) { + subscribe("DB_INSTRUMENT_DIRECTORY_COUNT"); + subscribe("DB_INSTRUMENT_DIRECTORY_INFO"); + subscribe("DB_INSTRUMENT_COUNT"); + subscribe("DB_INSTRUMENT_INFO"); + subscribe("DB_INSTRUMENTS_JOB_INFO"); + } + llID.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The InstrumentsDbListener to remove. + */ + public synchronized void + removeInstrumentsDbListener(InstrumentsDbListener l) { + boolean b = llID.remove(l); + if(b && llID.isEmpty()) { + unsubscribe("DB_INSTRUMENT_DIRECTORY_COUNT"); + unsubscribe("DB_INSTRUMENT_DIRECTORY_INFO"); + unsubscribe("DB_INSTRUMENT_COUNT"); + unsubscribe("DB_INSTRUMENT_INFO"); + unsubscribe("DB_INSTRUMENTS_JOB_INFO"); + } + } + + /** + * Registers the specified listener for receiving event messages. + * Listeners can be registered regardless of the connection state. + * @param l The GlobalInfoListener to register. + */ + public synchronized void + addGlobalInfoListener(GlobalInfoListener l) { + if(llGI.isEmpty()) subscribe("GLOBAL_INFO"); + llGI.add(l); + } + + /** + * Removes the specified listener. + * Listeners can be removed regardless of the connection state. + * @param l The GlobalInfoListener to remove. + */ + public synchronized void + removeGlobalInfoListener(GlobalInfoListener l) { + boolean b = llGI.remove(l); + if(b && llGI.isEmpty()) unsubscribe("GLOBAL_INFO"); + } + + /** * Gets the number of all audio output drivers currently * available for the LinuxSampler instance. * @return The number of all audio output drivers currently @@ -677,6 +1525,9 @@ getAudioOutputDriverCount() throws IOException, LscpException, LSException { verifyConnection(); out.writeLine("GET AVAILABLE_AUDIO_OUTPUT_DRIVERS"); + + if(getPrintOnlyMode()) return -1; + String s = getSingleLineResultSet().getResult(); return parseInt(s); } @@ -694,6 +1545,8 @@ public synchronized AudioOutputDriver[] getAudioOutputDrivers() throws IOException, LscpException, LSException { String[] drivers = getAudioOutputDriverNames(); + if(getPrintOnlyMode()) return null; + AudioOutputDriver[] aod = new AudioOutputDriver[drivers.length]; for(int i = 0; i < aod.length; i++) aod[i] = getAudioOutputDriverInfo(drivers[i]); @@ -715,13 +1568,14 @@ getAudioOutputDriverNames() throws IOException, LscpException, LSException { verifyConnection(); out.writeLine("LIST AVAILABLE_AUDIO_OUTPUT_DRIVERS"); + if(getPrintOnlyMode()) return null; return parseList(getSingleLineResultSet().getResult()); } /** * Gets detailed information about a specific audio output driver. * @param driverName The name of the audio output driver. - * + * @param depList An optional list of dependences parameters. * @return An AudioOutputDriver object containing * information about the specified audio output driver. * @@ -731,16 +1585,20 @@ * * @see #getAudioOutputDriverNames */ - private synchronized AudioOutputDriver - getAudioOutputDriverInfo(String driverName) throws IOException, LscpException, LSException { + public synchronized AudioOutputDriver + getAudioOutputDriverInfo(String driverName, Parameter... depList) + throws IOException, LscpException, LSException { + verifyConnection(); out.writeLine("GET AUDIO_OUTPUT_DRIVER INFO " + driverName); + if(getPrintOnlyMode()) return null; + ResultSet rs = getMultiLineResultSet(); AudioOutputDriver aod = new AudioOutputDriver(rs.getMultiLineResult()); aod.setName(driverName); for(String s : aod.getParameterNames()) - aod.addParameter(getAudioOutputDriverParameterInfo(driverName, s)); + aod.addParameter(getAudioOutputDriverParameterInfo(driverName, s, depList)); return aod; } @@ -774,10 +1632,13 @@ StringBuffer args = new StringBuffer(driver); args.append(' ').append(param); - for(Parameter p : deplist) + for(Parameter p : deplist) { + if(p == null || p.getName() == null || p.getValue() == null) continue; args.append(' ').append(p.getName()).append('=').append(p.getStringValue()); + } out.writeLine("GET AUDIO_OUTPUT_DRIVER_PARAMETER INFO " + args.toString()); + if(getPrintOnlyMode()) return null; ResultSet rs = getMultiLineResultSet(); @@ -835,10 +1696,14 @@ verifyConnection(); StringBuffer args = new StringBuffer(aoDriver); - for(Parameter p : paramList) + for(Parameter p : paramList) { + if(p == null || p.getName() == null || p.getValue() == null) continue; args.append(' ').append(p.getName()).append('=').append(p.getStringValue()); + } out.writeLine("CREATE AUDIO_OUTPUT_DEVICE " + args.toString()); + if(getPrintOnlyMode()) return -1; + ResultSet rs = getEmptyResultSet(); return rs.getIndex(); @@ -846,34 +1711,36 @@ /** * Destroys already created audio output device. - * @param deviceID The ID of the audio output device to be destroyed. + * @param deviceId The ID of the audio output device to be destroyed. * @throws IOException If some I/O error occurs. * @throws LSException If the destroying of the audio output device failed. * @throws LscpException If LSCP protocol corruption occurs. * @see #getAudioOutputDevices */ public synchronized void - destroyAudioOutputDevice(int deviceID) throws IOException, LSException, LscpException { + destroyAudioOutputDevice(int deviceId) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("DESTROY AUDIO_OUTPUT_DEVICE " + deviceID); + out.writeLine("DESTROY AUDIO_OUTPUT_DEVICE " + deviceId); + if(getPrintOnlyMode()) return; + ResultSet rs = getEmptyResultSet(); } /** * Enables/disables the specified audio output device. - * @param deviceID The ID of the audio output device to be enabled/disabled. + * @param deviceId The ID of the audio output device to be enabled/disabled. * @param enable If true the audio output device is enabled, * else the device is disabled. * @throws IOException If some I/O error occurs. * @throws LSException If there is no audio output - * device with numerical ID deviceID. + * device with numerical ID deviceId. * @throws LscpException If LSCP protocol corruption occurs. */ public void - enableAudioOutputDevice(int deviceID, boolean enable) + enableAudioOutputDevice(int deviceId, boolean enable) throws IOException, LSException, LscpException { - setAudioOutputDeviceParameter(deviceID, new BoolParameter("ACTIVE", enable)); + setAudioOutputDeviceParameter(deviceId, new BoolParameter("ACTIVE", enable)); } /** @@ -887,6 +1754,8 @@ getAudioOutputDeviceCount() throws IOException, LscpException, LSException { verifyConnection(); out.writeLine("GET AUDIO_OUTPUT_DEVICES"); + if(getPrintOnlyMode()) return -1; + String s = getSingleLineResultSet().getResult(); return parseInt(s); } @@ -902,6 +1771,8 @@ public synchronized AudioOutputDevice[] getAudioOutputDevices() throws IOException, LscpException, LSException { Integer[] idS = getAudioOutputDeviceIDs(); + if(getPrintOnlyMode()) return null; + AudioOutputDevice[] devices = new AudioOutputDevice[idS.length]; for(int i = 0; i < devices.length; i++) @@ -922,13 +1793,15 @@ getAudioOutputDeviceIDs() throws IOException, LscpException, LSException { verifyConnection(); out.writeLine("LIST AUDIO_OUTPUT_DEVICES"); + if(getPrintOnlyMode()) return null; + return parseIntList(getSingleLineResultSet().getResult()); } /** * Gets the current settings of a specific, already created audio output device. * - * @param deviceID Specifies the numerical ID of the audio output device. + * @param deviceId Specifies the numerical ID of the audio output device. * * @return An AudioOutputDevice instance containing information * about the specified device. @@ -936,21 +1809,22 @@ * @throws IOException If some I/O error occurs. * @throws LscpException If LSCP protocol corruption occurs. * @throws LSException If there is no audio output device - * with device id deviceID. + * with device id deviceId. * * @see #getAudioOutputDevices */ public synchronized AudioOutputDevice - getAudioOutputDeviceInfo(int deviceID) throws IOException, LscpException, LSException { + getAudioOutputDeviceInfo(int deviceId) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("GET AUDIO_OUTPUT_DEVICE INFO " + deviceID); + out.writeLine("GET AUDIO_OUTPUT_DEVICE INFO " + deviceId); + if(getPrintOnlyMode()) return null; ResultSet rs = getMultiLineResultSet(); String[] lnS = rs.getMultiLineResult(); AudioOutputDevice aod = new AudioOutputDevice(); - aod.setDeviceID(deviceID); + aod.setDeviceId(deviceId); Parameter channels; Parameter samplerate; @@ -968,7 +1842,7 @@ int count = channels.getValue() > 0 ? channels.getValue() : 0; AudioOutputChannel[] aoc = new AudioOutputChannel[count]; for(int i = 0; i < count; i++) { - aoc[i] = this.getAudioOutputChannelInfo(deviceID, i); + aoc[i] = getAudioOutputChannelInfo(deviceId, i); } aod.setAudioChannels(aoc); } else if(s.startsWith("SAMPLERATE: ")) { @@ -1005,7 +1879,7 @@ /** * Alters a specific setting of a created audio output device. * - * @param deviceID The numerical ID of the audio output device. + * @param deviceId The numerical ID of the audio output device. * @param prm A Parameter instance containing the name of the parameter * and the new value for this parameter. * @@ -1013,7 +1887,7 @@ * @throws LscpException If LSCP protocol corruption occurs. * @throws LSException If *