1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.bluetoothmidiservice;
18
19/**
20 * Convert MIDI over BTLE timestamps to system nanotime.
21 */
22public class MidiBtleTimeTracker {
23
24    public final static long NANOS_PER_MILLI = 1000000L;
25
26    private final static long RANGE_MILLIS = 0x2000; // per MIDI / BTLE standard
27    private final static long RANGE_NANOS = RANGE_MILLIS * NANOS_PER_MILLI;
28
29    private int mWindowMillis = 20; // typical max connection interval
30    private long mWindowNanos = mWindowMillis * NANOS_PER_MILLI;
31
32    private int mPreviousTimestamp; // Used to calculate deltas.
33    private long mPreviousNow;
34    // Our model of the peripherals millisecond clock.
35    private long mPeripheralTimeMillis;
36    // Host time that corresponds to time=0 on the peripheral.
37    private long mBaseHostTimeNanos;
38    private long mPreviousResult; // To prevent retrograde timestamp
39
40    public MidiBtleTimeTracker(long now) {
41        mPeripheralTimeMillis = 0;
42        mBaseHostTimeNanos = now;
43        mPreviousNow = now;
44    }
45
46    /**
47     * @param timestamp
48     *            13-bit millis in range of 0 to 8191
49     * @param now
50     *            current time in nanoseconds
51     * @return nanoseconds corresponding to the timestamp
52     */
53    public long convertTimestampToNanotime(int timestamp, long now) {
54        long deltaMillis = timestamp - mPreviousTimestamp;
55        // will be negative when timestamp wraps
56        if (deltaMillis < 0) {
57            deltaMillis += RANGE_MILLIS;
58        }
59        mPeripheralTimeMillis += deltaMillis;
60
61        // If we have not been called for a long time then
62        // make sure we have not wrapped multiple times.
63        if ((now - mPreviousNow) > (RANGE_NANOS / 2)) {
64            // Handle missed wraps.
65            long minimumTimeNanos = (now - mBaseHostTimeNanos)
66                    - (RANGE_NANOS / 2);
67            long minimumTimeMillis = minimumTimeNanos / NANOS_PER_MILLI;
68            while (mPeripheralTimeMillis < minimumTimeMillis) {
69                mPeripheralTimeMillis += RANGE_MILLIS;
70            }
71        }
72
73        // Convert peripheral time millis to host time nanos.
74        long timestampHostNanos = (mPeripheralTimeMillis * NANOS_PER_MILLI)
75                + mBaseHostTimeNanos;
76
77        // The event cannot be in the future. So move window if we hit that.
78        if (timestampHostNanos > now) {
79            mPeripheralTimeMillis = 0;
80            mBaseHostTimeNanos = now;
81            timestampHostNanos = now;
82        } else {
83            // Timestamp should not be older than our window time.
84            long windowBottom = now - mWindowNanos;
85            if (timestampHostNanos < windowBottom) {
86                mPeripheralTimeMillis = 0;
87                mBaseHostTimeNanos = windowBottom;
88                timestampHostNanos = windowBottom;
89            }
90        }
91        // prevent retrograde timestamp
92        if (timestampHostNanos < mPreviousResult) {
93            timestampHostNanos = mPreviousResult;
94        }
95        mPreviousResult = timestampHostNanos;
96        mPreviousTimestamp = timestamp;
97        mPreviousNow = now;
98        return timestampHostNanos;
99    }
100
101    public int getWindowMillis() {
102        return mWindowMillis;
103    }
104
105    public void setWindowMillis(int window) {
106        this.mWindowMillis = window;
107    }
108
109}
110