/*
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.jnmprotocol;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import net.sf.nmedit.jnmprotocol.AckMessage;
import net.sf.nmedit.jnmprotocol.ErrorMessage;
import net.sf.nmedit.jnmprotocol.IAmMessage;
import net.sf.nmedit.jnmprotocol.KnobAssignmentMessage;
import net.sf.nmedit.jnmprotocol.LightMessage;
import net.sf.nmedit.jnmprotocol.MeterMessage;
import net.sf.nmedit.jnmprotocol.MidiException;
import net.sf.nmedit.jnmprotocol.MidiMessage;
import net.sf.nmedit.jnmprotocol.MorphRangeChangeMessage;
import net.sf.nmedit.jnmprotocol.NewPatchInSlotMessage;
import net.sf.nmedit.jnmprotocol.NmProtocolListener;
import net.sf.nmedit.jnmprotocol.NoteMessage;
import net.sf.nmedit.jnmprotocol.PDLData;
import net.sf.nmedit.jnmprotocol.ParameterMessage;
import net.sf.nmedit.jnmprotocol.ParameterSelectMessage;
import net.sf.nmedit.jnmprotocol.PatchListMessage;
import net.sf.nmedit.jnmprotocol.PatchMessage;
import net.sf.nmedit.jnmprotocol.SetPatchTitleMessage;
import net.sf.nmedit.jnmprotocol.SlotActivatedMessage;
import net.sf.nmedit.jnmprotocol.SlotsSelectedMessage;
import net.sf.nmedit.jnmprotocol.SynthSettingsMessage;
import net.sf.nmedit.jnmprotocol.VoiceCountMessage;
import net.sf.nmedit.jpdl.BitStream;
import net.sf.nmedit.jpdl.IntStream;
import net.sf.nmedit.jpdl.Packet;
public abstract class MidiMessage
{
public static int calculateChecksum(BitStream bitStream)
{
int checksum = 0;
bitStream.setPosition(0);
while (bitStream.isAvailable(24)) {
checksum += bitStream.getInt(8);
}
checksum = checksum % 128;
return checksum;
}
public static boolean checksumIsCorrect(BitStream bitStream)
{
int checksum = calculateChecksum(bitStream);
bitStream.setPosition(bitStream.getSize()-16);
int messageChecksum = bitStream.getInt(8);
bitStream.setPosition(0);
if (messageChecksum == checksum) {
return true;
}
System.out.println("Checksum mismatch " + messageChecksum +
" != " + checksum + ".");
return false;
}
public static MidiMessage create(BitStream bitStream)
throws MidiException
{
try
{
return createImpl(bitStream);
}
catch (MidiException e)
{
// set the midi message which caused the exception
e.setMidiMessage(bitStream.toByteArray());
// rethrow exception
throw e;
}
}
private static MidiMessage createImpl(BitStream bitStream)
throws MidiException
{
String error;
Packet packet = new Packet();
boolean success =
PDLData
.getMidiSysexParser()
.parse(bitStream, packet);
bitStream.setPosition(0);
if (success) {
if (packet.contains("IAm")) {
return new IAmMessage(packet);
}
if (checksumIsCorrect(bitStream)) {
if (packet.contains("Lights")) {
return new LightMessage(packet);
}
if (packet.contains("Meters")) {
return new MeterMessage(packet);
}
if (packet.contains("VoiceCount")) {
return new VoiceCountMessage(packet);
}
if (packet.contains("SlotsSelected")) {
return new SlotsSelectedMessage(packet);
}
if (packet.contains("SlotActivated")) {
return new SlotActivatedMessage(packet);
}
if (packet.contains("NewPatchInSlot")) {
return new NewPatchInSlotMessage(packet);
}
if (packet.contains("KnobChange")) {
return new ParameterMessage(packet);
}
if (packet.contains("MorphRangeChange")) {
return new MorphRangeChangeMessage(packet);
}
if (packet.contains("KnobAssignment")||packet.contains("KnobAssignmentChange"))
{
return new KnobAssignmentMessage(packet);
}
if (packet.contains("PatchListResponse")) {
return new PatchListMessage(packet);
}
if(packet.contains("ACK")) {
return new AckMessage(packet);
}
if (packet.contains("NoteEvent")) {
return new NoteMessage(packet);
}
if (packet.contains("SetPatchTitle")) {
return new SetPatchTitleMessage(packet);
}
if (packet.contains("ParameterSelect")) {
return new ParameterSelectMessage(packet);
}
if(packet.contains("Error")) {
return new ErrorMessage(packet);
}
if (packet.contains("PatchPacket")) {
{
// check for type = 0x03 = (data1 << 1) | ((data2 >>> 6) & 0x1)
Packet 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);
}
}
}
return new PatchMessage(packet);
}
error = "unsupported packet";
}
else {
error = "checksum error";
}
}
else {
error = "parse failed";
}
throw new MidiException(error, 0);
}
public List<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(Packet 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
{
BitStream bitStream = new BitStream();
boolean success = PDLData
.getMidiSysexParser()
.generate(intStream, bitStream);
if (!success || intStream.isAvailable(1)) {
throw new MidiException("Information mismatch in generate. In "+this,
intStream.getSize() - intStream.getPosition());
}
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 void appendChecksum(IntStream intStream)
throws MidiException
{
int checksum = 0;
intStream.append(checksum);
BitStream bitStream = getBitStream(intStream);
checksum = calculateChecksum(bitStream);
intStream.setSize(intStream.getSize()-1);
intStream.append(checksum);
intStream.setPosition(0);
}
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("[");
Iterator<Parameter> params = parameters.values().iterator();
if (params.hasNext())
{
sb.append(params.next());
}
while (params.hasNext())
{
sb.append(",");
sb.append(params.next());
}
sb.append("]");
return sb.toString();
}
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):"?");
}
}
}