Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ff001809 authored by Mike Lockwood's avatar Mike Lockwood
Browse files

BluetoothMidiService: Use MidiBtleTimeTracker to interpret incoming Bluetooth MIDI timestamps

Also fixed some problems handling timestamp wrapping.

Change-Id: Ic0aefc54f2560425bea6d07ca0c4529d16699eaa
parent 93ea09a3
Loading
Loading
Loading
Loading
+18 −9
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ public class BluetoothPacketDecoder extends PacketDecoder {
    private static final String TAG = "BluetoothPacketDecoder";

    private final byte[] mBuffer;
    private MidiBtleTimeTracker mTimeTracker;

    private final int TIMESTAMP_MASK_HIGH = 0x1F80;
    private final int TIMESTAMP_MASK_LOW = 0x7F;
@@ -41,6 +42,10 @@ public class BluetoothPacketDecoder extends PacketDecoder {

    @Override
    public void decodePacket(byte[] buffer, MidiReceiver receiver) {
        if (mTimeTracker == null) {
            mTimeTracker = new MidiBtleTimeTracker(System.nanoTime());
        }

        int length = buffer.length;

        // NOTE his code allows running status across packets,
@@ -57,10 +62,12 @@ public class BluetoothPacketDecoder extends PacketDecoder {
        }

        // shift bits 0 - 5 to bits 7 - 12
        int timestamp = (header & HEADER_TIMESTAMP_MASK) << 7;
        int highTimestamp = (header & HEADER_TIMESTAMP_MASK) << 7;
        boolean lastWasTimestamp = false;
        int dataCount = 0;
        int previousLowTimestamp = 0;
        long nanoTimestamp = 0;
        int currentTimestamp = 0;

        // iterate through the rest of the packet, separating MIDI data from timestamps
        for (int i = 1; i < buffer.length; i++) {
@@ -69,25 +76,28 @@ public class BluetoothPacketDecoder extends PacketDecoder {
            if ((b & 0x80) != 0 && !lastWasTimestamp) {
                lastWasTimestamp = true;
                int lowTimestamp = b & TIMESTAMP_MASK_LOW;
                int newTimestamp = (timestamp & TIMESTAMP_MASK_HIGH) | lowTimestamp;
                if (lowTimestamp < previousLowTimestamp) {
                    newTimestamp = (newTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH;
                    highTimestamp = (highTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH;
                }
                previousLowTimestamp = lowTimestamp;

                if (newTimestamp != timestamp) {
                int newTimestamp = highTimestamp | lowTimestamp;
                if (newTimestamp != currentTimestamp) {
                    if (dataCount > 0) {
                        // send previous message separately since it has a different timestamp
                        try {
                           // FIXME use sendWithTimestamp
                            receiver.send(mBuffer, 0, dataCount);
                            receiver.sendWithTimestamp(mBuffer, 0, dataCount, nanoTimestamp);
                        } catch (IOException e) {
                            // ???
                        }
                        dataCount = 0;
                    }
                    currentTimestamp = newTimestamp;
                }
                timestamp = newTimestamp;

                // calculate nanoTimestamp
                long now = System.nanoTime();
                nanoTimestamp = mTimeTracker.convertTimestampToNanotime(currentTimestamp, now);
            } else {
                lastWasTimestamp = false;
                mBuffer[dataCount++] = b;
@@ -96,8 +106,7 @@ public class BluetoothPacketDecoder extends PacketDecoder {

        if (dataCount > 0) {
            try {
                // FIXME use sendWithTimestamp
                receiver.send(mBuffer, 0, dataCount);
                receiver.sendWithTimestamp(mBuffer, 0, dataCount, nanoTimestamp);
            } catch (IOException e) {
                // ???
            }
+109 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.android.bluetoothmidiservice;

/**
 * Convert MIDI over BTLE timestamps to system nanotime.
 */
public class MidiBtleTimeTracker {

    public final static long NANOS_PER_MILLI = 1000000L;

    private final static long RANGE_MILLIS = 0x2000; // per MIDI / BTLE standard
    private final static long RANGE_NANOS = RANGE_MILLIS * NANOS_PER_MILLI;

    private int mWindowMillis = 20; // typical max connection interval
    private long mWindowNanos = mWindowMillis * NANOS_PER_MILLI;

    private int mPreviousTimestamp; // Used to calculate deltas.
    private long mPreviousNow;
    // Our model of the peripherals millisecond clock.
    private long mPeripheralTimeMillis;
    // Host time that corresponds to time=0 on the peripheral.
    private long mBaseHostTimeNanos;
    private long mPreviousResult; // To prevent retrograde timestamp

    public MidiBtleTimeTracker(long now) {
        mPeripheralTimeMillis = 0;
        mBaseHostTimeNanos = now;
        mPreviousNow = now;
    }

    /**
     * @param timestamp
     *            13-bit millis in range of 0 to 8191
     * @param now
     *            current time in nanoseconds
     * @return nanoseconds corresponding to the timestamp
     */
    public long convertTimestampToNanotime(int timestamp, long now) {
        long deltaMillis = timestamp - mPreviousTimestamp;
        // will be negative when timestamp wraps
        if (deltaMillis < 0) {
            deltaMillis += RANGE_MILLIS;
        }
        mPeripheralTimeMillis += deltaMillis;

        // If we have not been called for a long time then
        // make sure we have not wrapped multiple times.
        if ((now - mPreviousNow) > (RANGE_NANOS / 2)) {
            // Handle missed wraps.
            long minimumTimeNanos = (now - mBaseHostTimeNanos)
                    - (RANGE_NANOS / 2);
            long minimumTimeMillis = minimumTimeNanos / NANOS_PER_MILLI;
            while (mPeripheralTimeMillis < minimumTimeMillis) {
                mPeripheralTimeMillis += RANGE_MILLIS;
            }
        }

        // Convert peripheral time millis to host time nanos.
        long timestampHostNanos = (mPeripheralTimeMillis * NANOS_PER_MILLI)
                + mBaseHostTimeNanos;

        // The event cannot be in the future. So move window if we hit that.
        if (timestampHostNanos > now) {
            mPeripheralTimeMillis = 0;
            mBaseHostTimeNanos = now;
            timestampHostNanos = now;
        } else {
            // Timestamp should not be older than our window time.
            long windowBottom = now - mWindowNanos;
            if (timestampHostNanos < windowBottom) {
                mPeripheralTimeMillis = 0;
                mBaseHostTimeNanos = windowBottom;
                timestampHostNanos = windowBottom;
            }
        }
        // prevent retrograde timestamp
        if (timestampHostNanos < mPreviousResult) {
            timestampHostNanos = mPreviousResult;
        }
        mPreviousResult = timestampHostNanos;
        mPreviousTimestamp = timestamp;
        mPreviousNow = now;
        return timestampHostNanos;
    }

    public int getWindowMillis() {
        return mWindowMillis;
    }

    public void setWindowMillis(int window) {
        this.mWindowMillis = window;
    }

}