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 
17 package com.android.bluetoothmidiservice;
18 
19 /**
20  * Convert MIDI over BTLE timestamps to system nanotime.
21  */
22 public 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 
MidiBtleTimeTracker(long now)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      */
convertTimestampToNanotime(int timestamp, long now)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 
getWindowMillis()101     public int getWindowMillis() {
102         return mWindowMillis;
103     }
104 
setWindowMillis(int window)105     public void setWindowMillis(int window) {
106         this.mWindowMillis = window;
107     }
108 
109 }
110