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