/* * 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; } }