/*
Nord Modular Midi Protocol 3.03 Library
Copyright (C) 2003 Marcus Andersson
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package net.sf.nmedit.jnmprotocol2;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import net.sf.nmedit.jpdl2.PDLException;
import net.sf.nmedit.jpdl2.PDLMessage;
import net.sf.nmedit.jpdl2.PDLPacket;
import net.sf.nmedit.jpdl2.PDLPacketParser;
import net.sf.nmedit.jpdl2.stream.BitStream;
import net.sf.nmedit.jpdl2.stream.IntStream;
public abstract class MidiMessage
{
private static Map<String, Constructor<? extends MidiMessage>> messageConstructors = new HashMap<String, Constructor<? extends MidiMessage>>();
static {
installMessage("iam", IAmMessage.class);
installMessage("lights", LightMessage.class);
installMessage("meters", MeterMessage.class);
installMessage("voicecount", VoiceCountMessage.class);
installMessage("slotsselected", SlotsSelectedMessage.class);
installMessage("slotactivated", SlotActivatedMessage.class);
installMessage("newPatchInSlot", NewPatchInSlotMessage.class);
installMessage("knobChange", ParameterMessage.class);
installMessage("morphRangeChange", MorphRangeChangeMessage.class);
installMessage("knobAssignment", KnobAssignmentMessage.class);
installMessage("PatchListResponse", PatchListMessage.class);
installMessage("ack", AckMessage.class);
installMessage("noteEvent", NoteMessage.class);
installMessage("SetPatchTitle", SetPatchTitleMessage.class);
installMessage("ParameterSelect", ParameterSelectMessage.class);
installMessage("error", ErrorMessage.class);
installMessage("PatchPacket", PatchMessage.class);
}
private static void installMessage(String key, Class<? extends MidiMessage> mclass)
{
Class<?>[] args = {PDLPacket.class};
Constructor<? extends MidiMessage> constructor;
try
{
constructor = mclass.getConstructor(args);
}
catch (Exception e)
{
throw new RuntimeException("constructor not found: "+mclass, e);
}
messageConstructors.put(key, constructor);
}
private static BitStream ensureIsValidSysex(BitStream stream)
{
stream.setPosition(0);
// find first 0xF0 and drop previous bytes
int StatusStartPosition = -1;
int BehindStatusEndPosition = -1;
while (stream.isAvailable(8))
{
int value = stream.getInt(8);
if ((value & 0x80) > 0) // check bit 8
{
if (value == 0xF7)
BehindStatusEndPosition = stream.getPosition();
else if (value == 0xF0)
StatusStartPosition = stream.getPosition()-8;
else
{
// crappy message, set invalid position values
StatusStartPosition = -1;
}
}
}
if (!(StatusStartPosition>=0 && StatusStartPosition<BehindStatusEndPosition))
return null; // corrupted message
if (StatusStartPosition>0 || BehindStatusEndPosition<stream.getSize())
{
// copy sysex 0xF0 ... 0xF7 from original message
stream.setPosition(StatusStartPosition);
BitStream sysex = new BitStream();
while (stream.getPosition()<BehindStatusEndPosition)
sysex.append(stream.getInt(8), 8);
stream = sysex;
}
stream.setPosition(0);
return stream;
}
public static MidiMessage create(BitStream bitStream)
throws MidiException
{
{
BitStream sysex = ensureIsValidSysex(bitStream);
if (sysex == null)
{
MidiException me = new MidiException("corrupted message", MidiException.INVALID_MIDI_DATA);
me.setMidiMessage(bitStream);
throw me;
}
bitStream = sysex;
}
PDLPacketParser parser = new PDLPacketParser(PDLData.getMidiDoc());
PDLMessage message;
try
{
message = parser.parseMessage(bitStream);
}
catch (PDLException e)
{
MidiException me = new MidiException("parse failed", MidiException.MIDI_PARSE_ERROR);
me.setMidiMessage(bitStream.toByteArray());
me.initCause(e);
throw me;
}
finally
{
bitStream.setPosition(0);
}
/*
if (!("meters".equals(message.getMessageId())||"lights".equals(message.getMessageId())))
{
System.out.println(message.getMessageId()+": "+PDLUtils.toString(message.getPacket()));
System.out.println(PDLUtils.toHexadecimal(bitStream.toByteArray()));
}*/
/*if ("unknownNMInfo".equals(message.getMessageId()))
{
System.out.println(PDLUtils.toString(message.getPacket()));
}*/
if ("PatchPacket".equals(message.getMessageId()))
{
PDLPacket packet = message.getPacket();
{
// check for type = 0x03 = (data1 << 1) | ((data2 >>> 6) & 0x1)
/*
PDLPacket pp = packet.getPacket("data:next");
int data1 = pp == null ? -1 : pp.getVariable("data");
if (data1 == 0x01)
{
pp = pp.getPacket("next");
int data2 = pp == null ? -1 : pp.getVariable("data");
if ((data2 & 0x40) > 0)
{
return new SynthSettingsMessage(packet);
}
}*/
PDLPacket pp = packet.getPacket("data");
if (pp != null)
{
int[] embedded_stream = pp.getVariableList("embedded_stream");
if (embedded_stream.length>=2 && embedded_stream[0] == 0x01 && (embedded_stream[1]&0x40)>0)
{
return new SynthSettingsMessage(packet);
}
}
}
return new PatchMessage(packet);
}
Constructor<? extends MidiMessage> mconstructor = messageConstructors.get(message.getMessageId());
if (mconstructor == null)
throw new MidiException("unsupported packet "+message, 0);
try
{
return mconstructor.newInstance(new Object[]{message.getPacket()});
} catch (Exception e)
{
MidiException me = new MidiException("could not create instance for messageId "+message.getMessageId(), MidiException.UNKNOWN_MIDI_MESSAGE);
me.initCause(e);
throw me;
}
}
public BitStream getBitStream()
throws MidiException
{
throw new MidiException(
getClass().getName()+
".getBitStream() not implemented.", 0);
}
public void notifyListener(NmProtocolListener listener)
{
throw new UnsupportedOperationException(
getClass().getName()+".notifyListener not implemented");
}
public boolean expectsReply()
{
return expectsreply;
}
public boolean isReply()
{
return isreply;
}
protected void addParameter(String name, String path)
{
if (isParameter(name))
throw new IllegalArgumentException("parameter already exists:"+name);
Parameter p = new Parameter(lastParameter, name, path);
if (firstParameter == null)
firstParameter = p;
lastParameter = p;
parameters.put(name, p);
}
protected boolean isParameter(String name)
{
return parameterForName(name) != null;
}
private void checkParameter(Parameter p, String parameter)
{
if (p == null)
throw new RuntimeException("Unsupported paramenter: " + parameter);
}
public void set(String parameter, int value)
{
Parameter p = parameterForName(parameter);
checkParameter(p, parameter);
p.setValue(value);
}
public Iterator<String> parameterNames()
{
// can't use parameters.keySet().iterator()
// because it does not respect the order
return new Iterator<String>()
{
Parameter pos = firstParameter;
public boolean hasNext()
{
return pos != null;
}
public String next()
{
if (!hasNext())
throw new NoSuchElementException();
Parameter res = pos;
pos = pos.next;
return res.name;
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
public int get(String parameter, int defaultValue)
{
Parameter p = parameterForName(parameter);
checkParameter(p, parameter);
return p.valueSet ? p.value : defaultValue;
}
private void checkValue(Parameter p)
{
if (!p.valueSet)
throw new RuntimeException("Missing parameter value: " + p.name);
}
public int get(String parameter)
{
Parameter p = parameterForName(parameter);
checkParameter(p, parameter);
checkValue(p);
return p.value;
}
public void setAll(PDLPacket packet)
{
for (Parameter p: parameters.values())
{
p.setValue(packet.getVariable(p.path));
}
}
public IntStream appendAll()
{
IntStream intStream = new IntStream(parameters.size()+10);
Parameter p = firstParameter;
while (p != null)
{
checkValue(p);
intStream.append(p.value);
p = p.next;
}
return intStream;
}
protected MidiMessage()
{
parameters = new HashMap<String, Parameter>();
expectsreply = false;
isreply = false;
addParameter("cc", "cc");
addParameter("slot", "slot");
set("slot", 0);
}
protected BitStream getBitStream(IntStream intStream)
throws MidiException
{
PDLPacketParser parser = new PDLPacketParser(PDLData.getMidiDoc());
BitStream bitStream = null;
try
{
parser.parse(intStream);
bitStream = parser.getBitStream();
}
catch (PDLException e)
{
MidiException me = new MidiException("Could not generate message: "+this+", "+(
intStream.getSize() - intStream.getPosition()), MidiException.MIDI_PARSE_ERROR);
me.initCause(e);
throw me;
}
if (intStream.isAvailable(1))
{
throw new MidiException("Information mismatch in generate. In "+this+", "+(
intStream.getSize() - intStream.getPosition()), MidiException.MIDI_PARSE_ERROR);
}
return bitStream;
}
public int getSlot()
{
return get("slot");
}
public void setSlot(int slot)
{
if (slot<0 || slot>=4)
throw new IllegalArgumentException("invalid slot: "+slot);
set("slot", slot);
}
/*
protected static List<BitStream> createBitstreamList()
{
return new LinkedList<BitStream>();
}
protected List<BitStream> createBitstreamList(BitStream bs)
{
List<BitStream> list = createBitstreamList();
list.add(bs);
return list;
}
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(getClass().getName());
sb.append("[");
toStringArgs(sb);
sb.append("]");
return sb.toString();
}
protected void toStringArgs(StringBuilder sb)
{
Iterator<Parameter> params = parameters.values().iterator();
if (params.hasNext())
{
sb.append(params.next());
}
while (params.hasNext())
{
sb.append(",");
sb.append(params.next());
}
}
private Parameter parameterForName(String name)
{
return parameters.get(name);
}
protected boolean isreply;
protected boolean expectsreply;
private Map<String, Parameter> parameters;
private Parameter firstParameter;
private Parameter lastParameter;
private static class Parameter
{
String name;
String path;
int value;
boolean valueSet = false;
Parameter next;
public Parameter(Parameter previous, String name, String path)
{
if (previous != null)
previous.next = this;
this.name = name;
this.path = path;
}
public void setValue(int value)
{
this.value = value;
this.valueSet = true;
}
public String toString()
{
return name+"="+(valueSet?Integer.toString(value):"?");
}
}
}