MIDI core handling improvements

A bunch of improvements to core MIDI handling. New functionality to
decode directly from a byte array (so you don't have to go through
InputStream), as well as more efficient and more concise code for
MessageOutputProcessor, plus some bug fixes.

Note that the tests haven't been run, maybe that would be a good idea.
master
Raph Levien 11 years ago
parent 16e99a3860
commit b22117bb52
  1. 13
      android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java
  2. 140
      core/src/com/levien/synthesizer/core/midi/MessageForwarder.java
  3. 116
      core/src/com/levien/synthesizer/core/midi/MessageFromBytes.java
  4. 10
      core/src/com/levien/synthesizer/core/midi/MessageInputProcessor.java
  5. 180
      core/src/com/levien/synthesizer/core/midi/MessageOutputProcessor.java
  6. 16
      core/src/com/levien/synthesizer/core/midi/MidiUtil.java

@ -27,19 +27,18 @@ import android.hardware.usb.UsbInterface;
import android.os.Build;
import android.util.Log;
import com.levien.synthesizer.android.AndroidGlue;
import com.levien.synthesizer.android.ui.PianoActivity2;
import com.levien.synthesizer.core.midi.MessageFromBytes;
import com.levien.synthesizer.core.midi.MidiListener;
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class UsbMidiDevice {
// probably want to change this to MessageOutputProcessor
private final AndroidGlue mReceiver;
private final MidiListener mReceiver;
private final UsbDeviceConnection mDeviceConnection;
private final UsbEndpoint mEndpoint;
private final WaiterThread mWaiterThread = new WaiterThread();
public UsbMidiDevice(AndroidGlue receiver, UsbDeviceConnection connection, UsbInterface intf) {
public UsbMidiDevice(MidiListener receiver, UsbDeviceConnection connection, UsbInterface intf) {
mReceiver = receiver;
mDeviceConnection = connection;
@ -115,10 +114,8 @@ public class UsbMidiDevice {
payloadBytes = 2;
}
if (payloadBytes > 0) {
byte[] newBuf = new byte[payloadBytes];
System.arraycopy(buf, i + 1, newBuf, 0, payloadBytes);
//Log.d("synth", "sending midi");
mReceiver.sendMidi(newBuf);
MessageFromBytes.send(mReceiver, buf, 1, payloadBytes);
}
}
}

@ -0,0 +1,140 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.levien.synthesizer.core.midi;
/**
* Class for forwarding messages to another MidiListener. Great for subclassing.
*/
public class MessageForwarder implements MidiListener {
public MessageForwarder(MidiListener target) {
target_ = target;
}
// Control events.
public void onNoteOff(int channel, int note, int velocity) {
target_.onNoteOff(channel, note, velocity);
}
public void onNoteOn(int channel, int note, int velocity) {
target_.onNoteOn(channel, note, velocity);
}
public void onNoteAftertouch(int channel, int note, int aftertouch) {
target_.onNoteAftertouch(channel, note, aftertouch);
}
public void onController(int channel, int control, int value) {
target_.onController(channel, control, value);
}
public void onProgramChange(int channel, int program) {
target_.onProgramChange(channel, program);
}
public void onChannelAftertouch(int channel, int aftertouch) {
target_.onChannelAftertouch(channel, aftertouch);
}
public void onPitchBend(int channel, int value) {
target_.onPitchBend(channel, value);
}
// Other events.
public void onTimingClock() {
target_.onTimingClock();
}
public void onActiveSensing() {
target_.onActiveSensing();
}
// Meta events.
public void onSequenceNumber(int sequenceNumber) {
target_.onSequenceNumber(sequenceNumber);
}
public void onText(byte[] text) {
target_.onText(text);
}
public void onCopyrightNotice(byte[] text) {
target_.onCopyrightNotice(text);
}
public void onSequenceName(byte[] text) {
target_.onSequenceName(text);
}
public void onInstrumentName(byte[] text) {
target_.onInstrumentName(text);
}
public void onLyrics(byte[] text) {
target_.onLyrics(text);
}
public void onMarker(byte[] text) {
target_.onMarker(text);
}
public void onCuePoint(byte[] text) {
target_.onCuePoint(text);
}
public void onChannelPrefix(int channel) {
target_.onChannelPrefix(channel);
}
public void onPort(byte[] data) {
target_.onPort(data);
}
public void onEndOfTrack() {
target_.onEndOfTrack();
}
public void onSetTempo(int microsecondsPerQuarterNote) {
target_.onSetTempo(microsecondsPerQuarterNote);
}
public void onSmpteOffset(byte[] data) {
target_.onSmpteOffset(data);
}
public void onTimeSignature(int numerator,
int denominator,
int metronomePulse,
int thirtySecondNotesPerQuarterNote) {
target_.onTimeSignature(numerator, denominator, metronomePulse,
thirtySecondNotesPerQuarterNote);
}
public void onKeySignature(int key, boolean isMinor) {
target_.onKeySignature(key, isMinor);
}
public void onSequencerSpecificEvent(byte[] data) {
target_.onSequencerSpecificEvent(data);
}
// SysEx events.
public void onSysEx(byte[] data) {
target_.onSysEx(data);
}
private MidiListener target_;
}

@ -0,0 +1,116 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.levien.synthesizer.core.midi;
/**
* MessageFromBytes sends MIDI messages from bytes to a MidiListener.
*/
public class MessageFromBytes {
public static final int ERROR = -1;
public static final int NEEDMOREBYTES = -2;
/**
* Send a midi message to the listener. Note: the message must begin
* with a valid status byte (running status is not supported).
*
* @param l the MidiListener to receive the message
* @param b the data
* @param off the start offset in the data
* @param len the number of bytes to consume
* @return Number of bytes consumed (nonnegative) on success, or
* ERROR if it's not valid MIDI, or NEEDMOREBYTES if it's truncated
*/
public static int send(MidiListener l, byte[] b, int off, int len) {
if (len == 0) {
return 0;
}
int code = b[off] & 0xff;
if ((code & 0xf0) == 0xf0) {
// TODO: NYI
return ERROR;
} else if ((code & 0x80) == 0x80) {
int channel = code & 0x0f;
int nbytes = ((code & 0xe0) == 0xc0) ? 2 : 3;
if (len < nbytes) {
return NEEDMOREBYTES;
}
if ((b[off + 1] & 0x80) != 0 || nbytes > 2 && (b[off + 2] & 0x80) != 0) {
return ERROR;
}
switch (code & 0xf0) {
case 0x80:
l.onNoteOff(channel, b[off + 1], b[off + 2]);
break;
case 0x90:
l.onNoteOn(channel, b[off + 1], b[off + 2]);
break;
case 0xa0:
l.onNoteAftertouch(channel, b[off + 1], b[off + 2]);
break;
case 0xb0:
l.onController(channel, b[off + 1], b[off + 2]);
break;
case 0xc0:
l.onProgramChange(channel, b[off + 1]);
break;
case 0xd0:
l.onChannelAftertouch(channel, b[off + 1]);
break;
case 0xe0:
l.onPitchBend(channel, b[off + 1] + (b[off + 2] << 7));
break;
}
return nbytes;
}
return ERROR;
}
public static int send(MidiListener l, byte[] b) {
return send(l, b, 0, b.length);
}
/**
* Send a sequence of midi messages to the listener. Note: each message must
* begin with a valid status byte (running status is not supported).
*
* @param l the MidiListener to receive the message
* @param b the data
* @param off the start offset in the data
* @param len the number of bytes to consume
* @return Number of bytes consumed (nonnegative) on success, or
* ERROR if it's not valid MIDI, or NEEDMOREBYTES if it's truncated
*/
public static int sendAll(MidiListener l, byte[] b, int off, int len) {
int i = 0;
while (i < len) {
int result = send(l, b, off + i, len - i);
if (result < 0) {
if (i == 0) {
return result;
} else {
break;
}
}
i += result;
}
return i;
}
public static int sendAll(MidiListener l, byte[] b) {
return sendAll(l, b, 0, b.length);
}
}

@ -1,12 +1,12 @@
/*
* Copyright 2011 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -122,7 +122,9 @@ public class MessageInputProcessor {
break;
}
case 0xE0: {
int value = MidiUtil.readWord(input);
int lsb = MidiUtil.readByte(input);
int msb = MidiUtil.readByte(input);
int value = (msb << 7) | lsb;
listener.onPitchBend(channel, value);
break;
}

@ -1,12 +1,12 @@
/*
* Copyright 2011 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -51,53 +51,31 @@ public abstract class MessageOutputProcessor implements MidiListener {
//
public void onNoteOff(int channel, int note, int velocity) {
buffer_.write(0x80 | channel);
buffer_.write(note);
buffer_.write(velocity);
notifyMessage();
notify3(0x80 | channel, note, velocity);
}
public void onNoteOn(int channel, int note, int velocity) {
buffer_.write(0x90 | channel);
buffer_.write(note);
buffer_.write(velocity);
notifyMessage();
notify3(0x90 | channel, note, velocity);
}
public void onNoteAftertouch(int channel, int note, int aftertouch) {
buffer_.write(0xA0 | channel);
buffer_.write(note);
buffer_.write(aftertouch);
notifyMessage();
notify3(0xA0 | channel, note, aftertouch);
}
public void onController(int channel, int control, int value) {
buffer_.write(0xB0 | channel);
buffer_.write(control);
buffer_.write(value);
notifyMessage();
notify3(0xB0 | channel, control, value);
}
public void onProgramChange(int channel, int program) {
buffer_.write(0xC0 | channel);
buffer_.write(program);
notifyMessage();
notify2(0xC0 | channel, program);
}
public void onChannelAftertouch(int channel, int aftertouch) {
buffer_.write(0xD0 | channel);
buffer_.write(aftertouch);
notifyMessage();
notify2(0xD0 | channel, aftertouch);
}
public void onPitchBend(int channel, int value) {
try {
buffer_.write(0xE0 | channel);
MidiUtil.writeWord(buffer_, value);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notify3(0xE0 | channel, value & 0x7F, (value >> 7) & 0x7F);
}
public void onTimingClock() {
@ -121,87 +99,31 @@ public abstract class MessageOutputProcessor implements MidiListener {
}
public void onText(byte[] text) {
try {
buffer_.write(0xFF);
buffer_.write(0x01);
MidiUtil.writeVarInt(buffer_, text.length);
buffer_.write(text);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notifyMetaBytes(0x01, text);
}
public void onCopyrightNotice(byte[] text) {
try {
buffer_.write(0xFF);
buffer_.write(0x02);
MidiUtil.writeVarInt(buffer_, text.length);
buffer_.write(text);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notifyMetaBytes(0x02, text);
}
public void onSequenceName(byte[] text) {
try {
buffer_.write(0xFF);
buffer_.write(0x03);
MidiUtil.writeVarInt(buffer_, text.length);
buffer_.write(text);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notifyMetaBytes(0x03, text);
}
public void onInstrumentName(byte[] text) {
try {
buffer_.write(0xFF);
buffer_.write(0x04);
MidiUtil.writeVarInt(buffer_, text.length);
buffer_.write(text);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notifyMetaBytes(0x04, text);
}
public void onLyrics(byte[] text) {
try {
buffer_.write(0xFF);
buffer_.write(0x05);
MidiUtil.writeVarInt(buffer_, text.length);
buffer_.write(text);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notifyMetaBytes(0x05, text);
}
public void onMarker(byte[] text) {
try {
buffer_.write(0xFF);
buffer_.write(0x06);
MidiUtil.writeVarInt(buffer_, text.length);
buffer_.write(text);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notifyMetaBytes(0x05, text);
}
public void onCuePoint(byte[] text) {
try {
buffer_.write(0xFF);
buffer_.write(0x07);
MidiUtil.writeVarInt(buffer_, text.length);
buffer_.write(text);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notifyMetaBytes(0x07, text);
}
public void onChannelPrefix(int channel) {
@ -217,26 +139,11 @@ public abstract class MessageOutputProcessor implements MidiListener {
}
public void onPort(byte[] data) {
try {
buffer_.write(0xFF);
buffer_.write(0x21);
MidiUtil.writeVarInt(buffer_, data.length);
buffer_.write(data);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notifyMetaBytes(0x21, data);
}
public void onEndOfTrack() {
try {
buffer_.write(0xFF);
buffer_.write(0x2F);
MidiUtil.writeVarInt(buffer_, 0);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notify3(0xFF, 0x2F, 0x00);
}
public void onSetTempo(int microsecondsPerQuarterNote) {
@ -256,15 +163,7 @@ public abstract class MessageOutputProcessor implements MidiListener {
if (data.length != 5) {
throw new RuntimeException("Invalid length for smpte offset event " + data.length + ".");
}
try {
buffer_.write(0xFF);
buffer_.write(0x54);
MidiUtil.writeVarInt(buffer_, 5);
buffer_.write(data);
notifyMessage();
} catch (IOException e) {
throw new RuntimeException(e);
}
notifyMetaBytes(0x54, data);
}
public void onTimeSignature(int numerator, int denominator, int metronomePulse,
@ -297,9 +196,15 @@ public abstract class MessageOutputProcessor implements MidiListener {
}
public void onSequencerSpecificEvent(byte[] data) {
notifyMetaBytes(0x7f, data);
}
/**
* TODO(klimt): This might be wrong. Double-check it. :)
*/
public void onSysEx(byte[] data) {
try {
buffer_.write(0xFF);
buffer_.write(0x7F);
buffer_.write(0xF0);
MidiUtil.writeVarInt(buffer_, data.length);
buffer_.write(data);
notifyMessage();
@ -308,12 +213,29 @@ public abstract class MessageOutputProcessor implements MidiListener {
}
}
/**
* TODO(klimt): This might be wrong. Double-check it. :)
*/
public void onSysEx(byte[] data) {
private void notify2(int b0, int b1) {
if (buf2_ == null) {
buf2_ = new byte[2];
}
buf2_[0] = (byte) b0;
buf2_[1] = (byte) b1;
onMessage(buf2_);
}
private void notify3(int b0, int b1, int b2) {
if (buf3_ == null) {
buf3_ = new byte[3];
}
buf3_[0] = (byte) b0;
buf3_[1] = (byte) b1;
buf3_[2] = (byte) b2;
onMessage(buf3_);
}
private void notifyMetaBytes(int type, byte[] data) {
try {
buffer_.write(0xF0);
buffer_.write(0xFF);
buffer_.write(type);
MidiUtil.writeVarInt(buffer_, data.length);
buffer_.write(data);
notifyMessage();
@ -324,4 +246,6 @@ public abstract class MessageOutputProcessor implements MidiListener {
// An internal byte buffer to hold intermediate output.
private ByteArrayOutputStream buffer_;
private byte[] buf2_;
private byte[] buf3_;
}

@ -1,12 +1,12 @@
/*
* Copyright 2011 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -82,18 +82,18 @@ public class MidiUtil {
// It's easier to just enumerate them than to make a loop.
//
if ((value & 0xF0000000) != 0) {
output.write(0x80 | ((value >> 28) & 0xFF));
output.write(0x80 | ((value >> 28) & 0x7F));
}
if ((value & 0xFFE00000) != 0) {
output.write(0x80 | ((value >> 21) & 0xFF));
output.write(0x80 | ((value >> 21) & 0x7F));
}
if ((value & 0xFFFFC000) != 0) {
output.write(0x80 | ((value >> 14) & 0xFF));
output.write(0x80 | ((value >> 14) & 0x7F));
}
if ((value & 0xFFFFFF80) != 0) {
output.write(0x80 | ((value >> 7) & 0xFF));
output.write(0x80 | ((value >> 7) & 0x7F));
}
output.write(value & 0xFF);
output.write(value & 0x7F);
}
/**

Loading…
Cancel
Save