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 import android.media.midi.MidiReceiver;
20 import android.util.Log;
21 
22 import java.io.IOException;
23 
24 /**
25  * This is an abstract base class that decodes a BLE-MIDI packet
26  * buffer and passes it to a {@link android.media.midi.MidiReceiver}
27  */
28 public class BluetoothPacketDecoder extends PacketDecoder {
29 
30     private static final String TAG = "BluetoothPacketDecoder";
31 
32     private final byte[] mBuffer;
33     private int mMaxPacketSize;
34     private int mBytesInBuffer;
35     private MidiBtleTimeTracker mTimeTracker;
36 
37     private int mLowTimestamp;
38     private long mNanoTimestamp;
39 
40     private static final int TIMESTAMP_MASK_HIGH = 0x1F80; // top 7 bits
41     private static final int TIMESTAMP_MASK_LOW = 0x7F;    // bottom 7 bits
42     private static final int HEADER_TIMESTAMP_MASK = 0x3F; // bottom 6 bits
43 
BluetoothPacketDecoder(int maxPacketSize)44     public BluetoothPacketDecoder(int maxPacketSize) {
45         mBuffer = new byte[maxPacketSize];
46         setMaxPacketSize(maxPacketSize);
47     }
48 
49     /**
50      * Dynamically sets the maximum packet size
51      */
setMaxPacketSize(int maxPacketSize)52     public void setMaxPacketSize(int maxPacketSize) {
53         mMaxPacketSize = Math.min(maxPacketSize, mBuffer.length);
54     }
55 
flushOutput(MidiReceiver receiver)56     private void flushOutput(MidiReceiver receiver) {
57         if (mBytesInBuffer > 0) {
58             try {
59                 receiver.send(mBuffer, 0, mBytesInBuffer, mNanoTimestamp);
60             } catch (IOException e) {
61                 // ???
62             }
63             mBytesInBuffer = 0;
64         }
65     }
66 
67     // NOTE: this code allows running status across packets,
68     // although the specification does not allow that.
69     @Override
decodePacket(byte[] buffer, MidiReceiver receiver)70     public void decodePacket(byte[] buffer, MidiReceiver receiver) {
71         if (mTimeTracker == null) {
72             mTimeTracker = new MidiBtleTimeTracker(System.nanoTime());
73         }
74 
75         int length = buffer.length;
76         if (length < 1) {
77             Log.e(TAG, "empty packet");
78             return;
79         }
80 
81         byte header = buffer[0];
82         // Check for the header bit 7.
83         // Ignore the reserved bit 6.
84         if ((header & 0x80) != 0x80) {
85             Log.e(TAG, "packet does not start with header");
86             return;
87         }
88 
89         // shift bits 0 - 5 to bits 7 - 12
90         int highTimestamp = (header & HEADER_TIMESTAMP_MASK) << 7;
91         boolean lastWasTimestamp = false;
92         int previousLowTimestamp = 0;
93         int currentTimestamp = highTimestamp | mLowTimestamp;
94 
95         int curMaxPacketSize = mMaxPacketSize;
96         // Iterate through the rest of the packet, separating MIDI data from timestamps.
97         for (int i = 1; i < buffer.length; i++) {
98             byte b = buffer[i];
99 
100             // Is this a timestamp byte?
101             if ((b & 0x80) != 0 && !lastWasTimestamp) {
102                 lastWasTimestamp = true;
103                 mLowTimestamp = b & TIMESTAMP_MASK_LOW;
104 
105                 // If the low timestamp byte wraps within the packet then
106                 // increment the high timestamp byte.
107                 if (mLowTimestamp < previousLowTimestamp) {
108                     highTimestamp = (highTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH;
109                 }
110                 previousLowTimestamp = mLowTimestamp;
111 
112                 // If the timestamp advances then send any pending data.
113                 int newTimestamp = highTimestamp | mLowTimestamp;
114                 if (newTimestamp != currentTimestamp) {
115                     // Send previous message separately since it has a different timestamp.
116                     flushOutput(receiver);
117                     currentTimestamp = newTimestamp;
118                 }
119 
120                 // Calculate MIDI nanosecond timestamp from BLE timestamp.
121                 long now = System.nanoTime();
122                 mNanoTimestamp = mTimeTracker.convertTimestampToNanotime(currentTimestamp, now);
123             } else {
124                 lastWasTimestamp = false;
125                 // Flush if full before adding more data.
126                 if (mBytesInBuffer >= curMaxPacketSize) {
127                     flushOutput(receiver);
128                 }
129                 mBuffer[mBytesInBuffer++] = b;
130             }
131         }
132 
133         flushOutput(receiver);
134     }
135 }
136