BLE-MIDI: fix timestamps for sysex data
Blocks of data in the middle of a SysEx used to get a zero timestamp. Now they get a proper timestamp. Thanks to Teenage Engineering for suggesting this fix. I also cleaned up the code to make it easier to understand. Bug: 140638458 Test: Send long, >50 byte, SysEx over BLE to an Android Test: device running the MIDI scope. Check timestamps Test: for the data blocks. See bug for more details. Change-Id: I1a553d54cc76025f854e53229d233a033ab7c297
This commit is contained in:
@@ -22,24 +22,41 @@ import android.util.Log;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an abstract base class that decodes a packet buffer and passes it to a
|
* This is an abstract base class that decodes a BLE-MIDI packet
|
||||||
* {@link android.media.midi.MidiReceiver}
|
* buffer and passes it to a {@link android.media.midi.MidiReceiver}
|
||||||
*/
|
*/
|
||||||
public class BluetoothPacketDecoder extends PacketDecoder {
|
public class BluetoothPacketDecoder extends PacketDecoder {
|
||||||
|
|
||||||
private static final String TAG = "BluetoothPacketDecoder";
|
private static final String TAG = "BluetoothPacketDecoder";
|
||||||
|
|
||||||
private final byte[] mBuffer;
|
private final byte[] mBuffer;
|
||||||
|
private int mBytesInBuffer;
|
||||||
private MidiBtleTimeTracker mTimeTracker;
|
private MidiBtleTimeTracker mTimeTracker;
|
||||||
|
|
||||||
private final int TIMESTAMP_MASK_HIGH = 0x1F80;
|
private int mLowTimestamp;
|
||||||
private final int TIMESTAMP_MASK_LOW = 0x7F;
|
private long mNanoTimestamp;
|
||||||
private final int HEADER_TIMESTAMP_MASK = 0x3F;
|
|
||||||
|
private static final int TIMESTAMP_MASK_HIGH = 0x1F80; // top 7 bits
|
||||||
|
private static final int TIMESTAMP_MASK_LOW = 0x7F; // bottom 7 bits
|
||||||
|
private static final int HEADER_TIMESTAMP_MASK = 0x3F; // bottom 6 bits
|
||||||
|
|
||||||
public BluetoothPacketDecoder(int maxPacketSize) {
|
public BluetoothPacketDecoder(int maxPacketSize) {
|
||||||
mBuffer = new byte[maxPacketSize];
|
mBuffer = new byte[maxPacketSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void flushOutput(MidiReceiver receiver) {
|
||||||
|
if (mBytesInBuffer > 0) {
|
||||||
|
try {
|
||||||
|
receiver.send(mBuffer, 0, mBytesInBuffer, mNanoTimestamp);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ???
|
||||||
|
}
|
||||||
|
mBytesInBuffer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this code allows running status across packets,
|
||||||
|
// although the specification does not allow that.
|
||||||
@Override
|
@Override
|
||||||
public void decodePacket(byte[] buffer, MidiReceiver receiver) {
|
public void decodePacket(byte[] buffer, MidiReceiver receiver) {
|
||||||
if (mTimeTracker == null) {
|
if (mTimeTracker == null) {
|
||||||
@@ -47,14 +64,11 @@ public class BluetoothPacketDecoder extends PacketDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int length = buffer.length;
|
int length = buffer.length;
|
||||||
|
|
||||||
// NOTE his code allows running status across packets,
|
|
||||||
// although the specification does not allow that.
|
|
||||||
|
|
||||||
if (length < 1) {
|
if (length < 1) {
|
||||||
Log.e(TAG, "empty packet");
|
Log.e(TAG, "empty packet");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte header = buffer[0];
|
byte header = buffer[0];
|
||||||
if ((header & 0xC0) != 0x80) {
|
if ((header & 0xC0) != 0x80) {
|
||||||
Log.e(TAG, "packet does not start with header");
|
Log.e(TAG, "packet does not start with header");
|
||||||
@@ -64,52 +78,46 @@ public class BluetoothPacketDecoder extends PacketDecoder {
|
|||||||
// shift bits 0 - 5 to bits 7 - 12
|
// shift bits 0 - 5 to bits 7 - 12
|
||||||
int highTimestamp = (header & HEADER_TIMESTAMP_MASK) << 7;
|
int highTimestamp = (header & HEADER_TIMESTAMP_MASK) << 7;
|
||||||
boolean lastWasTimestamp = false;
|
boolean lastWasTimestamp = false;
|
||||||
int dataCount = 0;
|
|
||||||
int previousLowTimestamp = 0;
|
int previousLowTimestamp = 0;
|
||||||
long nanoTimestamp = 0;
|
int currentTimestamp = highTimestamp | mLowTimestamp;
|
||||||
int currentTimestamp = 0;
|
|
||||||
|
|
||||||
// iterate through the rest of the packet, separating MIDI data from timestamps
|
// Iterate through the rest of the packet, separating MIDI data from timestamps.
|
||||||
for (int i = 1; i < buffer.length; i++) {
|
for (int i = 1; i < buffer.length; i++) {
|
||||||
byte b = buffer[i];
|
byte b = buffer[i];
|
||||||
|
|
||||||
|
// Is this a timestamp byte?
|
||||||
if ((b & 0x80) != 0 && !lastWasTimestamp) {
|
if ((b & 0x80) != 0 && !lastWasTimestamp) {
|
||||||
lastWasTimestamp = true;
|
lastWasTimestamp = true;
|
||||||
int lowTimestamp = b & TIMESTAMP_MASK_LOW;
|
mLowTimestamp = b & TIMESTAMP_MASK_LOW;
|
||||||
if (lowTimestamp < previousLowTimestamp) {
|
|
||||||
|
// If the low timestamp byte wraps within the packet then
|
||||||
|
// increment the high timestamp byte.
|
||||||
|
if (mLowTimestamp < previousLowTimestamp) {
|
||||||
highTimestamp = (highTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH;
|
highTimestamp = (highTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH;
|
||||||
}
|
}
|
||||||
previousLowTimestamp = lowTimestamp;
|
previousLowTimestamp = mLowTimestamp;
|
||||||
|
|
||||||
int newTimestamp = highTimestamp | lowTimestamp;
|
// If the timestamp advances then send any pending data.
|
||||||
|
int newTimestamp = highTimestamp | mLowTimestamp;
|
||||||
if (newTimestamp != currentTimestamp) {
|
if (newTimestamp != currentTimestamp) {
|
||||||
if (dataCount > 0) {
|
// Send previous message separately since it has a different timestamp.
|
||||||
// send previous message separately since it has a different timestamp
|
flushOutput(receiver);
|
||||||
try {
|
|
||||||
receiver.send(mBuffer, 0, dataCount, nanoTimestamp);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ???
|
|
||||||
}
|
|
||||||
dataCount = 0;
|
|
||||||
}
|
|
||||||
currentTimestamp = newTimestamp;
|
currentTimestamp = newTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate nanoTimestamp
|
// Calculate MIDI nanosecond timestamp from BLE timestamp.
|
||||||
long now = System.nanoTime();
|
long now = System.nanoTime();
|
||||||
nanoTimestamp = mTimeTracker.convertTimestampToNanotime(currentTimestamp, now);
|
mNanoTimestamp = mTimeTracker.convertTimestampToNanotime(currentTimestamp, now);
|
||||||
} else {
|
} else {
|
||||||
lastWasTimestamp = false;
|
lastWasTimestamp = false;
|
||||||
mBuffer[dataCount++] = b;
|
// Flush if full before adding more data.
|
||||||
|
if (mBytesInBuffer == mBuffer.length) {
|
||||||
|
flushOutput(receiver);
|
||||||
|
}
|
||||||
|
mBuffer[mBytesInBuffer++] = b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataCount > 0) {
|
flushOutput(receiver);
|
||||||
try {
|
|
||||||
receiver.send(mBuffer, 0, dataCount, nanoTimestamp);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ???
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user