• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 java.io.ByteArrayOutputStream;
20 
21 /**
22  * Converts between MIDI packets and USB MIDI 1.0 packets.
23  */
24 public class UsbMidiPacketConverter {
25 
26     // Refer to Table 4-1 in USB MIDI 1.0 spec.
27     private static final int[] PAYLOAD_SIZE = new int[]{
28             /* 0x00 */ -1, // Miscellaneous function codes. Reserved for future extensions.
29             /* 0x01 */ -1, // Cable events. Reserved for future expansion.
30             /* 0x02 */  2, // Two-byte System Common messages like MTC, SongSelect, etc
31             /* 0x03 */  3, // Three-byte System Common messages like SPP, etc.
32             /* 0x04 */  3, // SysEx starts or continues
33             /* 0x05 */  1, // Single-byte System Common Message or single-byte SysEx ends.
34             /* 0x06 */  2, // SysEx ends with following two bytes.
35             /* 0x07 */  3, // SysEx ends with following three bytes.
36             /* 0x08 */  3, // Note-off
37             /* 0x09 */  3, // Note-on
38             /* 0x0a */  3, // Poly-KeyPress
39             /* 0x0b */  3, // Control Change
40             /* 0x0c */  2, // Program Change
41             /* 0x0d */  2, // Channel Pressure
42             /* 0x0e */  3, // PitchBend Change
43             /* 0x0f */  1  // Single Byte
44     };
45 
46     // Each System MIDI message is a certain size. These can be mapped to a
47     // Code Index number defined in Table 4-1 of USB MIDI 1.0.
48     private static final int[] CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE = new int[]{
49             /* 0x00 */ -1, // Start of Exclusive. Special case.
50             /* 0x01 */  2, // MIDI Time Code. Two byte message
51             /* 0x02 */  3, // Song Point Pointer. Three byte message
52             /* 0x03 */  2, // Song Select. Two byte message
53             /* 0x04 */ -1, // Undefined MIDI System Common
54             /* 0x05 */ -1, // Undefined MIDI System Common
55             /* 0x06 */  5, // Tune Request. One byte message
56             /* 0x07 */ -1, // End of Exclusive. Special case.
57             /* 0x08 */  5, // Timing clock. One byte message
58             /* 0x09 */ -1, // Undefined MIDI System Real-time
59             /* 0x0a */  5, // Start. One byte message
60             /* 0x0b */  5, // Continue. One byte message
61             /* 0x0c */  5, // Stop. One byte message
62             /* 0x0d */ -1, // Undefined MIDI System Real-time
63             /* 0x0e */  5, // Active Sensing. One byte message
64             /* 0x0f */  5  // System Reset. One byte message
65     };
66 
67     // These code index numbers also come from Table 4-1 in USB MIDI 1.0 spec.
68     private static final byte CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES = 0x4;
69     private static final byte CODE_INDEX_NUMBER_SINGLE_BYTE = 0xF;
70     private static final byte CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE = (byte) 0x5;
71 
72     // System messages are defined in MIDI.
73     private static final byte FIRST_SYSTEM_MESSAGE_VALUE = (byte) 0xF0;
74     private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
75     private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
76 
77     private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder();
78     private UsbMidiEncoder[] mUsbMidiEncoders;
79 
UsbMidiPacketConverter(int numEncoders)80     public UsbMidiPacketConverter(int numEncoders) {
81         mUsbMidiEncoders = new UsbMidiEncoder[numEncoders];
82         for (int i = 0; i < numEncoders; i++) {
83             mUsbMidiEncoders[i] = new UsbMidiEncoder();
84         }
85     }
86 
87     /**
88      * Converts a USB MIDI array into a raw MIDI array.
89      *
90      * @param usbMidiBytes the USB MIDI bytes to convert
91      * @param size the size of usbMidiBytes
92      * @return byte array of raw MIDI packets
93      */
usbMidiToRawMidi(byte[] usbMidiBytes, int size)94     public byte[] usbMidiToRawMidi(byte[] usbMidiBytes, int size) {
95         return mUsbMidiDecoder.decode(usbMidiBytes, size);
96     }
97 
98     /**
99      * Converts a raw MIDI array into a USB MIDI array.
100      *
101      * @param midiBytes the raw MIDI bytes to convert
102      * @param size the size of usbMidiBytes
103      * @param encoderId which encoder to use
104      * @return byte array of USB MIDI packets
105      */
rawMidiToUsbMidi(byte[] midiBytes, int size, int encoderId)106     public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size, int encoderId) {
107         return mUsbMidiEncoders[encoderId].encode(midiBytes, size);
108     }
109 
110     private class UsbMidiDecoder {
111         // Decodes the data from USB MIDI to raw MIDI.
112         // Each valid 4 byte input maps to a 1-3 byte output.
113         // Reference the USB MIDI 1.0 spec for more info.
decode(byte[] usbMidiBytes, int size)114         public byte[] decode(byte[] usbMidiBytes, int size) {
115             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
116             for (int i = 0; i + 3 < size; i += 4) {
117                 int codeIndex = usbMidiBytes[i] & 0x0f;
118                 int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
119                 if (numPayloadBytes < 0) {
120                     continue;
121                 }
122                 outputStream.write(usbMidiBytes, i + 1, numPayloadBytes);
123             }
124             return outputStream.toByteArray();
125         }
126     }
127 
128     private class UsbMidiEncoder {
129         // In order to facilitate large scale transfers, SysEx can be sent in multiple packets.
130         // If encode() is called without an SysEx end, we must continue SysEx for the next packet.
131         // All other packets should be 3 bytes or less and must be not be broken between packets.
132         private byte[] mStoredSystemExclusiveBytes = new byte[3];
133         private int mNumStoredSystemExclusiveBytes = 0;
134         private boolean mHasSystemExclusiveStarted = false;
135 
136         private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data
137 
138         // Encodes the data from raw MIDI to USB MIDI.
139         // Each valid 1-3 byte input maps to a 4 byte output.
140         // Reference the USB MIDI 1.0 spec for more info.
141         // MidiFramer is not needed here as this code handles partial packets.
142         // Long SysEx messages split between packets will encode and return a
143         // byte stream even if the SysEx end has not been sent.
144         // If there are less than 3 remaining data bytes in a SysEx message left,
145         // these bytes will be combined with the next set of packets.
encode(byte[] midiBytes, int size)146         public byte[] encode(byte[] midiBytes, int size) {
147             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
148             int curLocation = 0;
149             while (curLocation < size) {
150                 if (midiBytes[curLocation] >= 0) { // Data byte
151                     if (mHasSystemExclusiveStarted) {
152                         mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
153                                 midiBytes[curLocation];
154                         mNumStoredSystemExclusiveBytes++;
155                         if (mNumStoredSystemExclusiveBytes == 3) {
156                             outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES);
157                             outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
158                             mNumStoredSystemExclusiveBytes = 0;
159                         }
160                     } else {
161                         writeSingleByte(outputStream, midiBytes[curLocation]);
162                     }
163                     curLocation++;
164                     continue;
165                 } else if (midiBytes[curLocation] != SYSEX_END_EXCLUSIVE) {
166                     // SysEx operation was interrupted. Pass the data directly down.
167                     if (mHasSystemExclusiveStarted) {
168                         int index = 0;
169                         while (index < mNumStoredSystemExclusiveBytes) {
170                             writeSingleByte(outputStream, mStoredSystemExclusiveBytes[index]);
171                             index++;
172                         }
173                         mNumStoredSystemExclusiveBytes = 0;
174                         mHasSystemExclusiveStarted = false;
175                     }
176                 }
177 
178                 if (midiBytes[curLocation] < FIRST_SYSTEM_MESSAGE_VALUE) { // Channel message
179                     byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
180                     int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
181                     if (curLocation + channelMessageSize <= size) {
182                         outputStream.write(codeIndexNumber);
183                         outputStream.write(midiBytes, curLocation, channelMessageSize);
184                         // Fill in the rest of the bytes with 0.
185                         outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
186                         curLocation += channelMessageSize;
187                     } else { // The packet is missing data. Use single byte messages.
188                         while (curLocation < size) {
189                             writeSingleByte(outputStream, midiBytes[curLocation]);
190                             curLocation++;
191                         }
192                     }
193                 } else if (midiBytes[curLocation] == SYSEX_START_EXCLUSIVE) {
194                     mHasSystemExclusiveStarted = true;
195                     mStoredSystemExclusiveBytes[0] = midiBytes[curLocation];
196                     mNumStoredSystemExclusiveBytes = 1;
197                     curLocation++;
198                 } else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
199                     // 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
200                     outputStream.write(CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
201                             + mNumStoredSystemExclusiveBytes);
202                     mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
203                             midiBytes[curLocation];
204                     mNumStoredSystemExclusiveBytes++;
205                     outputStream.write(mStoredSystemExclusiveBytes, 0,
206                              mNumStoredSystemExclusiveBytes);
207                     // Fill in the rest of the bytes with 0.
208                     outputStream.write(mEmptyBytes, 0, 3 - mNumStoredSystemExclusiveBytes);
209                     mHasSystemExclusiveStarted = false;
210                     mNumStoredSystemExclusiveBytes = 0;
211                     curLocation++;
212                 } else {
213                     int systemType = midiBytes[curLocation] & 0x0f;
214                     int codeIndexNumber = CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE[systemType];
215                     if (codeIndexNumber < 0) { // Unknown type. Use single byte messages.
216                         writeSingleByte(outputStream, midiBytes[curLocation]);
217                         curLocation++;
218                     } else {
219                         int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
220                         if (curLocation + systemMessageSize <= size) {
221                             outputStream.write(codeIndexNumber);
222                             outputStream.write(midiBytes, curLocation, systemMessageSize);
223                             // Fill in the rest of the bytes with 0.
224                             outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
225                             curLocation += systemMessageSize;
226                         } else { // The packet is missing data. Use single byte messages.
227                             while (curLocation < size) {
228                                 writeSingleByte(outputStream, midiBytes[curLocation]);
229                                 curLocation++;
230                             }
231                         }
232                     }
233                 }
234             }
235             return outputStream.toByteArray();
236         }
237 
writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite)238         private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
239             outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE);
240             outputStream.write(byteToWrite);
241             outputStream.write(0);
242             outputStream.write(0);
243         }
244     }
245 }
246