--- jlscp/trunk/src/org/linuxsampler/lscp/Client.java 2007/10/07 20:29:41 1393 +++ jlscp/trunk/src/org/linuxsampler/lscp/Client.java 2009/02/28 21:35:09 1849 @@ -1,7 +1,7 @@ /* * jlscp - a java LinuxSampler control protocol API * - * Copyright (C) 2005-2007 Grigor Iliev + * Copyright (C) 2005-2008 Grigor Iliev * * This file is part of jlscp. * @@ -28,7 +28,6 @@ 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; @@ -41,9 +40,9 @@ /** * 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.1, 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: @@ -85,6 +84,7 @@ EventThread() { super("LSCP-Event-Thread"); } + @Override public void run() { while(!mustTerminate()) { @@ -164,6 +164,51 @@ 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 @@ -344,16 +389,20 @@ 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"); } @@ -485,6 +534,7 @@ 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 */ @@ -497,6 +547,8 @@ /** 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(); @@ -522,18 +574,94 @@ !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 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("DB_INSTRUMENT_DIRECTORY_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); @@ -662,6 +790,17 @@ } catch(NumberFormatException 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 { s = s.substring("TOTAL_VOICE_COUNT:".length()); @@ -825,6 +964,16 @@ 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); @@ -1134,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 @@ -1244,6 +1415,50 @@ /** * 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 @@ -1418,7 +1633,7 @@ args.append(' ').append(param); for(Parameter p : deplist) { - if(p.getValue() == null) continue; + if(p == null || p.getName() == null || p.getValue() == null) continue; args.append(' ').append(p.getName()).append('=').append(p.getStringValue()); } @@ -1482,7 +1697,7 @@ StringBuffer args = new StringBuffer(aoDriver); for(Parameter p : paramList) { - if(p.getValue() == null) continue; + if(p == null || p.getName() == null || p.getValue() == null) continue; args.append(' ').append(p.getName()).append('=').append(p.getStringValue()); } @@ -2004,7 +2219,7 @@ args.append(' ').append(param); for(Parameter p : deplist) { - if(p.getValue() == null) continue; + if(p == null || p.getName() == null || p.getValue() == null) continue; args.append(' ').append(p.getName()).append('=').append(p.getStringValue()); } @@ -2069,7 +2284,7 @@ StringBuffer args = new StringBuffer(miDriver); for(Parameter p : paramList) { - if(p.getValue() == null) continue; + if(p == null || p.getName() == null || p.getValue() == null) continue; args.append(' ').append(p.getName()).append('=').append(p.getStringValue()); } @@ -2216,8 +2431,15 @@ mid.setActive(Boolean.parseBoolean(s)); } else if(s.startsWith("PORTS: ")) { s = s.substring("PORTS: ".length()); - int ports = Parser.parseInt(s); - MidiPort[] midiPorts = new MidiPort[ports > 0 ? ports : 0]; + + Parameter ports = (Parameter) + getMidiInputDriverParameterInfo(drv, "PORTS"); + + ports.parseValue(s); + mid.setPortsParameter(ports); + + int j = ports.getValue(); + MidiPort[] midiPorts = new MidiPort[j > 0 ? j : 0]; for(int i = 0; i < midiPorts.length; i++) midiPorts[i] = getMidiInputPortInfo(deviceId, i); @@ -2460,7 +2682,7 @@ public synchronized int addMidiInstrumentMap(String name) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("ADD MIDI_INSTRUMENT_MAP '" + toEscapedString(name) + "'"); + out.writeLine("ADD MIDI_INSTRUMENT_MAP '" + toEscapedText(name) + "'"); if(getPrintOnlyMode()) return -1; ResultSet rs = getEmptyResultSet(); @@ -2563,7 +2785,7 @@ for(String s : lnS) { if(s.startsWith("NAME: ")) { - name = s.substring("NAME: ".length()); + name = toNonEscapedString(s.substring("NAME: ".length())); } else if(s.startsWith("DEFAULT: ")) { b = Boolean.parseBoolean(s.substring("DEFAULT: ".length())); } else { @@ -2611,7 +2833,7 @@ throws IOException, LscpException, LSException { verifyConnection(); - name = toEscapedString(name); + name = toEscapedText(name); out.writeLine("SET MIDI_INSTRUMENT_MAP NAME " + + mapId + " '" + name + "'"); if(getPrintOnlyMode()) return; @@ -2661,7 +2883,7 @@ cmd.append(entry.getMidiBank()).append(' '); cmd.append(entry.getMidiProgram()).append(' '); cmd.append(info.getEngine()).append(" '"); - cmd.append(info.getFilePath()).append("' "); + cmd.append(conv(info.getFilePath())).append("' "); cmd.append(info.getInstrumentIndex()).append(' '); cmd.append(info.getVolume()); if(!info.getLoadMode().name().equals("DEFAULT")) { @@ -2669,7 +2891,7 @@ } if(info.getName() != null) { - String s = toEscapedString(info.getName()); + String s = toEscapedText(info.getName()); cmd.append(" '").append(s).append("'"); } @@ -2760,10 +2982,42 @@ } /** - * Gets all MIDI instrument contained int the specified MIDI instrument map. + * Gets all MIDI instrument entries contained int the specified MIDI instrument map. + * @param mapId The ID of the map, which instruments should be obtained. + * @return An int array providing all MIDI instrument entries + * in the specified MIDI instrument map. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If some other error occurs. + */ + public synchronized int[][] + getMidiInstrumentEntries(int mapId) throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("LIST MIDI_INSTRUMENTS " + String.valueOf(mapId)); + if(getPrintOnlyMode()) return null; + + String[] entries = parseArray(getSingleLineResultSet().getResult()); + int[][] e = new int[entries.length][3]; + + for(int i = 0; i < entries.length; i++) { + Integer[] vals = parseIntList(entries[i]); + if(vals.length != 3) { + throw new LscpException(LscpI18n.getLogMsg("CommandFailed!")); + } + + e[i][0] = vals[0]; + e[i][1] = vals[1]; + e[i][2] = vals[2]; + } + + return e; + } + + /** + * Gets all MIDI instruments contained int the specified MIDI instrument map. * @param mapId The ID of the map, which instruments should be obtained. * @return A MidiInstrumentInfo array providing - * all MIDI instruments from all MIDI instrument maps. + * all MIDI instruments in the specified MIDI instrument map. * @throws IOException If some I/O error occurs. * @throws LscpException If LSCP protocol corruption occurs. * @throws LSException If some other error occurs. @@ -2811,14 +3065,25 @@ throws IOException, LscpException, LSException { verifyConnection(); + requestMidiInstrumentInfo(mapId, bank, program); + return getMidiInstrumentInfoResponse(mapId, bank, program); + } + + private void + requestMidiInstrumentInfo(int mapId, int bank, int program) throws IOException { StringBuffer cmd = new StringBuffer("GET MIDI_INSTRUMENT INFO "); cmd.append(mapId).append(' '); cmd.append(bank).append(' '); cmd.append(program); out.writeLine(cmd.toString()); - if(getPrintOnlyMode()) return null; + } + + private MidiInstrumentInfo + getMidiInstrumentInfoResponse(int mapId, int bank, int program) + throws IOException, LscpException, LSException { + if(getPrintOnlyMode()) return null; ResultSet rs = getMultiLineResultSet(); MidiInstrumentEntry entry = new MidiInstrumentEntry(bank, program); return new MidiInstrumentInfo(mapId, entry, rs.getMultiLineResult()); @@ -2868,7 +3133,7 @@ throws IOException, LscpException, LSException { String cmd = nonModal ? "LOAD INSTRUMENT NON_MODAL " : "LOAD INSTRUMENT "; - String args = '\'' + filename + "' " + instrIdx + ' ' + samplerChn; + String args = '\'' + conv(filename) + "' " + instrIdx + ' ' + samplerChn; out.writeLine(cmd + args); if(getPrintOnlyMode()) return; @@ -3520,7 +3785,7 @@ verifyConnection(); String s = String.valueOf(channel) + " " + String.valueOf(midiCtrl); - if(name != null) s += " '" + toEscapedString(name) + "'"; + if(name != null) s += " '" + toEscapedText(name) + "'"; out.writeLine("CREATE FX_SEND " + s); if(getPrintOnlyMode()) return -1; @@ -3651,7 +3916,7 @@ throws IOException, LscpException, LSException { verifyConnection(); - String args = " " + channel + " " + fxSend + " '" + toEscapedString(name) + "'"; + String args = " " + channel + " " + fxSend + " '" + toEscapedText(name) + "'"; out.writeLine("SET FX_SEND NAME" + args); if(getPrintOnlyMode()) return; @@ -3750,9 +4015,55 @@ * @see #getSamplerChannels */ public synchronized void - editInstrument(int samplerChn) throws IOException, LscpException, LSException { + editChannelInstrument(int samplerChn) throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("EDIT CHANNEL INSTRUMENT " + samplerChn); + if(getPrintOnlyMode()) return; + + ResultSet rs = getEmptyResultSet(); + } + + /** + * Sends a MIDI event to this sampler channel. + * @param samplerChn The sampler channel number. + * @param type The type of MIDI message to send. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If samplerChn is not a valid channel number or if + * there is no instrument loaded on the specified sampler channel. + * @see #getSamplerChannels + */ + public synchronized void + sendChannelMidiData(int samplerChn, MidiDataEvent.Type type, int arg1, int arg2) + throws IOException, LscpException, LSException { + + verifyConnection(); + StringBuffer sb = new StringBuffer(); + sb.append("SEND CHANNEL MIDI_DATA "); + sb.append(type).append(" ").append(samplerChn).append(" "); + sb.append(arg1).append(" ").append(arg2); + + out.writeLine(sb.toString()); + if(getPrintOnlyMode()) return; + + ResultSet rs = getEmptyResultSet(); + } + + /** + * Resets the specified sampler channel. + * + * @param samplerChn The sampler channel number. + * + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If samplerChn is not a valid channel number or if + * there is no engine assigned yet to the specified sampler channel. + * @see #getSamplerChannels + */ + public synchronized void + resetChannel(int samplerChn) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("EDIT INSTRUMENT " + samplerChn); + out.writeLine("RESET CHANNEL " + samplerChn); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -3770,7 +4081,7 @@ public synchronized void addDbDirectory(String dir) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("ADD DB_INSTRUMENT_DIRECTORY '" + dir + "'"); + out.writeLine("ADD DB_INSTRUMENT_DIRECTORY '" + conv(dir) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -3805,7 +4116,7 @@ verifyConnection(); String s = "REMOVE DB_INSTRUMENT_DIRECTORY "; if(force) s += "FORCE "; - out.writeLine(s + "'" + dir + "'"); + out.writeLine(s + "'" + conv(dir) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -3828,7 +4139,7 @@ String cmd = "REMOVE DB_INSTRUMENT_DIRECTORY "; if(force) cmd += "FORCE "; - for(String s : dirs) out.writeLine(cmd + "'" + s + "'"); + for(String s : dirs) out.writeLine(cmd + "'" + conv(s) + "'"); if(getPrintOnlyMode()) return; @@ -3866,7 +4177,7 @@ String s; if(recursive) s = "GET DB_INSTRUMENT_DIRECTORIES RECURSIVE '"; else s = "GET DB_INSTRUMENT_DIRECTORIES '"; - out.writeLine(s + dir + "'"); + out.writeLine(s + conv(dir) + "'"); if(getPrintOnlyMode()) return -1; s = getSingleLineResultSet().getResult(); @@ -3885,7 +4196,7 @@ public synchronized String[] getDbDirectoryNames(String dir) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("LIST DB_INSTRUMENT_DIRECTORIES '" + dir + "'"); + out.writeLine("LIST DB_INSTRUMENT_DIRECTORIES '" + conv(dir) + "'"); if(getPrintOnlyMode()) return null; String[] names = parseEscapedStringList(getSingleLineResultSet().getResult()); @@ -3907,7 +4218,7 @@ public synchronized DbDirectoryInfo getDbDirectoryInfo(String dir) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("GET DB_INSTRUMENT_DIRECTORY INFO '" + dir + "'"); + out.writeLine("GET DB_INSTRUMENT_DIRECTORY INFO '" + conv(dir) + "'"); if(getPrintOnlyMode()) return null; ResultSet rs = getMultiLineResultSet(); @@ -3940,7 +4251,7 @@ if(!hasEndingFileSeparator(dir)) dir += "/"; DbDirectoryInfo[] infoS = new DbDirectoryInfo[dirS.length]; for(int i = 0; i < dirS.length; i++) { - infoS[i] = getDbDirectoryInfo(dir + toEscapedFileName(dirS[i])); + infoS[i] = getDbDirectoryInfo(conv(dir) + toEscapedFsEntry(dirS[i])); } return infoS; } @@ -3956,13 +4267,13 @@ * public synchronized DbDirectoryInfo[] getDbDirectories(String dir) throws IOException, LscpException, LSException { - String[] dirS = getDbDirectoryNames(dir); + String[] dirS = getDbDirectoryNames(conv(dir)); if(dirS.length == 0) return new DbDirectoryInfo[0]; - if(dir.charAt(dir.length() - 1) != '/') dir += "/"; + if(dir.charAt(dir.length() - 1) != '/') dir += "/"; // FIXME: for(int i = 0; i < dirS.length; i++) { - out.writeLine("GET DB_INSTRUMENT_DIRECTORY INFO '" + dir + dirS[i] + "'"); + out.writeLine("GET DB_INSTRUMENT_DIRECTORY INFO '" + conv(dir + dirS[i]) + "'"); } if(getPrintOnlyMode()) return null; @@ -4006,8 +4317,8 @@ public synchronized void renameDbDirectory(String dir, String name) throws IOException, LSException, LscpException { verifyConnection(); - name = toEscapedString(name); - out.writeLine("SET DB_INSTRUMENT_DIRECTORY NAME '" + dir + "' '" + name + "'"); + name = toEscapedText(name); + out.writeLine("SET DB_INSTRUMENT_DIRECTORY NAME '" + conv(dir) + "' '" + conv(name) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4024,7 +4335,7 @@ public synchronized void moveDbDirectory(String dir, String dst) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("MOVE DB_INSTRUMENT_DIRECTORY '" + dir + "' '" + dst + "'"); + out.writeLine("MOVE DB_INSTRUMENT_DIRECTORY '" + conv(dir) + "' '" + conv(dst) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4042,7 +4353,7 @@ moveDbDirectories(String dirs[], String dst) throws IOException, LSException, LscpException { verifyConnection(); for(String s : dirs) { - out.writeLine("MOVE DB_INSTRUMENT_DIRECTORY '" + s + "' '" + dst + "'"); + out.writeLine("MOVE DB_INSTRUMENT_DIRECTORY '" + conv(s) + "' '" + conv(dst) + "'"); } if(getPrintOnlyMode()) return; @@ -4060,7 +4371,7 @@ public synchronized void copyDbDirectory(String dir, String dst) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("COPY DB_INSTRUMENT_DIRECTORY '" + dir + "' '" + dst + "'"); + out.writeLine("COPY DB_INSTRUMENT_DIRECTORY '" + conv(dir) + "' '" + conv(dst) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4078,7 +4389,7 @@ copyDbDirectories(String[] dirs, String dst) throws IOException, LSException, LscpException { verifyConnection(); for(String s : dirs) { - out.writeLine("COPY DB_INSTRUMENT_DIRECTORY '" + s + "' '" + dst + "'"); + out.writeLine("COPY DB_INSTRUMENT_DIRECTORY '" + conv(s) + "' '" + conv(dst) + "'"); } if(getPrintOnlyMode()) return; @@ -4099,7 +4410,7 @@ verifyConnection(); String s = "SET DB_INSTRUMENT_DIRECTORY DESCRIPTION '"; - out.writeLine(s + dir + "' '" + toEscapedString(desc) + "'"); + out.writeLine(s + conv(dir) + "' '" + toEscapedText(desc) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4148,7 +4459,7 @@ verifyConnection(); String s = "ADD DB_INSTRUMENTS"; if(background) s += " NON_MODAL"; - s += " '" + dbDir + "' '" + filePath + "' "; + s += " '" + conv(dbDir) + "' '" + conv(filePath) + "' "; out.writeLine(s + String.valueOf(instrIndex)); if(getPrintOnlyMode()) return -1; @@ -4195,7 +4506,7 @@ verifyConnection(); String s = "ADD DB_INSTRUMENTS"; if(background) s += " NON_MODAL"; - out.writeLine(s + " '" + dbDir + "' '" + filePath + "'"); + out.writeLine(s + " '" + conv(dbDir) + "' '" + conv(filePath) + "'"); if(getPrintOnlyMode()) return -1; ResultSet rs = getEmptyResultSet(); @@ -4262,6 +4573,42 @@ addDbInstruments(ScanMode mode, String dbDir, String fsDir, boolean background) throws IOException, LSException, LscpException { + return addDbInstruments(mode, dbDir, fsDir, background, false); + } + + /** + * Adds the instruments in the specified file system directory + * to the specified instruments database directory. + * @param mode Determines the scanning mode. If RECURSIVE is + * specified, all supported instruments in the specified file system + * direcotry will be added to the specified instruments database + * directory, including the instruments in subdirectories + * of the supplied directory. If NON_RECURSIVE is specified, + * the instruments in the subdirectories will not be processed. + * If FLAT is specified, all supported instruments in the specified + * file system direcotry will be added, including the instruments in + * subdirectories of the supplied directory, but the respective + * subdirectory structure will not be recreated in the instruments + * database and all instruments will be added directly in the + * specified database directory. + * @param dbDir The absolute path name of the database directory + * in which the supported instruments will be added. + * @param fsDir The absolute path name of the file system directory. + * @param background If true, the scan will be done + * in background and this method may return before the job is finished. + * @param insDir If true a drieectory is created for each + * instrument file. + * @return If background is true, the ID + * of the scan job. + * @throws IOException If some I/O error occurs. + * @throws LSException If the operation failed. + * @throws LscpException If LSCP protocol corruption occurs. + * @see #addInstrumentsDbListener + */ + public synchronized int + addDbInstruments(ScanMode mode, String dbDir, String fsDir, boolean background, boolean insDir) + throws IOException, LSException, LscpException { + verifyConnection(); StringBuffer sb = new StringBuffer("ADD DB_INSTRUMENTS"); if(background) sb.append(" NON_MODAL"); @@ -4277,16 +4624,18 @@ sb.append(" FLAT"); break; } + if(insDir) + sb.append(" FILE_AS_DIR"); - sb.append(" '").append(dbDir).append("' '"); - sb.append(fsDir).append("'"); + sb.append(" '").append(conv(dbDir)).append("' '"); + sb.append(conv(fsDir)).append("'"); out.writeLine(sb.toString()); if(getPrintOnlyMode()) return -1; ResultSet rs = getEmptyResultSet(); return rs.getIndex(); } - + /** * Removes the specified instrument from the instruments database. * @param instr The absolute path name of the instrument to remove. @@ -4298,7 +4647,7 @@ removeDbInstrument(String instr) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("REMOVE DB_INSTRUMENT '" + instr + "'"); + out.writeLine("REMOVE DB_INSTRUMENT '" + conv(instr) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4315,7 +4664,7 @@ removeDbInstruments(String[] instrs) throws IOException, LscpException, LSException { verifyConnection(); for(String s : instrs) { - out.writeLine("REMOVE DB_INSTRUMENT '" + s + "'"); + out.writeLine("REMOVE DB_INSTRUMENT '" + conv(s) + "'"); } if(getPrintOnlyMode()) return; @@ -4353,7 +4702,7 @@ String s; if(recursive) s = "GET DB_INSTRUMENTS RECURSIVE '"; else s = "GET DB_INSTRUMENTS '"; - out.writeLine(s + dir + "'"); + out.writeLine(s + conv(dir) + "'"); if(getPrintOnlyMode()) return -1; s = getSingleLineResultSet().getResult(); @@ -4372,7 +4721,7 @@ public synchronized String[] getDbInstrumentNames(String dir) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("LIST DB_INSTRUMENTS '" + dir + "'"); + out.writeLine("LIST DB_INSTRUMENTS '" + conv(dir) + "'"); if(getPrintOnlyMode()) return null; String[] names = parseEscapedStringList(getSingleLineResultSet().getResult()); @@ -4394,7 +4743,7 @@ public synchronized DbInstrumentInfo getDbInstrumentInfo(String instr) throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("GET DB_INSTRUMENT INFO '" + instr + "'"); + out.writeLine("GET DB_INSTRUMENT INFO '" + conv(instr) + "'"); if(getPrintOnlyMode()) return null; ResultSet rs = getMultiLineResultSet(); @@ -4423,7 +4772,7 @@ DbInstrumentInfo[] infoS = new DbInstrumentInfo[instrS.length]; for(int i = 0; i < instrS.length; i++) { - infoS[i] = getDbInstrumentInfo(dir + toEscapedFileName(instrS[i])); + infoS[i] = getDbInstrumentInfo(conv(dir) + toEscapedFsEntry(instrS[i])); } return infoS; } @@ -4442,10 +4791,10 @@ String[] instrS = getDbInstrumentNames(dir); if(instrS.length == 0) return new DbInstrumentInfo[0]; - if(dir.charAt(dir.length() - 1) != '/') dir += "/"; + if(dir.charAt(dir.length() - 1) != '/') dir += "/"; FIXME: for(int i = 0; i < instrS.length; i++) { - out.writeLine("GET DB_INSTRUMENT INFO '" + dir + instrS[i] + "'"); + out.writeLine("GET DB_INSTRUMENT INFO '" + conv(dir) + instrS[i] + "'"); } if(getPrintOnlyMode()) return null; @@ -4491,8 +4840,8 @@ throws IOException, LSException, LscpException { verifyConnection(); - name = toEscapedString(name); - out.writeLine("SET DB_INSTRUMENT NAME '" + instr + "' '" + name + "'"); + name = toEscapedText(name); + out.writeLine("SET DB_INSTRUMENT NAME '" + conv(instr) + "' '" + conv(name) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4509,7 +4858,7 @@ public synchronized void moveDbInstrument(String instr, String dst) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("MOVE DB_INSTRUMENT '" + instr + "' '" + dst + "'"); + out.writeLine("MOVE DB_INSTRUMENT '" + conv(instr) + "' '" + conv(dst) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4527,7 +4876,7 @@ moveDbInstruments(String[] instrs, String dst) throws IOException, LSException, LscpException { verifyConnection(); for(String s : instrs) { - out.writeLine("MOVE DB_INSTRUMENT '" + s + "' '" + dst + "'"); + out.writeLine("MOVE DB_INSTRUMENT '" + conv(s) + "' '" + conv(dst) + "'"); } if(getPrintOnlyMode()) return; @@ -4545,7 +4894,7 @@ public synchronized void copyDbInstrument(String instr, String dst) throws IOException, LSException, LscpException { verifyConnection(); - out.writeLine("COPY DB_INSTRUMENT '" + instr + "' '" + dst + "'"); + out.writeLine("COPY DB_INSTRUMENT '" + conv(instr) + "' '" + conv(dst) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4563,7 +4912,7 @@ copyDbInstruments(String[] instrs, String dst) throws IOException, LSException, LscpException { verifyConnection(); for(String s : instrs) { - out.writeLine("COPY DB_INSTRUMENT '" + s + "' '" + dst + "'"); + out.writeLine("COPY DB_INSTRUMENT '" + conv(s) + "' '" + conv(dst) + "'"); } if(getPrintOnlyMode()) return; @@ -4583,8 +4932,28 @@ throws IOException, LSException, LscpException { verifyConnection(); - desc = toEscapedString(desc); - out.writeLine("SET DB_INSTRUMENT DESCRIPTION '" + instr + "' '" + desc + "'"); + desc = toEscapedText(desc); + out.writeLine("SET DB_INSTRUMENT DESCRIPTION '" + conv(instr) + "' '" + desc + "'"); + if(getPrintOnlyMode()) return; + + ResultSet rs = getEmptyResultSet(); + } + + /** + * Substitutes all occurrences of the instrument file + * oldPath in the database, with newPath. + * @param oldPath The absolute path name of the instrument file to substitute. + * @param newPath The new absolute path name. + * @throws IOException If some I/O error occurs. + * @throws LSException If the operation failed. + * @throws LscpException If LSCP protocol corruption occurs. + */ + public synchronized void + setDbInstrumentFilePath(String oldPath, String newPath) + throws IOException, LSException, LscpException { + + verifyConnection(); + out.writeLine("SET DB_INSTRUMENT FILE_PATH '" + conv(oldPath) + "' '" + conv(newPath) + "'"); if(getPrintOnlyMode()) return; ResultSet rs = getEmptyResultSet(); @@ -4628,10 +4997,10 @@ StringBuffer sb = new StringBuffer(); sb.append("FIND DB_INSTRUMENT_DIRECTORIES"); if(nonRecursive) sb.append(" NON_RECURSIVE"); - sb.append(" '").append(dir).append("'"); + sb.append(" '").append(conv(dir)).append("'"); if(query.name != null && query.name.length() > 0) { - sb.append(" NAME='").append(toEscapedString(query.name)).append("'"); + sb.append(" NAME='").append(toEscapedText(query.name)).append("'"); } String s = query.getCreatedAfter(); @@ -4656,7 +5025,7 @@ if(query.description != null && query.description.length() > 0) { sb.append(" DESCRIPTION='"); - sb.append(toEscapedString(query.description)).append("'"); + sb.append(toEscapedText(query.description)).append("'"); } out.writeLine(sb.toString()); @@ -4709,10 +5078,10 @@ StringBuffer sb = new StringBuffer(); sb.append("FIND DB_INSTRUMENTS"); if(nonRecursive) sb.append(" NON_RECURSIVE"); - sb.append(" '").append(dir).append("'"); + sb.append(" '").append(conv(dir)).append("'"); if(query.name != null && query.name.length() > 0) { - sb.append(" NAME='").append(toEscapedString(query.name)).append("'"); + sb.append(" NAME='").append(toEscapedText(query.name)).append("'"); } if(query.formatFamilies.size() > 0) { @@ -4753,7 +5122,7 @@ if(query.description != null && query.description.length() > 0) { sb.append(" DESCRIPTION='"); - sb.append(toEscapedString(query.description)).append("'"); + sb.append(toEscapedText(query.description)).append("'"); } if(query.instrumentType != DbSearchQuery.InstrumentType.BOTH) { @@ -4766,16 +5135,16 @@ } if(query.product != null && query.product.length() > 0) { - sb.append(" PRODUCT='").append(toEscapedString(query.product)).append("'"); + sb.append(" PRODUCT='").append(toEscapedText(query.product)).append("'"); } if(query.artists != null && query.artists.length() > 0) { - sb.append(" ARTISTS='").append(toEscapedString(query.artists)).append("'"); + sb.append(" ARTISTS='").append(toEscapedText(query.artists)).append("'"); } if(query.keywords != null && query.keywords.length() > 0) { sb.append(" KEYWORDS='"); - sb.append(toEscapedString(query.keywords)).append("'"); + sb.append(toEscapedText(query.keywords)).append("'"); } out.writeLine(sb.toString()); @@ -4791,6 +5160,23 @@ } /** + * Returns a list of all instrument files in the database + * that that don't exist in the filesystem. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If other error occurs. + */ + public synchronized String[] + findLostDbInstrumentFiles() throws IOException, LscpException, LSException { + + verifyConnection(); + out.writeLine("FIND LOST DB_INSTRUMENT_FILES"); + if(getPrintOnlyMode()) return null; + + return parseEscapedStringList(getSingleLineResultSet().getResult()); + } + + /** * Gets status information about the specified job. * @param jobId The ID of the job. * @return A ScanJobInfo instance providing information @@ -4828,39 +5214,36 @@ } /** - * Resets the specified sampler channel. + * Resets the whole sampler. * - * @param samplerChn The sampler channel number. - * * @throws IOException If some I/O error occurs. * @throws LscpException If LSCP protocol corruption occurs. - * @throws LSException If samplerChn is not a valid channel number or if - * there is no engine assigned yet to the specified sampler channel. - * @see #getSamplerChannels */ public synchronized void - resetChannel(int samplerChn) throws IOException, LscpException, LSException { + resetSampler() throws IOException, LscpException { verifyConnection(); - out.writeLine("RESET CHANNEL " + samplerChn); + out.writeLine("RESET"); if(getPrintOnlyMode()) return; - ResultSet rs = getEmptyResultSet(); + try { ResultSet rs = getEmptyResultSet(); } + catch(LSException x) { getLogger().warning(x.getMessage()); } } /** - * Resets the whole sampler. - * + * Gets the current number of all active streams. + * @return The current number of all active streams. * @throws IOException If some I/O error occurs. * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If some other error occurs. */ - public synchronized void - resetSampler() throws IOException, LscpException { + public synchronized int + getTotalStreamCount() throws IOException, LscpException, LSException { verifyConnection(); - out.writeLine("RESET"); - if(getPrintOnlyMode()) return; + out.writeLine("GET TOTAL_STREAM_COUNT"); + if(getPrintOnlyMode()) return -1; - try { ResultSet rs = getEmptyResultSet(); } - catch(LSException x) { getLogger().warning(x.getMessage()); } + String s = getSingleLineResultSet().getResult(); + return parseInt(s); } /** @@ -4918,8 +5301,8 @@ } /** - * Gets the golobal volume of the sampler. - * @return The golobal volume of the sampler. + * Gets the global volume of the sampler. + * @return The global volume of the sampler. * @throws IOException If some I/O error occurs. * @throws LscpException If LSCP protocol corruption occurs. * @throws LSException If some other error occurs. @@ -4952,6 +5335,155 @@ ResultSet rs = getEmptyResultSet(); } + /** + * Gets the global sampler-wide limit of maximum voices. + * @return The global sampler-wide limit of maximum voices. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If some other error occurs. + */ + public synchronized int + getGlobalVoiceLimit() throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("GET VOICES"); + if(getPrintOnlyMode()) return -1; + + String s = getSingleLineResultSet().getResult(); + return parseInt(s); + } + + /** + * Sets the global sampler-wide limit of maximum voices. + * @param maxVoices The new global limit of maximum voices. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If some other error occurs. + * @see #getVolume + */ + public synchronized void + setGlobalVoiceLimit(int maxVoices) throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("SET VOICES " + maxVoices); + if(getPrintOnlyMode()) return; + + ResultSet rs = getEmptyResultSet(); + } + + /** + * Gets the global sampler-wide limit of maximum disk streams. + * @return The global sampler-wide limit of maximum disk streams. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If some other error occurs. + */ + public synchronized int + getGlobalStreamLimit() throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("GET STREAMS"); + if(getPrintOnlyMode()) return -1; + + String s = getSingleLineResultSet().getResult(); + return parseInt(s); + } + + /** + * Sets the global sampler-wide limit for maximum disk streams. + * @param maxVoices The new global limit of maximum disk streams. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If some other error occurs. + * @see #getVolume + */ + public synchronized void + setGlobalStreamLimit(int maxStreams) throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("SET STREAMS " + maxStreams); + if(getPrintOnlyMode()) return; + + ResultSet rs = getEmptyResultSet(); + } + + /** + * Gets the number of instruments in the specified instrument file. + * @param filename The absolute path name of the instrument file. + * @return The number of instruments in the specified instrument file. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If the file is not found, or other error occur. + */ + public synchronized int + getFileInstrumentCount(String filename) throws IOException, LscpException, LSException { + verifyConnection(); + out.writeLine("GET FILE INSTRUMENTS '" + conv(filename) +"'"); + if(getPrintOnlyMode()) return -1; + + String s = getSingleLineResultSet().getResult(); + return parseInt(s); + } + + /** + * Gets information about the instrument with index + * instrIdx in the specified instrument file. + * @param filename The absolute path name of the instrument file. + * @param instrIdx The index of the instrument in the specified instrument file. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If failed to retrieve information. + */ + public synchronized Instrument + getFileInstrumentInfo(String filename, int instrIdx) + throws IOException, LscpException, LSException { + + verifyConnection(); + out.writeLine("GET FILE INSTRUMENT INFO '" + conv(filename) + "' " + String.valueOf(instrIdx)); + if(getPrintOnlyMode()) return null; + + ResultSet rs = getMultiLineResultSet(); + Instrument instr = new FileInstrument(rs.getMultiLineResult()) { }; + + return instr; + } + + /** + * Gets the list of instruments in the specified instrument file. + * @param filename The absolute path name of the instrument file. + * @return An Instrument array providing + * information about all instruments in the specified instrument file. + * @throws IOException If some I/O error occurs. + * @throws LscpException If LSCP protocol corruption occurs. + * @throws LSException If the specified file name is invalid. + */ + public synchronized Instrument[] + getFileInstruments(String filename) throws IOException, LscpException, LSException { + int l = getFileInstrumentCount(filename); + if(l < 0) return null; + Instrument[] instrS = new FileInstrument[l]; + + for(int i = 0; i < instrS.length; i++) { + instrS[i] = getFileInstrumentInfo(filename, i); + } + return instrS; + } + + private static class FileInstrument extends AbstractInstrument { + FileInstrument(String[] resultSet) throws LscpException { + super(resultSet); + } + + public String + getEngine() { + // TODO: engine lookup? + return getFormatFamily(); + } + + @Override + public boolean + parse(String s) throws LscpException { + if(s.startsWith("PRODUCT: ") || s.startsWith("ARTISTS: ")) return true; + return super.parse(s); + } + } + private void getEmptyResultSets(int count, String err) throws LSException { StringBuffer sb = new StringBuffer();