1 /*
2  * Copyright (C) 2022 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.server.usb;
18 
19 import android.util.Log;
20 
21 import java.io.ByteArrayOutputStream;
22 
23 /**
24  * Converts between raw MIDI packets and USB MIDI 1.0 packets.
25  * This is NOT thread-safe. Please handle locking outside this function for multiple threads.
26  * For data mapping to an invalid cable number, this converter will use the first cable.
27  */
28 public class UsbMidiPacketConverter {
29     private static final String TAG = "UsbMidiPacketConverter";
30 
31     // Refer to Table 4-1 in USB MIDI 1.0 spec.
32     private static final int[] PAYLOAD_SIZE = new int[]{
33             /* 0x00 */ -1, // Miscellaneous function codes. Reserved for future extensions.
34             /* 0x01 */ -1, // Cable events. Reserved for future expansion.
35             /* 0x02 */  2, // Two-byte System Common messages like MTC, SongSelect, etc
36             /* 0x03 */  3, // Three-byte System Common messages like SPP, etc.
37             /* 0x04 */  3, // SysEx starts or continues
38             /* 0x05 */  1, // Single-byte System Common Message or single-byte SysEx ends.
39             /* 0x06 */  2, // SysEx ends with following two bytes.
40             /* 0x07 */  3, // SysEx ends with following three bytes.
41             /* 0x08 */  3, // Note-off
42             /* 0x09 */  3, // Note-on
43             /* 0x0a */  3, // Poly-KeyPress
44             /* 0x0b */  3, // Control Change
45             /* 0x0c */  2, // Program Change
46             /* 0x0d */  2, // Channel Pressure
47             /* 0x0e */  3, // PitchBend Change
48             /* 0x0f */  1  // Single Byte
49     };
50 
51     // Each System MIDI message is a certain size. These can be mapped to a
52     // Code Index number defined in Table 4-1 of USB MIDI 1.0.
53     private static final int[] CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE = new int[]{
54             /* 0x00 */ -1, // Start of Exclusive. Special case.
55             /* 0x01 */  2, // MIDI Time Code. Two byte message
56             /* 0x02 */  3, // Song Point Pointer. Three byte message
57             /* 0x03 */  2, // Song Select. Two byte message
58             /* 0x04 */ -1, // Undefined MIDI System Common
59             /* 0x05 */ -1, // Undefined MIDI System Common
60             /* 0x06 */  5, // Tune Request. One byte message
61             /* 0x07 */ -1, // End of Exclusive. Special case.
62             /* 0x08 */  5, // Timing clock. One byte message
63             /* 0x09 */ -1, // Undefined MIDI System Real-time
64             /* 0x0a */  5, // Start. One byte message
65             /* 0x0b */  5, // Continue. One byte message
66             /* 0x0c */  5, // Stop. One byte message
67             /* 0x0d */ -1, // Undefined MIDI System Real-time
68             /* 0x0e */  5, // Active Sensing. One byte message
69             /* 0x0f */  5  // System Reset. One byte message
70     };
71 
72     // These code index numbers also come from Table 4-1 in USB MIDI 1.0 spec.
73     private static final byte CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES = 0x4;
74     private static final byte CODE_INDEX_NUMBER_SINGLE_BYTE = 0xF;
75     private static final byte CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE = (byte) 0x5;
76 
77     // System messages are defined in MIDI.
78     private static final byte FIRST_SYSTEM_MESSAGE_VALUE = (byte) 0xF0;
79     private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
80     private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
81 
82     private UsbMidiEncoder[] mUsbMidiEncoders;
83     private ByteArrayOutputStream mEncoderOutputStream = new ByteArrayOutputStream();
84 
85     private UsbMidiDecoder mUsbMidiDecoder;
86 
87     /**
88      * Creates encoders.
89      *
90      * createEncoders() must be called before raw MIDI can be converted to USB MIDI.
91      *
92      * @param size the number of encoders to create
93      */
createEncoders(int size)94     public void createEncoders(int size) {
95         mUsbMidiEncoders = new UsbMidiEncoder[size];
96         for (int i = 0; i < size; i++) {
97             mUsbMidiEncoders[i] = new UsbMidiEncoder(i);
98         }
99     }
100 
101     /**
102      * Converts a raw MIDI array into a USB MIDI array.
103      *
104      * Call pullEncodedMidiPackets to retrieve the byte array.
105      *
106      * @param midiBytes the raw MIDI bytes to convert
107      * @param size the size of usbMidiBytes
108      * @param encoderId which encoder to use
109      */
encodeMidiPackets(byte[] midiBytes, int size, int encoderId)110     public void encodeMidiPackets(byte[] midiBytes, int size, int encoderId) {
111         // Use the first encoder if the encoderId is invalid.
112         if (encoderId >= mUsbMidiEncoders.length) {
113             Log.w(TAG, "encoderId " + encoderId + " invalid");
114             encoderId = 0;
115         }
116         byte[] encodedPacket = mUsbMidiEncoders[encoderId].encode(midiBytes, size);
117         mEncoderOutputStream.write(encodedPacket, 0, encodedPacket.length);
118     }
119 
120     /**
121      * Returns the encoded MIDI packets from encodeMidiPackets
122      *
123      * @return byte array of USB MIDI packets
124      */
pullEncodedMidiPackets()125     public byte[] pullEncodedMidiPackets() {
126         byte[] output = mEncoderOutputStream.toByteArray();
127         mEncoderOutputStream.reset();
128         return output;
129     }
130 
131     /**
132      * Creates decoders.
133      *
134      * createDecoders() must be called before USB MIDI can be converted to raw MIDI.
135      *
136      * @param size the number of decoders to create
137      */
createDecoders(int size)138     public void createDecoders(int size) {
139         mUsbMidiDecoder = new UsbMidiDecoder(size);
140     }
141 
142     /**
143      * Converts a USB MIDI array into a multiple MIDI arrays, one per cable.
144      *
145      * Call pullDecodedMidiPackets to retrieve the byte array.
146      *
147      * @param usbMidiBytes the USB MIDI bytes to convert
148      * @param size the size of usbMidiBytes
149      */
decodeMidiPackets(byte[] usbMidiBytes, int size)150     public void decodeMidiPackets(byte[] usbMidiBytes, int size) {
151         mUsbMidiDecoder.decode(usbMidiBytes, size);
152     }
153 
154     /**
155      * Returns the decoded MIDI packets from decodeMidiPackets
156      *
157      * @param cableNumber the cable to pull data from
158      * @return byte array of raw MIDI packets
159      */
pullDecodedMidiPackets(int cableNumber)160     public byte[] pullDecodedMidiPackets(int cableNumber) {
161         return mUsbMidiDecoder.pullBytes(cableNumber);
162     }
163 
164     private class UsbMidiDecoder {
165         int mNumJacks;
166         ByteArrayOutputStream[] mDecodedByteArrays;
167 
UsbMidiDecoder(int numJacks)168         UsbMidiDecoder(int numJacks) {
169             mNumJacks = numJacks;
170             mDecodedByteArrays = new ByteArrayOutputStream[numJacks];
171             for (int i = 0; i < numJacks; i++) {
172                 mDecodedByteArrays[i] = new ByteArrayOutputStream();
173             }
174         }
175 
176         // Decodes the data from USB MIDI to raw MIDI.
177         // Each valid 4 byte input maps to a 1-3 byte output.
178         // Reference the USB MIDI 1.0 spec for more info.
decode(byte[] usbMidiBytes, int size)179         public void decode(byte[] usbMidiBytes, int size) {
180             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
181             if (size % 4 != 0) {
182                 Log.w(TAG, "size " + size + " not multiple of 4");
183             }
184             for (int i = 0; i + 3 < size; i += 4) {
185                 int cableNumber = (usbMidiBytes[i] >> 4) & 0x0f;
186                 int codeIndex = usbMidiBytes[i] & 0x0f;
187                 int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
188                 if (numPayloadBytes < 0) {
189                     continue;
190                 }
191                 // Use the first cable if the cable number is invalid.
192                 if (cableNumber >= mNumJacks) {
193                     Log.w(TAG, "cableNumber " + cableNumber + " invalid");
194                     cableNumber = 0;
195                 }
196                 mDecodedByteArrays[cableNumber].write(usbMidiBytes, i + 1, numPayloadBytes);
197             }
198         }
199 
pullBytes(int cableNumber)200         public byte[] pullBytes(int cableNumber) {
201             // Use the first cable if the cable number is invalid.
202             if (cableNumber >= mNumJacks) {
203                 Log.w(TAG, "cableNumber " + cableNumber + " invalid");
204                 cableNumber = 0;
205             }
206             byte[] output = mDecodedByteArrays[cableNumber].toByteArray();
207             mDecodedByteArrays[cableNumber].reset();
208             return output;
209         }
210     }
211 
212     private class UsbMidiEncoder {
213         // In order to facilitate large scale transfers, SysEx can be sent in multiple packets.
214         // If encode() is called without an SysEx end, we must continue SysEx for the next packet.
215         // All other packets should be 3 bytes or less and must be not be broken between packets.
216         private byte[] mStoredSystemExclusiveBytes = new byte[3];
217         private int mNumStoredSystemExclusiveBytes = 0;
218         private boolean mHasSystemExclusiveStarted = false;
219 
220         private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data
221 
222         private byte mShiftedCableNumber;
223 
UsbMidiEncoder(int cableNumber)224         UsbMidiEncoder(int cableNumber) {
225             // Jack Id is always the left nibble of every byte so shift this now.
226             mShiftedCableNumber = (byte) (cableNumber << 4);
227         }
228 
229         // Encodes the data from raw MIDI to USB MIDI.
230         // Each valid 1-3 byte input maps to a 4 byte output.
231         // Reference the USB MIDI 1.0 spec for more info.
232         // MidiFramer is not needed here as this code handles partial packets.
233         // Long SysEx messages split between packets will encode and return a
234         // byte stream even if the SysEx end has not been sent.
235         // If there are less than 3 remaining data bytes in a SysEx message left,
236         // these bytes will be combined with the next set of packets.
encode(byte[] midiBytes, int size)237         public byte[] encode(byte[] midiBytes, int size) {
238             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
239             int curLocation = 0;
240             while (curLocation < size) {
241                 if (midiBytes[curLocation] >= 0) { // Data byte
242                     if (mHasSystemExclusiveStarted) {
243                         mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
244                                 midiBytes[curLocation];
245                         mNumStoredSystemExclusiveBytes++;
246                         if (mNumStoredSystemExclusiveBytes == 3) {
247                             outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES
248                                     | mShiftedCableNumber);
249                             outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
250                             mNumStoredSystemExclusiveBytes = 0;
251                         }
252                     } else {
253                         writeSingleByte(outputStream, midiBytes[curLocation]);
254                     }
255                     curLocation++;
256                     continue;
257                 } else if (midiBytes[curLocation] != SYSEX_END_EXCLUSIVE) {
258                     // SysEx operation was interrupted. Pass the data directly down.
259                     if (mHasSystemExclusiveStarted) {
260                         int index = 0;
261                         while (index < mNumStoredSystemExclusiveBytes) {
262                             writeSingleByte(outputStream, mStoredSystemExclusiveBytes[index]);
263                             index++;
264                         }
265                         mNumStoredSystemExclusiveBytes = 0;
266                         mHasSystemExclusiveStarted = false;
267                     }
268                 }
269 
270                 if (midiBytes[curLocation] < FIRST_SYSTEM_MESSAGE_VALUE) { // Channel message
271                     byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
272                     int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
273                     if (curLocation + channelMessageSize <= size) {
274                         outputStream.write(codeIndexNumber | mShiftedCableNumber);
275                         outputStream.write(midiBytes, curLocation, channelMessageSize);
276                         // Fill in the rest of the bytes with 0.
277                         outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
278                         curLocation += channelMessageSize;
279                     } else { // The packet is missing data. Use single byte messages.
280                         while (curLocation < size) {
281                             writeSingleByte(outputStream, midiBytes[curLocation]);
282                             curLocation++;
283                         }
284                     }
285                 } else if (midiBytes[curLocation] == SYSEX_START_EXCLUSIVE) {
286                     mHasSystemExclusiveStarted = true;
287                     mStoredSystemExclusiveBytes[0] = midiBytes[curLocation];
288                     mNumStoredSystemExclusiveBytes = 1;
289                     curLocation++;
290                 } else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
291                     // 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
292                     outputStream.write((CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
293                             + mNumStoredSystemExclusiveBytes) | mShiftedCableNumber);
294                     mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
295                             midiBytes[curLocation];
296                     mNumStoredSystemExclusiveBytes++;
297                     outputStream.write(mStoredSystemExclusiveBytes, 0,
298                              mNumStoredSystemExclusiveBytes);
299                     // Fill in the rest of the bytes with 0.
300                     outputStream.write(mEmptyBytes, 0, 3 - mNumStoredSystemExclusiveBytes);
301                     mHasSystemExclusiveStarted = false;
302                     mNumStoredSystemExclusiveBytes = 0;
303                     curLocation++;
304                 } else {
305                     int systemType = midiBytes[curLocation] & 0x0f;
306                     int codeIndexNumber = CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE[systemType];
307                     if (codeIndexNumber < 0) { // Unknown type. Use single byte messages.
308                         writeSingleByte(outputStream, midiBytes[curLocation]);
309                         curLocation++;
310                     } else {
311                         int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
312                         if (curLocation + systemMessageSize <= size) {
313                             outputStream.write(codeIndexNumber | mShiftedCableNumber);
314                             outputStream.write(midiBytes, curLocation, systemMessageSize);
315                             // Fill in the rest of the bytes with 0.
316                             outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
317                             curLocation += systemMessageSize;
318                         } else { // The packet is missing data. Use single byte messages.
319                             while (curLocation < size) {
320                                 writeSingleByte(outputStream, midiBytes[curLocation]);
321                                 curLocation++;
322                             }
323                         }
324                     }
325                 }
326             }
327             return outputStream.toByteArray();
328         }
329 
writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite)330         private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
331             outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE | mShiftedCableNumber);
332             outputStream.write(byteToWrite);
333             outputStream.write(0);
334             outputStream.write(0);
335         }
336     }
337 }
338