diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c3944d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/.classpath + +/.project + +bin/ +.gitgnore \ No newline at end of file diff --git a/src/rwdoclet/src/com/ruinwesen/doclet/RWDoclet.java b/src/rwdoclet/src/com/ruinwesen/doclet/RWDoclet.java index f853d8f..c3fcd5c 100644 --- a/src/rwdoclet/src/com/ruinwesen/doclet/RWDoclet.java +++ b/src/rwdoclet/src/com/ruinwesen/doclet/RWDoclet.java @@ -3,12 +3,17 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; -import java.util.regex.*; - -import com.sun.javadoc.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.ConstructorDoc; +import com.sun.javadoc.ExecutableMemberDoc; +import com.sun.javadoc.MethodDoc; +import com.sun.javadoc.Parameter; +import com.sun.javadoc.RootDoc; +import com.sun.javadoc.Tag; public class RWDoclet { static String outputDir = "/Users/manuel/javadoc-output/"; diff --git a/src/rwmidi/MidiEvent.java b/src/rwmidi/MidiEvent.java index ee2af5c..3adc633 100644 --- a/src/rwmidi/MidiEvent.java +++ b/src/rwmidi/MidiEvent.java @@ -1,105 +1,109 @@ -package rwmidi; - -import javax.sound.midi.InvalidMidiDataException; -import javax.sound.midi.MidiMessage; -import javax.sound.midi.ShortMessage; - -/** - * Simple wrapper around MIDI messages, used to abstract from the actual bytes and provide a - * more symbolic representation of the MIDI data. This class is used as a superclass for - * messages received on a {@Link MidiInput} object. You don't usually have to create such objects yourself. - * - */ -public class MidiEvent extends ShortMessage{ - public static final int SYSEX_START = 0xF0; - public static final int SYSEX_END = 0xF7; - public static final int NOTE_OFF = 0x80; - public static final int NOTE_ON = 0x90; - public static final int CONTROL_CHANGE = 0xB0; - public static final int PROGRAM_CHANGE = 0xC0; - private int midiChannel = 0; - - MidiInput input = null; - - protected MidiEvent(byte[] data){ - super(data); - } - - MidiEvent(final MidiMessage _midiMessage){ - this(_midiMessage.getMessage()); - } - - MidiEvent(int command, int number, int value){ - this(new byte[] { (byte) NOTE_ON, 0, 0 }); - try{ - setMessage(command | midiChannel, number, value); - }catch (InvalidMidiDataException e){ - e.printStackTrace(); - } - } - - /** - * - * @return the input on which this message was received. - */ - public MidiInput getInput() { - return input; - } - - void setInput(MidiInput _input) { - input = _input; - } - - /** - * - * @return the first data byte of this message - */ - public int getData1(){ - if (length > 1){ - return (data[1] & 0xFF); - } - return 0; - } - - void setData1(final int _data1){ - data[1] = (byte) (_data1 & 0xFF); - } - - /** - * - * @return the second data byte of this message - */ - public int getData2(){ - if (length > 2){ - return (data[2] & 0xFF); - } - return 0; - } - - void setData2(final int _data2){ - data[1] = (byte) (_data2 & 0xFF); - } - - protected static MidiEvent create(MidiMessage msg) { - if (msg instanceof javax.sound.midi.SysexMessage) - return new SysexMessage((javax.sound.midi.SysexMessage)msg); - else if (msg instanceof ShortMessage) { - ShortMessage smsg = (ShortMessage)msg; - final int midiCommand = smsg.getCommand(); - final int midiChannel = smsg.getChannel(); - final int midiData1 = smsg.getData1(); - final int midiData2 = smsg.getData2(); - - if (midiCommand == MidiEvent.NOTE_ON && midiData2 > 0) { - return new Note(midiCommand, midiChannel, midiData1, midiData2); - } else if (midiCommand == MidiEvent.NOTE_OFF || ((midiCommand == NOTE_ON) && (midiData2 == 0))) { - return new Note(midiCommand, midiChannel, midiData1, midiData2); - } else if (midiCommand == MidiEvent.CONTROL_CHANGE) { - return new Controller(midiChannel, midiData1, midiData2); - } else if (midiCommand == MidiEvent.PROGRAM_CHANGE) { - return new ProgramChange(midiData1); - } - } - return null; - } -} +package rwmidi; + +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MidiMessage; +import javax.sound.midi.ShortMessage; + +/** + * Simple wrapper around MIDI messages, used to abstract from the actual bytes and provide a + * more symbolic representation of the MIDI data. This class is used as a superclass for + * messages received on a {@Link MidiInput} object. You don't usually have to create such objects yourself. + * + */ +public class MidiEvent extends ShortMessage{ + public static final int SYSEX_START = 0xF0; + public static final int SYSEX_END = 0xF7; + public static final int NOTE_OFF = 0x80; + public static final int NOTE_ON = 0x90; + public static final int CONTROL_CHANGE = 0xB0; + public static final int PITCH_BEND = 0xE0; + + private int midiChannel = 0; + + MidiInput input = null; + + protected MidiEvent(byte[] data){ + super(data); + } + + MidiEvent(final MidiMessage _midiMessage){ + this(_midiMessage.getMessage()); + } + + MidiEvent(int command, int number, int value){ + this(new byte[] { (byte) NOTE_ON, 0, 0 }); + try{ + setMessage(command | midiChannel, number, value); + }catch (InvalidMidiDataException e){ + e.printStackTrace(); + } + } + + /** + * + * @return the input on which this message was received. + */ + public MidiInput getInput() { + return input; + } + + void setInput(MidiInput _input) { + input = _input; + } + + /** + * + * @return the first data byte of this message + */ + public int getData1(){ + if (length > 1){ + return (data[1] & 0xFF); + } + return 0; + } + + void setData1(final int _data1){ + data[1] = (byte) (_data1 & 0xFF); + } + + /** + * + * @return the second data byte of this message + */ + public int getData2(){ + if (length > 2){ + return (data[2] & 0xFF); + } + return 0; + } + + void setData2(final int _data2){ + data[1] = (byte) (_data2 & 0xFF); + } + + protected static MidiEvent create(MidiMessage msg) { + if (msg instanceof javax.sound.midi.SysexMessage) + return new SysexMessage((javax.sound.midi.SysexMessage)msg); + else if (msg instanceof ShortMessage) { + ShortMessage smsg = (ShortMessage)msg; + final int midiCommand = smsg.getCommand(); + final int sysMessage = smsg.getStatus(); + final int midiChannel = smsg.getChannel(); + final int midiData1 = smsg.getData1(); + final int midiData2 = smsg.getData2(); + + if (midiCommand == MidiEvent.NOTE_ON && midiData2 > 0) { + return new Note(midiCommand, midiChannel, midiData1, midiData2); + } else if (midiCommand == MidiEvent.NOTE_OFF || ((midiCommand == NOTE_ON) && (midiData2 == 0))) { + return new Note(midiCommand, midiChannel, midiData1, midiData2); + } else if (midiCommand == MidiEvent.CONTROL_CHANGE) { + return new Controller(midiChannel, midiData1, midiData2); + } else if (midiCommand == MidiEvent.PROGRAM_CHANGE) { + return new ProgramChange(midiData1); + } else if (sysMessage >= MidiEvent.SONG_POSITION_POINTER) { //if we get this far, only syncs are left + return new SyncEvent(msg); + } + } + return null; + } +} diff --git a/src/rwmidi/MidiInput.java b/src/rwmidi/MidiInput.java index fe8d8f4..472b5f2 100644 --- a/src/rwmidi/MidiInput.java +++ b/src/rwmidi/MidiInput.java @@ -1,14 +1,13 @@ package rwmidi; import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.MidiMessage; -import javax.sound.midi.MidiSystem; import javax.sound.midi.MidiUnavailableException; import javax.sound.midi.Receiver; -import javax.sound.midi.ShortMessage; -import javax.sound.midi.SysexMessage; import javax.sound.midi.Transmitter; /** @@ -18,9 +17,13 @@ * to close the corresponding MidiDevice (however, this will close all MidiInputs connected to this device). */ public class MidiInput implements Receiver { - + javax.sound.midi.MidiDevice jDevice; - ArrayList plugList; + CopyOnWriteArrayList plugList; + int divisions = 0; + long pulseOne = 0; + long pulseTwo = 0; + long pulseTime; /** * Create a MidiInput from a javax.sound.midi.MidiDevice . Don't use this unless you know what you are doing. @@ -29,18 +32,18 @@ public class MidiInput implements Receiver { */ public MidiInput(javax.sound.midi.MidiDevice dev2) throws MidiUnavailableException { this.jDevice = dev2; - dev2.open(); + if (!dev2.isOpen()) + dev2.open(); Transmitter trsmt = dev2.getTransmitter(); trsmt.setReceiver(this); - plugList = new ArrayList(); + plugList = new CopyOnWriteArrayList(); currentMessage = new ArrayList(); - System.out.println("Foo"); } - + protected MidiInput(MidiInputDevice _device) throws MidiUnavailableException { this(_device.getDevice()); } - + public String getName() { javax.sound.midi.MidiDevice.Info info = jDevice.getDeviceInfo(); return info.getName() + " " + info.getVendor(); @@ -61,7 +64,7 @@ public void close() { } ArrayList currentMessage; - + public static void printHex(byte[] b) { printHex(b, 0, b.length); } @@ -90,53 +93,85 @@ public static void printHex(byte[] b, int start, int length) { } System.out.println(); } - - public void send(final MidiMessage message, final long timeStamp) { - if ((message.getLength() > 1)) { - System.out.println("message " + message); - printHex(message.getMessage()); + private void addSysexBytes(byte bytes[]) { + for (byte b : bytes) { + if (b == (byte)0xF7) { + currentMessage.add((byte)0xF7); + byte msg[] = new byte[currentMessage.size()]; + for (int i = 0; i < currentMessage.size(); i++) { + msg[i] = ((Byte)currentMessage.get(i)).byteValue(); + } + javax.sound.midi.SysexMessage newMsg = new javax.sound.midi.SysexMessage(); + try { + newMsg.setMessage(msg, msg.length); + for (Plug plug : plugList) + plug.callPlug(this, newMsg); + } catch (InvalidMidiDataException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } else { + currentMessage.add(b); + } } + } + + public void send(final MidiMessage message, final long timeStamp) { + // if ((message.getLength() >= 1)) { + // System.out.println("message " + message); + // printHex(message.getMessage()); + // } if (message instanceof javax.sound.midi.SysexMessage || message.getStatus() == (byte)0xF7) { if (message.getStatus() == 0xF0) { -// System.out.println("clear message and start new "); + // System.out.println("clear message and start new "); currentMessage.clear(); // no shortcut for sysex messages currentMessage.add((byte)0xF0); } - for (byte b : ((javax.sound.midi.SysexMessage)message).getData()) { - if (b == (byte)0xF7) { - currentMessage.add((byte)0xF7); - byte msg[] = new byte[currentMessage.size()]; - for (int i = 0; i < currentMessage.size(); i++) { - msg[i] = ((Byte)currentMessage.get(i)).byteValue(); - } - javax.sound.midi.SysexMessage newMsg = new javax.sound.midi.SysexMessage(); - try { - newMsg.setMessage(msg, msg.length); - for (Plug plug : plugList) - plug.callPlug(this, newMsg); - } catch (InvalidMidiDataException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } else { - currentMessage.add(b); - } - } + addSysexBytes(((javax.sound.midi.SysexMessage)message).getData()); + /// } else { - if (message.getStatus() >= 0xF8) { + //commenting out to enable midi sync messages + // if (message.getStatus() >= 0xF8) { + // return; + // } else { + if (message.getStatus() == 0xF7) { + addSysexBytes(message.getMessage()); return; - } else { -// System.out.println("clear current message " + Integer.toHexString((byte)message.getStatus())); - currentMessage.clear(); // discard maybe sysex message } -// System.out.println("received message "); -// printHex(message.getMessage()); + // } for (Plug plug : plugList) plug.callPlug(this, message); + if (message.getStatus() == SyncEvent.TIMING_CLOCK && divisions != 0){ + calculatePulseSpace(); + for (int i = 1; i < divisions; i++){ + if (pulseTime > 5){ + try { + Thread.sleep(pulseTime); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + for (Plug plug : plugList) + plug.callPlug(this, message); + } + } + if (message.getStatus() == SyncEvent.STOP && divisions != 0){ + pulseOne = 0; + pulseTwo = 0; + pulseTime = 0; + } } } + private void calculatePulseSpace(){ + pulseOne = System.nanoTime(); + if(pulseTwo != 0){ + pulseTime = TimeUnit.MILLISECONDS.convert((pulseOne - pulseTwo) / divisions, TimeUnit.NANOSECONDS); + } + pulseTwo = pulseOne; + } /** * Register a callback method on a specific channel for a specific MIDI command. The value field is the MIDI status byte, * for example 0x90 for NOTE ON. @@ -146,15 +181,15 @@ public void send(final MidiMessage message, final long timeStamp) { * @param value MIDI status byte, -1 for all messages */ public void plug(final Object _object, - final String _methodName, - final int channel, - final int value) { + final String _methodName, + final int channel, + final int value) { if (Plug.objectHasMethod(_object, _methodName)) { Plug plug = new Plug(_object, _methodName, channel, value); plugList.add(plug); } } - + /** * Register a callback method for all MIDI messages received on this input. * @param object Callback object @@ -165,7 +200,7 @@ public void plug( final String methodName) { plug(object, methodName, -1, -1); } - + /** * Register a callback method for all MIDI messages received on a specific channel on this input. * @param object Callback object @@ -188,7 +223,7 @@ public void plug( public void plug(Object obj) { plug(obj, -1); } - + /** * Register an object with standard midi callbacks on a specific channels. The callbacks are noteOnReceived(Note), * noteOffReceived(Note), controllerChangeReceived(Controller), programChangeReceived(ProgramChange) and diff --git a/src/rwmidi/MidiInputDevice.java b/src/rwmidi/MidiInputDevice.java index f8e0c4a..6c2e83f 100644 --- a/src/rwmidi/MidiInputDevice.java +++ b/src/rwmidi/MidiInputDevice.java @@ -48,5 +48,11 @@ public MidiInput createInput(Object obj, int channel) { MidiInput input = createInput(); input.plug(obj, channel); return input; - } + } + + public MidiInput createInput(int convertTo) { + MidiInput input = createInput(); + input.divisions = convertTo/24; + return input; + } } diff --git a/src/rwmidi/MidiOutput.java b/src/rwmidi/MidiOutput.java index 1c7a204..d9b893a 100644 --- a/src/rwmidi/MidiOutput.java +++ b/src/rwmidi/MidiOutput.java @@ -13,13 +13,20 @@ public class MidiOutput { Receiver receiver; javax.sound.midi.MidiDevice device; + static final int MAXPITCHBEND = 16383; + static final int MINPITCHBEND = 0; MidiOutput(javax.sound.midi.MidiDevice device) throws MidiUnavailableException { this.device = device; - device.open(); + if (!device.isOpen()) + device.open(); receiver = device.getReceiver(); } + + MidiOutput(Receiver _receiver) { + receiver = _receiver; + } MidiOutput(MidiOutputDevice _device) throws MidiUnavailableException { this(_device.getDevice()); @@ -29,6 +36,7 @@ public String getName() { javax.sound.midi.MidiDevice.Info info = device.getDeviceInfo(); return info.getName() + " " + info.getVendor(); } + /** * Send a NOTE ON message on this output. @@ -90,6 +98,30 @@ public int sendController(int channel, int cc, int value) { } } + /** + * Send a Pitch Bend change message on this output. + * @param channel Channel on which to send the message + * @param value Pitch Bend value + * @return 1 on success, 0 on error + */ + public int sendPitchBend(int channel, int value) { + if (value > MAXPITCHBEND) value = MAXPITCHBEND; + if (value < MINPITCHBEND) value = MINPITCHBEND; + byte lsb = (byte) (value % 128); + byte msb = (byte) (value/128); + + ShortMessage msg = new ShortMessage(); + try { + msg.setMessage(MidiEvent.PITCH_BEND, channel, lsb, msb); + receiver.send(msg, -1); + return 1; + } catch (InvalidMidiDataException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return 0; + } + } + /** * Send a Program Change on this output * @param channel Channel on which to send the message diff --git a/src/rwmidi/SyncEvent.java b/src/rwmidi/SyncEvent.java new file mode 100644 index 0000000..aa17ee5 --- /dev/null +++ b/src/rwmidi/SyncEvent.java @@ -0,0 +1,11 @@ +package rwmidi; + +import javax.sound.midi.MidiMessage; + +public class SyncEvent extends MidiEvent { + + public SyncEvent(MidiMessage message) { + super(message); + // TODO Auto-generated constructor stub + } +}