1 /* 2 * Copyright (C) 2020 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 static org.junit.Assert.assertArrayEquals; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertTrue; 22 23 import android.util.Log; 24 25 import androidx.test.filters.SmallTest; 26 import androidx.test.runner.AndroidJUnit4; 27 28 import com.android.internal.midi.MidiFramer; 29 30 import org.junit.Test; 31 import org.junit.runner.RunWith; 32 33 import java.io.IOException; 34 35 @RunWith(AndroidJUnit4.class) 36 @SmallTest 37 public class BluetoothMidiDecoderTest { 38 39 private static final String TAG = "BluetoothMidiDecoderTest"; 40 private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; 41 private static final long NANOS_PER_MSEC = 1000000L; 42 43 static class DecoderChecker { 44 AccumulatingMidiReceiver mReceiver; 45 BluetoothPacketDecoder mDecoder; 46 DecoderChecker()47 DecoderChecker() { 48 mReceiver = new AccumulatingMidiReceiver(); 49 final int maxBytes = 20; 50 mDecoder = new BluetoothPacketDecoder(maxBytes); 51 } 52 compareWithExpected(final byte[][] expectedMessages)53 void compareWithExpected(final byte[][] expectedMessages) { 54 byte[][] actualRows = mReceiver.getBuffers(); 55 Long[] actualTimestamps = mReceiver.getTimestamps(); 56 long previousTime = 0; 57 // Compare the gathered with the expected. 58 assertEquals(expectedMessages.length, actualRows.length); 59 for (int i = 0; i < expectedMessages.length; i++) { 60 byte[] expectedRow = expectedMessages[i]; 61 Log.d(TAG, "expectedRow = " 62 + MidiFramer.formatMidiData(expectedRow, 0, expectedRow.length)); 63 byte[] actualRow = actualRows[i]; 64 Log.d(TAG, "actualRow = " 65 + MidiFramer.formatMidiData(actualRow, 0, actualRow.length)); 66 assertArrayEquals(expectedRow, actualRow); 67 // Are the timestamps monotonic? 68 long currentTime = actualTimestamps[i]; 69 Log.d(TAG, "previousTime = " + previousTime + ", currentTime = " + currentTime); 70 assertTrue(currentTime >= previousTime); 71 previousTime = currentTime; 72 } 73 } 74 decodePacket(byte[] packet)75 void decodePacket(byte[] packet) throws IOException { 76 mDecoder.decodePacket(packet, mReceiver); 77 } 78 decodePackets(byte[][] multiplePackets)79 void decodePackets(byte[][] multiplePackets) throws IOException { 80 try { 81 for (int i = 0; i < multiplePackets.length; i++) { 82 byte[] packet = multiplePackets[i]; 83 mDecoder.decodePacket(packet, mReceiver); 84 } 85 } catch (Exception e) { 86 assertEquals(null, e); 87 } 88 } 89 test(byte[] encoded, byte[][] decoded)90 void test(byte[] encoded, byte[][] decoded) throws IOException { 91 decodePacket(encoded); 92 compareWithExpected(decoded); 93 } 94 test(byte[][] encoded, byte[][] decoded)95 void test(byte[][] encoded, byte[][] decoded) throws IOException { 96 decodePackets(encoded); 97 compareWithExpected(decoded); 98 } 99 } 100 101 @Test testOneNoteOn()102 public void testOneNoteOn() throws IOException { 103 final byte[] encoded = { 104 (byte) 0x80, // high bit of header must be set 105 (byte) 0x80, // high bit of timestamp 106 (byte) 0x90, 0x40, 0x64 107 }; 108 final byte[][] decoded = { 109 {(byte) 0x90, 0x40, 0x64} 110 }; 111 new DecoderChecker().test(encoded, decoded); 112 } 113 114 @Test testReservedHeaderBit()115 public void testReservedHeaderBit() throws IOException { 116 final byte[] encoded = { 117 // Decoder should ignore the reserved bit. 118 (byte) (0x80 | 0x40), // set RESERVED bit in header! 119 (byte) 0x80, // high bit of timestamp 120 (byte) 0x90, 0x40, 0x64 121 }; 122 final byte[][] decoded = { 123 {(byte) 0x90, 0x40, 0x64} 124 }; 125 new DecoderChecker().test(encoded, decoded); 126 } 127 128 @Test testTwoNotesOnRunning()129 public void testTwoNotesOnRunning() throws IOException { 130 final byte[] encoded = { 131 (byte) 0x80, // high bit of header must be set 132 (byte) 0x80, // high bit of timestamp 133 (byte) 0x90, 0x40, 0x64, 134 (byte) 0x85, // timestamp 135 (byte) 0x42, 0x70 136 }; 137 final byte[][] decoded = { 138 {(byte) 0x90, 0x40, 0x64}, 139 {(byte) 0x42, 0x70} 140 }; 141 new DecoderChecker().test(encoded, decoded); 142 } 143 144 @Test testTwoNoteOnsTwoChannels()145 public void testTwoNoteOnsTwoChannels() throws IOException { 146 final byte[] encoded = { 147 (byte) 0x80, // high bit of header must be set 148 (byte) 0x80, // high bit of timestamp 149 (byte) 0x93, 0x40, 0x60, 150 // two channels so no running status 151 (byte) 0x80, // high bit of timestamp 152 (byte) 0x95, 0x47, 0x64 153 }; 154 final byte[][] decoded = { 155 {(byte) 0x93, 0x40, 0x60, (byte) 0x95, 0x47, 0x64} 156 }; 157 new DecoderChecker().test(encoded, decoded); 158 } 159 160 @Test testTwoNoteOnsOverTime()161 public void testTwoNoteOnsOverTime() throws IOException { 162 final byte[][] encoded = {{ 163 (byte) 0x80, // high bit of header must be set 164 (byte) 0x80, // high bit of timestamp 165 (byte) 0x98, 0x45, 0x60 166 }, 167 { 168 (byte) 0x80, // high bit of header must be set 169 (byte) 0x82, // timestamp advanced by 2 msec 170 (byte) 0x90, 0x40, 0x64, 171 (byte) 0x84, // timestamp needed because of time delay 172 // encoder uses running status 173 0x47, 0x72 174 }}; 175 final byte[][] decoded = { 176 {(byte) 0x98, 0x45, 0x60}, 177 {(byte) 0x90, 0x40, 0x64}, 178 {(byte) 0x47, 0x72} 179 }; 180 new DecoderChecker().test(encoded, decoded); 181 } 182 183 @Test testSysExBasic()184 public void testSysExBasic() throws IOException { 185 final byte[][] encoded = {{ 186 (byte) 0x80, // high bit of header must be set 187 (byte) 0x80, // timestamp 188 (byte) 0xF0, 0x7D, // Begin prototyping SysEx 189 0x01, 0x02, 0x03, 0x04, 0x05, 190 (byte) 0x80, // timestamp 191 (byte) 0xF7 // End SysEx 192 }}; 193 final byte[][] decoded = { 194 {(byte) 0xF0, 0x7D, // experimental SysEx 195 0x01, 0x02, 0x03, 0x04, 0x05, (byte) 0xF7} 196 }; 197 new DecoderChecker().test(encoded, decoded); 198 } 199 200 @Test testSysExTwoPackets()201 public void testSysExTwoPackets() throws IOException { 202 final byte[][] encoded = {{ 203 (byte) 0x80, // high bit of header must be set 204 (byte) 0x80, // timestamp 205 (byte) 0xF0, 0x7D, // Begin prototyping SysEx 206 0x01, 0x02 207 }, 208 { 209 (byte) 0x80, // high bit of header must be set 210 0x03, 0x04, 0x05, 211 (byte) 0x80, // timestamp 212 (byte) 0xF7 // End SysEx 213 }}; 214 final byte[][] decoded = { 215 {(byte) 0xF0, 0x7D, 0x01, 0x02}, // experimental SysEx 216 {0x03, 0x04, 0x05, (byte) 0xF7} 217 }; 218 new DecoderChecker().test(encoded, decoded); 219 } 220 221 @Test testSysExThreePackets()222 public void testSysExThreePackets() throws IOException { 223 final byte[][] encoded = { 224 {(byte) 0x80, // high bit of header must be set 225 (byte) 0x80, // timestamp 226 (byte) 0xF0, 0x7D, // Begin prototyping SysEx 227 0x01, 0x02 228 }, 229 { 230 (byte) 0x80, // high bit of header must be set 231 0x03, 0x04, 0x05, 232 }, 233 { 234 (byte) 0x80, // high bit of header must be set 235 0x06, 0x07, 0x08, 236 (byte) 0x80, // timestamp 237 (byte) 0xF7 // End SysEx 238 }}; 239 final byte[][] decoded = { 240 {(byte) 0xF0, 0x7D, 0x01, 0x02}, // experimental SysEx 241 {0x03, 0x04, 0x05}, 242 {0x06, 0x07, 0x08, (byte) 0xF7} 243 }; 244 new DecoderChecker().test(encoded, decoded); 245 } 246 247 } 248