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