I'm writing an Android application. A MIDI piano keyboard is connected physically by a cable to an Android device. I have been following the official Android Midi documentation here https://developer.android.com/reference/android/media/midi/package-summary, but I am stuck with decoding the raw Midi data which I am receiving.
@RequiresApi(api = Build.VERSION_CODES.M)
class MidiFramer extends MidiReceiver {
public void onSend(byte[] data, int offset,
int count, long timestamp) throws IOException {
// parse MIDI or whatever
// How to convert data to something readable? Below doesn't make any sense.
Log.v(LOG_TAG, "onSend strData:" + data +" length:"+data.length);
StringBuffer sb = new StringBuffer();
for (int i=0; i<data.length; i++){
String hex = new String (data, StandardCharsets.UTF_8);
sb.append(hex);
}
Log.v(LOG_TAG, "onSend sb:" + sb.toString());
}
}
Essentially from the raw Midi data which is being received, I want to know what note is being played (e.g. D4 / C#5) on the physical piano keyboard. Any help would be appreciated.
This is my refactored solution using inspiration from multiple sources already tested with various devices and connections:
@WorkerThread
private fun onSend(data: ByteArray, startOffset: Int, count: Int) {
var offset = startOffset
fun param(index: Int) = data[offset + index].unsignedInt
while (offset < count) {
val command = data[offset] and STATUS_COMMAND_MASK
val channel: Int = data[offset] and STATUS_CHANNEL_MASK
when (command) {
STATUS_NOTE_ON -> {
val velocity = param(2)
if (velocity == 0) noteOff(channel, note = param(1))
else noteOn(channel, note = param(1), velocity = param(2))
offset += 3
}
STATUS_NOTE_OFF -> {
noteOff(channel, note = param(1), velocity = param(2))
offset += 3
}
STATUS_CONTROL_CHANGE -> {
cc(channel, command = param(1), value = param(2))
offset += 3
}
STATUS_PITCH_BEND -> {
pitchBend(channel, bend = (param(2) shl 7) + data[offset + 1])
offset += 3
}
STATUS_PROGRAM_CHANGE -> {
programChange(channel, program = param(1))
offset += 2
}
STATUS_CHANNEL_PRESSURE -> {
channelPressure(channel, program = param(1))
offset += 2
}
STATUS_START -> {
start()
offset += 1
}
STATUS_CONTINUE -> {
midiContinue()
offset += 1
}
STATUS_STOP -> {
stop()
offset += 1
}
STATUS_CLOCK -> {
timing()
offset += 1
}
else -> {
logWarn {
"Unhandled MIDI data.size:${data.size} offset:$offset," +
" count:$count, command:$command}"
}
offset += 1
}
}
}
}
That picth band part I am not sure about, but everywhere it is done differently, so will be seen if I ever get bug report on that, or will able to test it out.
Posting some additional code so you can get it working easyly..
object MidiConstants {
internal const val TAG = "MidiTools"
const val STATUS_COMMAND_MASK = 0xF0
const val STATUS_CHANNEL_MASK = 0x0F
// Channel voice messages.
const val STATUS_NOTE_OFF = 0x80
const val STATUS_NOTE_ON = 0x90
const val STATUS_POLYPHONIC_AFTERTOUCH = 0xA0
const val STATUS_CONTROL_CHANGE = 0xB0
const val STATUS_PROGRAM_CHANGE = 0xC0
const val STATUS_CHANNEL_PRESSURE = 0xD0
const val STATUS_PITCH_BEND = 0xE0
// System Common Messages.
const val STATUS_SYSEX_START = 0xF0
const val STATUS_MIDI_TIME_CODE = 0xF1
const val STATUS_SONG_POSITION = 0xF2
const val STATUS_SONG_SELECT = 0xF3
const val STATUS_TUNE_REQUEST = 0xF6
const val STATUS_SYSEX_END = 0xF7
const val STATUS_RESET = 0xFF //followed by 0xFF and 0x00
// System Real-Time Messages, single byte
const val STATUS_CLOCK = 0xF8 //248
const val STATUS_START = 0xFA //250
const val STATUS_CONTINUE = 0xFB //251
const val STATUS_STOP = 0xFC //252
const val STATUS_ACTIVE_SENSING = 0xFE //254
}
infix fun Byte.and(that: Int): Int = this.toInt().and(that)
Hope I didnt forget anythin...