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