package jp.kshoji.blemidi.device;
import android.support.annotation.NonNull;
/**
* Represents BLE MIDI Output Device
*
* @author K.Shoji
*/
public abstract class MidiOutputDevice {
public static final int MAX_TIMESTAMP = 8192;
/**
* Transfer data
*
* @param writeBuffer byte array to write
*/
protected abstract void transferData(@NonNull byte[] writeBuffer);
/**
* Obtains the device name
*
* @return device name
*/
@NonNull
public abstract String getDeviceName();
/**
* Obtains the device address
*
* @return device address
*/
@NonNull
public abstract String getDeviceAddress();
@NonNull
@Override
public final String toString() {
return getDeviceName();
}
/**
* Sends MIDI message to output device.
*
* @param byte1 the first byte
*/
private void sendMidiMessage(int byte1) {
long timestamp = System.currentTimeMillis() % MAX_TIMESTAMP;
byte[] writeBuffer = new byte[] { (byte) (0x80 | ((timestamp >> 7) & 0x3f)), (byte) (0x80 | (timestamp & 0x7f)), (byte) byte1 };
transferData(writeBuffer);
}
/**
* Sends MIDI message to output device.
*
* @param byte1 the first byte
* @param byte2 the second byte
*/
private void sendMidiMessage(int byte1, int byte2) {
byte[] writeBuffer = new byte[4];
long timestamp = System.currentTimeMillis() % MAX_TIMESTAMP;
writeBuffer[0] = (byte) (0x80 | ((timestamp >> 7) & 0x3f));
writeBuffer[1] = (byte) (0x80 | (timestamp & 0x7f));
writeBuffer[2] = (byte) byte1;
writeBuffer[3] = (byte) byte2;
transferData(writeBuffer);
}
/**
* Sends MIDI message to output device.
*
* @param byte1 the first byte
* @param byte2 the second byte
* @param byte3 the third byte
*/
private void sendMidiMessage(int byte1, int byte2, int byte3) {
byte[] writeBuffer = new byte[5];
long timestamp = System.currentTimeMillis() % MAX_TIMESTAMP;
writeBuffer[0] = (byte) (0x80 | ((timestamp >> 7) & 0x3f));
writeBuffer[1] = (byte) (0x80 | (timestamp & 0x7f));
writeBuffer[2] = (byte) byte1;
writeBuffer[3] = (byte) byte2;
writeBuffer[4] = (byte) byte3;
transferData(writeBuffer);
}
/**
* SysEx
*
* @param systemExclusive : start with 'F0', and end with 'F7'
*/
public final void sendMidiSystemExclusive(@NonNull byte[] systemExclusive) {
byte[] timestampAddedSystemExclusive = new byte[systemExclusive.length + 2];
System.arraycopy(systemExclusive, 0, timestampAddedSystemExclusive, 1, systemExclusive.length);
long timestamp = System.currentTimeMillis() % MAX_TIMESTAMP;
// extend a byte for timestamp LSB, before the last byte('F7')
timestampAddedSystemExclusive[systemExclusive.length + 1] = systemExclusive[systemExclusive.length - 1];
// set first byte to timestamp LSB
timestampAddedSystemExclusive[0] = (byte) (0x80 | (timestamp & 0x7f));
// split into 20 bytes. BLE can't send more than 20 bytes by default MTU.
byte[] writeBuffer = new byte[20];
for (int i = 0; i < timestampAddedSystemExclusive.length; i += 19) {
// Don't send 0xF7 timestamp LSB inside of SysEx(MIDI parser will fail) 0x7f -> 0x7e
timestampAddedSystemExclusive[systemExclusive.length] = (byte) (0x80 | (timestamp & 0x7e));
if (i + 19 <= timestampAddedSystemExclusive.length) {
System.arraycopy(timestampAddedSystemExclusive, i, writeBuffer, 1, 19);
} else {
// last message
writeBuffer = new byte[timestampAddedSystemExclusive.length - i + 1];
System.arraycopy(timestampAddedSystemExclusive, i, writeBuffer, 1, timestampAddedSystemExclusive.length - i);
}
// timestamp MSB
writeBuffer[0] = (byte) (0x80 | ((timestamp >> 7) & 0x3f));
transferData(writeBuffer);
timestamp = System.currentTimeMillis() % MAX_TIMESTAMP;
}
}
/**
* Note-off
*
* @param channel 0-15
* @param note 0-127
* @param velocity 0-127
*/
public final void sendMidiNoteOff(int channel, int note, int velocity) {
sendMidiMessage(0x80 | (channel & 0xf), note, velocity);
}
/**
* Note-on
*
* @param channel 0-15
* @param note 0-127
* @param velocity 0-127
*/
public final void sendMidiNoteOn(int channel, int note, int velocity) {
sendMidiMessage(0x90 | (channel & 0xf), note, velocity);
}
/**
* Poly-KeyPress
*
* @param channel 0-15
* @param note 0-127
* @param pressure 0-127
*/
public final void sendMidiPolyphonicAftertouch(int channel, int note, int pressure) {
sendMidiMessage(0xa0 | (channel & 0xf), note, pressure);
}
/**
* Control Change
*
* @param channel 0-15
* @param function 0-127
* @param value 0-127
*/
public final void sendMidiControlChange(int channel, int function, int value) {
sendMidiMessage(0xb0 | (channel & 0xf), function, value);
}
/**
* Program Change
*
* @param channel 0-15
* @param program 0-127
*/
public final void sendMidiProgramChange(int channel, int program) {
sendMidiMessage(0xc0 | (channel & 0xf), program);
}
/**
* Channel Pressure
*
* @param channel 0-15
* @param pressure 0-127
*/
public final void sendMidiChannelAftertouch(int channel, int pressure) {
sendMidiMessage(0xd0 | (channel & 0xf), pressure);
}
/**
* PitchBend Change
*
* @param channel 0-15
* @param amount 0(low)-8192(center)-16383(high)
*/
public final void sendMidiPitchWheel(int channel, int amount) {
sendMidiMessage(0xe0 | (channel & 0xf), amount & 0x7f, (amount >> 7) & 0x7f);
}
/**
* MIDI Time Code(MTC) Quarter Frame
*
* @param timing 0-127
*/
public final void sendMidiTimeCodeQuarterFrame(int timing) {
sendMidiMessage(0xf1, timing & 0x7f);
}
/**
* Song Select
*
* @param song 0-127
*/
public final void sendMidiSongSelect(int song) {
sendMidiMessage(0xf3, song & 0x7f);
}
/**
* Song Position Pointer
*
* @param position 0-16383
*/
public final void sendMidiSongPositionPointer(int position) {
sendMidiMessage(0xf2, position & 0x7f, (position >> 7) & 0x7f);
}
/**
* Tune Request
*/
public final void sendMidiTuneRequest() {
sendMidiMessage(0xf6);
}
/**
* Timing Clock
*/
public final void sendMidiTimingClock() {
sendMidiMessage(0xf8);
}
/**
* Start Playing
*/
public final void sendMidiStart() {
sendMidiMessage(0xfa);
}
/**
* Continue Playing
*/
public final void sendMidiContinue() {
sendMidiMessage(0xfb);
}
/**
* Stop Playing
*/
public final void sendMidiStop() {
sendMidiMessage(0xfc);
}
/**
* Active Sensing
*/
public final void sendMidiActiveSensing() {
sendMidiMessage(0xfe);
}
/**
* Reset Device
*/
public final void sendMidiReset() {
sendMidiMessage(0xff);
}
/**
* RPN message
*
* @param channel 0-15
* @param function 14bits
* @param value 7bits or 14bits
*/
public final void sendRPNMessage(int channel, int function, int value) {
sendRPNMessage(channel, (function >> 7) & 0x7f, function & 0x7f, value);
}
/**
* RPN message
*
* @param channel 0-15
* @param functionMSB higher 7bits
* @param functionLSB lower 7bits
* @param value 7bits or 14bits
*/
public final void sendRPNMessage(int channel, int functionMSB, int functionLSB, int value) {
// send the function
sendMidiControlChange(channel, 101, functionMSB & 0x7f);
sendMidiControlChange(channel, 100, functionLSB & 0x7f);
// send the value
if ((value >> 7) > 0) {
sendMidiControlChange(channel, 6, (value >> 7) & 0x7f);
sendMidiControlChange(channel, 38, value & 0x7f);
} else {
sendMidiControlChange(channel, 6, value & 0x7f);
}
// send the NULL function
sendMidiControlChange(channel, 101, 0x7f);
sendMidiControlChange(channel, 100, 0x7f);
}
/**
* NRPN message
*
* @param channel 0-15
* @param function 14bits
* @param value 7bits or 14bits
*/
public final void sendNRPNMessage(int channel, int function, int value) {
sendNRPNMessage(channel, (function >> 7) & 0x7f, function & 0x7f, value);
}
/**
* NRPN message
*
* @param channel 0-15
* @param functionMSB higher 7bits
* @param functionLSB lower 7bits
* @param value 7bits or 14bits
*/
public final void sendNRPNMessage(int channel, int functionMSB, int functionLSB, int value) {
// send the function
sendMidiControlChange(channel, 99, functionMSB & 0x7f);
sendMidiControlChange(channel, 98, functionLSB & 0x7f);
// send the value
if ((value >> 7) > 0) {
sendMidiControlChange(channel, 6, (value >> 7) & 0x7f);
sendMidiControlChange(channel, 38, value & 0x7f);
} else {
sendMidiControlChange(channel, 6, value & 0x7f);
}
// send the NULL function
sendMidiControlChange(channel, 101, 0x7f);
sendMidiControlChange(channel, 100, 0x7f);
}
}