/* Copyright (C) 2006 Christian Schneider * * This file is part of Nomad. * * Nomad is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Nomad is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Nomad; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Created on Jan 2, 2007 */ package net.sf.nmedit.jsynth.clavia.nordmodular; import java.awt.EventQueue; import java.util.HashMap; import java.util.Map; import javax.sound.midi.MidiUnavailableException; import net.sf.nmedit.jnmprotocol2.ActivePidListener; import net.sf.nmedit.jnmprotocol2.ErrorMessage; import net.sf.nmedit.jnmprotocol2.IAmMessage; import net.sf.nmedit.jnmprotocol2.MessageMulticaster; import net.sf.nmedit.jnmprotocol2.MidiDriver; import net.sf.nmedit.jnmprotocol2.MidiException; import net.sf.nmedit.jnmprotocol2.MidiMessage; import net.sf.nmedit.jnmprotocol2.NmMessageAcceptor; import net.sf.nmedit.jnmprotocol2.NmProtocol; import net.sf.nmedit.jnmprotocol2.NmProtocolListener; import net.sf.nmedit.jnmprotocol2.RequestSynthSettingsMessage; import net.sf.nmedit.jnmprotocol2.SlotActivatedMessage; import net.sf.nmedit.jnmprotocol2.SynthSettingsMessage; import net.sf.nmedit.jnmprotocol2.utils.ProtocolRunner; import net.sf.nmedit.jnmprotocol2.utils.ProtocolThreadExecutionPolicy; import net.sf.nmedit.jnmprotocol2.utils.QueueBuffer; import net.sf.nmedit.jnmprotocol2.utils.StoppableThread; import net.sf.nmedit.jnmprotocol2.utils.ProtocolRunner.ProtocolErrorHandler; import net.sf.nmedit.jpatch.clavia.nordmodular.NM1ModuleDescriptions; import net.sf.nmedit.jsynth.AbstractSynthesizer; import net.sf.nmedit.jsynth.ComStatus; import net.sf.nmedit.jsynth.DefaultMidiPorts; import net.sf.nmedit.jsynth.MidiPortSupport; import net.sf.nmedit.jsynth.SlotManager; import net.sf.nmedit.jsynth.SynthException; import net.sf.nmedit.jsynth.Synthesizer; import net.sf.nmedit.jsynth.clavia.nordmodular.worker.NMStorePatchWorker; import net.sf.nmedit.jsynth.clavia.nordmodular.worker.ScheduledMessage; import net.sf.nmedit.jsynth.clavia.nordmodular.worker.Scheduler; import net.sf.nmedit.jsynth.midi.MidiPort; import net.sf.nmedit.jsynth.worker.StorePatchWorker; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class NordModular extends AbstractSynthesizer implements Synthesizer, DefaultMidiPorts { private final static Log log = LogFactory.getLog(NordModular.class); private double dspGlobal = 0; private NmProtocol protocol; private StoppableThread protocolThread; private boolean connected = false; private ComStatus comStatus = ComStatus.Offline; private MessageMulticaster multicaster; private MidiDriver midiDriver; private MidiPortSupport midiports; private boolean ignoreErrors = false; private Scheduler scheduler; private NmMessageHandler messageHander; private NM1ModuleDescriptions moduleDescriptions; private NmSlotManager slotManager; // private int maxSlotCount = 4; private int deviceId = -1; private int serial = -1; private final static String DEFAULT_DEVICE_NAME = "Nord Modular"; private final static String DEVICE_NAME_KEYBOARD = "Nord Modular Keyboard"; private final static String DEVICE_NAME_RACK = "Nord Modular Rack"; private final static String DEVICE_NAME_MICRO = "Micro Modular"; private String name = DEFAULT_DEVICE_NAME; private boolean settingsChangedFlag = false; private boolean settingsInSync = true; // false, true = external, internal private Property midiClockSource = new Property("midiClockSource", true); // false, true = active, inactive private Property ledsActive = new Property("ledsActive", true); // false, true = local on, local off private Property localOn = new Property("localOn", false); // false, true = active slot, selected slots private Property keyboardMode = new Property("keyboardMode", false); // false, true = normal, inverted private Property pedalPolarity = new Property("pedalPolarity", false); // print value = (value+1) private Property globalSync = new Property("globalSync", 0, 31, 0); // value range: -127..0..127 private Property masterTune = new Property("masterTune", -127, 127, 0, true); // false, true = immediate, hook private Property knobMode = new Property("knobMode", false); // other properties private Property programChangeReceive = new Property("programChangeReceive", true); private Property programChangeSend = new Property("programChangeSend", true); private Property midiVelScaleMin = new Property("midiVelScaleMin", 0, 127, 0); private Property midiVelScaleMax = new Property("midiVelScaleMax", 0, 127, 127); private Property midiClockBpm = new Property("midiClockBpm", 31, 239, 120); private Property[] slotMidiChannels = { new Property("midiChannelSlot0", 0, 16, 0), new Property("midiChannelSlot1", 0, 16, 1), new Property("midiChannelSlot2", 0, 16, 2), new Property("midiChannelSlot3", 0, 16, 3) }; private Property[] slotVoiceCount = { new Property("slot0VoiceCount", 0, 255, 0), new Property("slot1VoiceCount", 0, 255, 0), new Property("slot2VoiceCount", 0, 255, 0), new Property("slot3VoiceCount", 0, 255, 0) }; private Property activeSlot = new Property("activeSlot", 0, 3, 0); private NmBank[] banks; public int getMaxSlotCount() { if (!connected) return 0; if (deviceId == IAmMessage.MICRO_MODULAR) return 1; // 1 slot return 4; // default: 4 slots } public int getMaxBankCount() { if (!connected) return 0; if (deviceId == IAmMessage.MICRO_MODULAR) return 1; // 1 bank return 9; // default: 9 banks } public int getPId(int slot) { return multicaster.getActivePid(slot); } public NM1ModuleDescriptions getModuleDescriptions() { return moduleDescriptions; } public Scheduler getScheduler() { return scheduler; } public boolean isMicroModular() { return deviceId == IAmMessage.MICRO_MODULAR; } public int getDeviceId() { return deviceId; } public int getSerial() { return serial; } private class NMActivePidListener extends ActivePidListener { protected void pidChanged(int slotId, int pid) { // no op } } public NordModular(NM1ModuleDescriptions moduleDescriptions) { banks = new NmBank[0]; this.moduleDescriptions = moduleDescriptions; slotManager = new NmSlotManager(this); midiports = new MidiPortSupport(this, "pc-in", "pc-out"); multicaster = new MessageMulticaster(new NMActivePidListener()); multicaster.addProtocolListener(new NmProtocolListener(){ public void messageReceived(ErrorMessage m) { try { setConnected(false); } catch (SynthException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); protocol = new SchedulingProtocol(); protocol.setMessageHandler(multicaster); messageHander = new NmMessageHandler(this); addProtocolListener(messageHander); scheduler = new Scheduler(protocol); protocolThread = new StoppableThread(new ProtocolThreadExecutionPolicy(protocol), new ProtocolRunner(protocol, new Nm1ProtocolErrorHandler(this))); } private class SchedulingProtocol extends NmProtocol { protected void heartbeatImpl() throws MidiException { try { scheduler.schedule(); } catch (SynthException e) { if (e.getCause() != null && e.getCause() instanceof MidiException) throw (MidiException)e.getCause(); MidiException me = new MidiException(e.getMessage(), -1); me.initCause(e); throw me; } super.heartbeatImpl(); comUpdateStatus(); } protected void send(javax.sound.midi.MidiMessage message) { super.send(message); comTransmit(); } public void send(MidiMessage midiMessage) throws MidiException { super.send(midiMessage); comTransmit(); } protected void received(byte[] data) { super.received(data); comReceive(); } protected void dispatchEvents(QueueBuffer<MidiMessage> events) { super.dispatchEvents(events); comReceive(); } } public String getVendor() { return "Clavia"; } public String getName() { return name; } public String getDeviceName() { switch (deviceId) { case IAmMessage.MICRO_MODULAR: return DEVICE_NAME_MICRO; case IAmMessage.NORD_MODULAR_RACK: return DEVICE_NAME_RACK; case IAmMessage.NORD_MODULAR_KEYBOARD: return DEVICE_NAME_KEYBOARD; default: return DEFAULT_DEVICE_NAME; } } private MidiDriver createMidiDriver() throws SynthException { midiports.validatePlugs(); return new MidiDriver(midiports.getInPlug().getDeviceInfo(), midiports.getOutPlug().getDeviceInfo()); } private void connect() throws SynthException { if (log.isInfoEnabled()) { log.info("connect()"); } midiDriver = createMidiDriver(); try { midiDriver.connect(); } catch (MidiUnavailableException e) { if (log.isWarnEnabled()) { log.warn("mididriver.connect() failed", e); } throw new SynthException(e); } try { midiDriver.getTransmitter().setReceiver(protocol.getReceiver()); protocol.getTransmitter().setReceiver(midiDriver.getReceiver()); } catch (Throwable t) { if (log.isWarnEnabled()) { log.warn("setting receiver/transmitter failed", t); } throw new SynthException(t); } protocol.reset(); NmMessageAcceptor<IAmMessage> iamAcceptor = new NmMessageAcceptor<IAmMessage>(IAmMessage.class); NmMessageAcceptor<SynthSettingsMessage> settingsAcceptor = new NmMessageAcceptor<SynthSettingsMessage>(SynthSettingsMessage.class); try { multicaster.addProtocolListener(iamAcceptor); if (log.isInfoEnabled()) { log.info("sending "+IAmMessage.class.getName()+", expecting reply..."); } try { protocol.send(new IAmMessage()); } catch (Exception e) { if (log.isWarnEnabled()) { log.warn("sending "+IAmMessage.class.getName()+" failed", e); } throw new SynthException(e); } final long timeout = 10000; // 10 seconds iamAcceptor.waitForReply(protocol, timeout); IAmMessage iam = iamAcceptor.getFirstMessage(); if (log.isInfoEnabled()) { log.info("received "+IAmMessage.class.getName()+": "+iam); } validateVersion(iam, 3, 3); deviceId = iam.getDeviceId(); serial = iam.getSerial(); switch (deviceId) { case IAmMessage.NORD_MODULAR_RACK: break; case IAmMessage.NORD_MODULAR_KEYBOARD: break; case IAmMessage.MICRO_MODULAR: break; default: { log.warn("unknown deviceId: "+deviceId+" ("+iam+")"+", assume device is 'Nord Modular Keyboard'"); // assume keyboard deviceId = IAmMessage.NORD_MODULAR_KEYBOARD; break; } } setConnectedFlag(true); // request synth settings if (log.isInfoEnabled()) { log.info("requesting synth settings"); } multicaster.addProtocolListener(settingsAcceptor); try { protocol.send(new RequestSynthSettingsMessage()); } catch (Exception e) { if (log.isWarnEnabled()) { log.warn("sending "+RequestSynthSettingsMessage.class.getName()+" failed", e); } throw new SynthException("Request synth settings failed.", e); } settingsAcceptor.waitForReply(protocol, timeout); if (log.isInfoEnabled()) { log.info("synth settings received"); } setSettings(settingsAcceptor.getFirstMessage()); if (log.isInfoEnabled()) { log.info("adapted properties to received synth settings"); } } catch (SynthException e) { if (log.isWarnEnabled()) { log.warn("connect() failed.", e); } disconnect(); throw e; } catch (Exception e) { if (log.isWarnEnabled()) { log.warn("connect() failed.", e); } disconnect(); throw new SynthException(e); } finally { multicaster.removeProtocolListener(settingsAcceptor); multicaster.removeProtocolListener(iamAcceptor); } if (log.isInfoEnabled()) { log.info("starting protocol thread..."); } // now everything is fine - start the protocol thread protocolThread.start(); // request patches if (log.isInfoEnabled()) { log.info("requesting patches..."); } for (int i=0;i<slotManager.getSlotCount();i++) { NmSlot slot = slotManager.getSlot(i); if (slot.isEnabled()) slot.requestPatch(); } if (log.isInfoEnabled()) { log.info("connect() successfull."); } } public boolean getMidiClockSource() { return midiClockSource.getBooleanValue(); } public void setMidiClockSource(boolean internal) { midiClockSource.setValue(internal); } public int getMidiVelScaleMin() { return midiVelScaleMin.getValue(); } public void setMidiVelScaleMin(int value) { midiVelScaleMin.setValue(value); } public int getMidiVelScaleMax() { return midiVelScaleMax.getValue(); } public void setMidiVelScaleMax(int value) { midiVelScaleMax.setValue(value); } public boolean isLEDsActive() { return ledsActive.getBooleanValue(); } public void setLEDsActive(boolean value) { ledsActive.setValue(value); } public int getMidiClockBPM() { return midiClockBpm.getValue(); } public void setMidiClockBPM(int bpm) { midiClockBpm.setValue(bpm); } public boolean isLocalOn() { return localOn.getBooleanValue(); } public void setLocalOn(boolean on) { this.localOn.setValue(on); } public boolean getKeyboardMode() { return keyboardMode.getBooleanValue(); } public void setKeyboardMode(boolean selectedSlots) { keyboardMode.setValue(selectedSlots); } public boolean getPedalPolarity() { return pedalPolarity.getBooleanValue(); } public void setPedalPolarity(boolean inverted) { pedalPolarity.setValue(inverted); } public int getGlobalSync() { return globalSync.getValue(); } public void setGlobalSync(int value) { globalSync.setValue(value); } public int getMasterTune() { return masterTune.getValue(); } public void setMasterTune(int value) { masterTune.setValue(value); } public boolean getProgramChangeSend() { return programChangeSend.getBooleanValue(); } public void setProgramChangeSend(boolean enabled) { programChangeSend.setValue(enabled); } public boolean getProgramChangeReceive() { return programChangeReceive.getBooleanValue(); } public void setProgramChangeReceive(boolean enabled) { programChangeReceive.setValue(enabled); } public boolean getKnobMode() { return knobMode.getBooleanValue(); } public void setKnobMode(boolean hook) { knobMode.setValue(hook); } private boolean isValidSlot(int slot) { return slot>=0 && slot<slotManager.getSlotCount(); } private void checkSlot(int slot) { if (!isValidSlot(slot)) throw new IndexOutOfBoundsException("invalid slot index: "+slot); } public int getMidiChannel(int slot) { checkSlot(slot); return slotMidiChannels[slot].getValue(); } public void setMidiChannel(int slot, int channel) { checkSlot(slot); slotMidiChannels[slot].setValue(channel); } public boolean isSlotEnabled(int slot) { checkSlot(slot); return slotManager.getSlot(slot).isEnabled(); } public void setSlotEnabled(int slot, boolean enable) { checkSlot(slot); slotManager.getSlot(slot).setEnabled(enable); } public int getVoiceCount(int slot) { checkSlot(slot); return slotVoiceCount[slot].getValue(); } public void setVoiceCount(int slot, int voiceCount) { checkSlot(slot); slotVoiceCount[slot].setValue(voiceCount); } public int getActiveSlot() { return activeSlot.getValue(); } public void setActiveSlot(int selectSlot) { checkSlot(selectSlot); int oldValue = activeSlot.getValue(); activeSlot.setValue(selectSlot); getScheduler().offer(new ScheduledMessage(this,new SlotActivatedMessage(selectSlot))); slotManager.getSlot(oldValue).fireSelectedSlotChange(true, false); slotManager.getSlot(selectSlot).fireSelectedSlotChange(false, true); } public void setName(String name) { String oldName = this.name; if (name == null) { name = ""; } else if (name.length()>16) name = name.substring(0, 16); if (oldName == null || (!name.equals(oldName))) { this.name = name; settingsChangedFlag = true; firePropertyChange(PROPERTY_NAME, oldName, name); } } public void setSettings(SynthSettingsMessage message) { Map<String, Object> settings = message.getParamMap(); setName((String) settings.get("name")); midiClockSource.readValue(settings); midiVelScaleMin.readValue(settings); midiVelScaleMax.readValue(settings); ledsActive.readValue(settings); midiClockBpm.readValue(settings); localOn.readValue(settings); keyboardMode.readValue(settings); pedalPolarity.readValue(settings); globalSync.readValue(settings); masterTune.readValue(settings); programChangeSend.readValue(settings); programChangeReceive.readValue(settings); knobMode.readValue(settings); for (int i=0;i<4;i++) { slotMidiChannels[i].readValue(settings); } if (message.containsExtendedSettings()) { for (int i=0;i<slotManager.getSlotCount();i++) { boolean disabled = i>=slotManager.getSlotCount(); int value = 0; if (!disabled) { Object e = settings.get(slotEnabledPropertyName(i)); try { value = Math.max(0, Math.min(1, ((Integer)e).intValue())); } catch (ClassCastException cce) { // ignore } } slotManager.getSlot(i).setEnabledValue(value>0); slotVoiceCount[i].readValue(settings, disabled); } // TODO check if slot is available activeSlot.readValue(settings); } if (settingsChangedFlag) { settingsChangedFlag = false; firePropertyChange("settings", null, "settings"); } } private String slotEnabledPropertyName(int slotIndex) { return "slot"+slotIndex+"Selected"; } public Object getClientProperty(Object key) { if (!"icon".equals(key)) return super.getClientProperty(key); Object icon = super.getClientProperty("icon"); if (icon != null) return icon; switch (deviceId) { case IAmMessage.MICRO_MODULAR: icon = super.getClientProperty("icon.nm.micro"); break; case IAmMessage.NORD_MODULAR_RACK: icon = super.getClientProperty("icon.nm.rack"); break; } if (icon == null) icon = super.getClientProperty("icon.nm.keyboard"); return icon; } private void disconnect() { if (log.isInfoEnabled()) { log.info("disconnect()"); } this.serial = -1; this.deviceId = -1; protocolThread.stop(); midiDriver.disconnect(); protocol.reset(); setConnectedFlag(false); } public NmProtocol getProtocol() { return protocol; } private void setConnectedFlag(boolean connected) { if (this.connected != connected) { this.connected = connected; updateBanks(); fireSynthesizerStateChanged(); if (connected) setComStatus(ComStatus.Idle); else setComStatus(ComStatus.Offline); } } private void updateBanks() { if (!connected) { banks = new NmBank[0]; return; } int cnt = getMaxBankCount(); banks = new NmBank[cnt]; for (int i=0;i<cnt;i++) banks[i] = new NmBank(this, i); } private SynthSettingsMessage createSettingsMessage() throws MidiException { Map<String, Object> settings = new HashMap<String, Object>(); settings.put("name", getName()); midiClockSource.putValue(settings); midiVelScaleMin.putValue(settings); midiVelScaleMax.putValue(settings); ledsActive.putValue(settings); midiClockBpm.putValue(settings); localOn.putValue(settings); keyboardMode.putValue(settings); pedalPolarity.putValue(settings); globalSync.putValue(settings); masterTune.putValue(settings); programChangeSend.putValue(settings); programChangeReceive.putValue(settings); knobMode.putValue(settings); for (int i=0;i<4;i++) { slotMidiChannels[i].putValue(settings); } return new SynthSettingsMessage(settings); } public void sendSettings() { if (isConnected()) { try { protocol.send(createSettingsMessage()); } catch (Exception e) { throw new RuntimeException(e); } settingsInSync = true; } } public void syncSettings() { EventQueue.invokeLater(new Runnable(){ public void run() { syncSettingsImmediatelly(); } }); } public void syncSettingsImmediatelly() { if ((!settingsInSync) && isConnected()) { sendSettings(); } } protected void fireSynthesizerStateChanged() { if (isConnected()) connected(); else disconnected(); super.fireSynthesizerStateChanged(); } private void connected() { scheduler.clear(); NmSlot[] slots = new NmSlot[getMaxSlotCount()]; for (int i=0;i<slots.length;i++) slots[i] = new NmSlot(this, i); slotManager.setSlots(slots); } private void disconnected() { scheduler.clear(); for (NmSlot slot: slotManager) { // unregister patch slot.setPatch(null); } slotManager.setSlots(new NmSlot[0]); } public void setConnected( boolean connected ) throws SynthException { if (this.connected != connected) { if (!connected) { disconnect(); } else { connect(); } } } public boolean isConnected() { return connected; } private void validateVersion(IAmMessage msg, int versionLow, int versionHigh) throws SynthException { int msgVersionLow = msg.get("versionLow"); int msgVersionHigh = msg.get("versionHigh"); if (msgVersionLow != versionLow || msgVersionHigh != versionHigh) { throw new SynthException("Unsupported OS version: " +msgVersionHigh+"."+msgVersionLow +" (expected " +versionHigh+"."+versionLow +")"); } } private static class Nm1ProtocolErrorHandler extends ProtocolErrorHandler implements Runnable { private NordModular nm1; public Nm1ProtocolErrorHandler( NordModular nm1 ) { this.nm1 = nm1; } public void handleError(Throwable t) throws Throwable { if (t instanceof MidiException) { MidiException me = (MidiException) t; switch (me.getError()) { case MidiException.INVALID_MIDI_DATA: case MidiException.MIDI_PARSE_ERROR: case MidiException.UNKNOWN_MIDI_MESSAGE: { me.printStackTrace(); // ignore // TODO log error return; } case MidiException.TIMEOUT: { // go on: EventQueue.invokeLater(this); throw me; } } } if (nm1.isIgnoreErrorsEnabled()) { t.printStackTrace(); } else { EventQueue.invokeLater(this); throw t; } } public void run() { try { nm1.setConnected(false); } catch (SynthException e) { // no op } } } public void addProtocolListener( NmProtocolListener l ) { multicaster.addProtocolListener(l); } public void removeProtocolListener( NmProtocolListener l ) { multicaster.removeProtocolListener(l); } public void setIgnoreErrorsEnabled( boolean ignoreErrors ) { this.ignoreErrors = ignoreErrors; } public boolean isIgnoreErrorsEnabled() { return ignoreErrors; } public MidiPort[] getPorts() { return midiports.toArray(); } public MidiPort getPCInPort() { return midiports.getInPort(); } public MidiPort getPCOutPort() { return midiports.getOutPort(); } public NmBank[] getBanks() { NmBank[] copy = new NmBank[banks.length]; for (int i=0;i<banks.length;i++) copy[i] = banks[i]; return copy; } public MidiPort getPort( int index ) { return midiports.getPort(index); } public NmBank getBank( int index ) { return banks[index]; } public NmSlot getSlot( int index ) { return slotManager.getSlot(index); } public int getPortCount() { return midiports.getPortCount(); } public int getBankCount() { return banks.length; } public int getSlotCount() { return slotManager.getSlotCount(); } public SlotManager<NmSlot> getSlotManager() { return slotManager; } NmSlotManager getNmSlotManager() { return slotManager; } public MidiPort getDefaultMidiInPort() { return getPCInPort(); } public MidiPort getDefaultMidiOutPort() { return getPCOutPort(); } protected void fireSlotEnabledChange(int slotIndex, boolean oldEnabled, boolean newEnabled) { firePropertyChange(slotEnabledPropertyName(slotIndex), oldEnabled, newEnabled); } private class Property { private String propertyName; private int minValue; private int maxValue; private int value; private boolean isBooleanProperty = false; private boolean signedByte = false; public Property(String propertyName, boolean defaultValue) { this(propertyName, 0, 1, defaultValue ? 1 : 0); this.isBooleanProperty = true; } public Property(String propertyName, int minValue, int maxValue, int defaultValue, boolean signedByte) { this(propertyName, minValue, maxValue, defaultValue); this.signedByte = signedByte; } public Property(String propertyName, int minValue, int maxValue, int defaultValue) { this.propertyName = propertyName; this.minValue = minValue; this.maxValue = maxValue; this.value = defaultValue; } public void setValue(boolean value) { setValue(value ? 1 : 0); } public boolean getBooleanValue() { return value > 0; } public void setValue(int value) { value = Math.max(minValue, Math.min(value, maxValue)); int oldValue = this.value; if (oldValue != value) { this.value = value; NordModular.this.settingsChangedFlag = true; NordModular.this.settingsInSync = false; if (isBooleanProperty) { NordModular.this.firePropertyChange(propertyName, oldValue>0, value>0); } else { NordModular.this.firePropertyChange(propertyName, oldValue, value); } } } public int getValue() { return value; } protected void putValue(Map<String, Object> settings) { int internal = (signedByte) ? (((byte)value)&0xFF) : value; settings.put(propertyName, internal); } protected void readValue(Map<String, Object> settings) { readValue(settings, false); } protected void readValue(Map<String, Object> settings, boolean zero) { try { int newValue = 0; if (!zero) { newValue = ((Integer) settings.get(propertyName)).intValue(); if (signedByte) { // cast to signed byte, then back to int newValue = (byte) newValue; } } setValue( newValue ); } catch (NullPointerException e) { // ignore } catch (ClassCastException e) { // ignore } } } public StorePatchWorker createStorePatchWorker() { return new NMStorePatchWorker(this); } public double getDoubleProperty(String propertyName) { if (DSP_GLOBAL.equals(propertyName)) { return dspGlobal; } throw new IllegalArgumentException("no such property: "+propertyName); } public Object getProperty(String propertyName) { if (DSP_GLOBAL.equals(propertyName)) { return dspGlobal; } return null; } public boolean hasProperty(String propertyName) { return false;// DSP_GLOBAL.equals(propertyName); } public ComStatus getComStatus() { return comStatus; } protected void setComStatus(ComStatus status) { ComStatus newValue = connected ? status : ComStatus.Offline; ComStatus oldValue = this.comStatus; if (oldValue != newValue) { this.comStatus = newValue; fireComStatusChanged(newValue); } } long comLastReceiveDisableAt = 0; long comLastTransmitDisableAt = 0; static final long COM_THRESHOLD = 75; // milliseconds protected void comReceive() { comLastReceiveDisableAt = System.currentTimeMillis()+COM_THRESHOLD; comUpdateStatus(); } protected void comTransmit() { comLastTransmitDisableAt = System.currentTimeMillis()+COM_THRESHOLD; comUpdateStatus(); } public void comUpdateStatus() { if (EventQueue.isDispatchThread()) { __comUpdateStatusImpl(); } else { EventQueue.invokeLater(new Runnable(){ public void run() { __comUpdateStatusImpl(); }}); } } private void __comUpdateStatusImpl() { long t = System.currentTimeMillis(); boolean receive = comLastReceiveDisableAt>t; boolean transmit = comLastTransmitDisableAt>t; ComStatus newStatus; if (transmit) { if (receive) { newStatus = ComStatus.TransmitReceive; } else { newStatus = ComStatus.Transmit; } } else { if (receive) { newStatus = ComStatus.Receive; } else { newStatus = ComStatus.Idle; } } setComStatus(newStatus); } }