The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1019 lines
36KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. The code included in this file is provided under the terms of the ISC license
  8. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  9. To use, copy, modify, and/or distribute this software for any purpose with or
  10. without fee is hereby granted provided that the above copyright notice and
  11. this permission notice appear in all copies.
  12. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  13. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  14. DISCLAIMED.
  15. ==============================================================================
  16. */
  17. namespace juce
  18. {
  19. namespace universal_midi_packets
  20. {
  21. constexpr uint8_t operator""_u8 (unsigned long long int i) { return static_cast<uint8_t> (i); }
  22. constexpr uint16_t operator""_u16 (unsigned long long int i) { return static_cast<uint16_t> (i); }
  23. constexpr uint32_t operator""_u32 (unsigned long long int i) { return static_cast<uint32_t> (i); }
  24. constexpr uint64_t operator""_u64 (unsigned long long int i) { return static_cast<uint64_t> (i); }
  25. class UniversalMidiPacketTests : public UnitTest
  26. {
  27. public:
  28. UniversalMidiPacketTests()
  29. : UnitTest ("Universal MIDI Packet", UnitTestCategories::midi)
  30. {
  31. }
  32. void runTest() override
  33. {
  34. auto random = getRandom();
  35. beginTest ("Short bytestream midi messages can be round-tripped through the UMP converter");
  36. {
  37. Midi1ToBytestreamTranslator translator (0);
  38. forEachNonSysExTestMessage (random, [&] (const MidiMessage& m)
  39. {
  40. Packets packets;
  41. Conversion::toMidi1 (m, packets);
  42. expect (packets.size() == 1);
  43. // Make sure that the message type is correct
  44. expect (Utils::getMessageType (packets.data()[0]) == ((m.getRawData()[0] >> 0x4) == 0xf ? 0x1 : 0x2));
  45. translator.dispatch (View {packets.data() },
  46. 0,
  47. [&] (const MidiMessage& roundTripped)
  48. {
  49. expect (equal (m, roundTripped));
  50. });
  51. });
  52. }
  53. beginTest ("Bytestream SysEx converts to universal packets");
  54. {
  55. {
  56. // Zero length message
  57. Packets packets;
  58. Conversion::toMidi1 (createRandomSysEx (random, 0), packets);
  59. expect (packets.size() == 2);
  60. expect (packets.data()[0] == 0x30000000);
  61. expect (packets.data()[1] == 0x00000000);
  62. }
  63. {
  64. const auto message = createRandomSysEx (random, 1);
  65. Packets packets;
  66. Conversion::toMidi1 (message, packets);
  67. expect (packets.size() == 2);
  68. const auto* sysEx = message.getSysExData();
  69. expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x01, sysEx[0], 0));
  70. expect (packets.data()[1] == 0x00000000);
  71. }
  72. {
  73. const auto message = createRandomSysEx (random, 6);
  74. Packets packets;
  75. Conversion::toMidi1 (message, packets);
  76. expect (packets.size() == 2);
  77. const auto* sysEx = message.getSysExData();
  78. expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x06, sysEx[0], sysEx[1]));
  79. expect (packets.data()[1] == Utils::bytesToWord (sysEx[2], sysEx[3], sysEx[4], sysEx[5]));
  80. }
  81. {
  82. const auto message = createRandomSysEx (random, 12);
  83. Packets packets;
  84. Conversion::toMidi1 (message, packets);
  85. expect (packets.size() == 4);
  86. const auto* sysEx = message.getSysExData();
  87. expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x16, sysEx[0], sysEx[1]));
  88. expect (packets.data()[1] == Utils::bytesToWord (sysEx[2], sysEx[3], sysEx[4], sysEx[5]));
  89. expect (packets.data()[2] == Utils::bytesToWord (0x30, 0x36, sysEx[6], sysEx[7]));
  90. expect (packets.data()[3] == Utils::bytesToWord (sysEx[8], sysEx[9], sysEx[10], sysEx[11]));
  91. }
  92. {
  93. const auto message = createRandomSysEx (random, 13);
  94. Packets packets;
  95. Conversion::toMidi1 (message, packets);
  96. expect (packets.size() == 6);
  97. const auto* sysEx = message.getSysExData();
  98. expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x16, sysEx[0], sysEx[1]));
  99. expect (packets.data()[1] == Utils::bytesToWord (sysEx[2], sysEx[3], sysEx[4], sysEx[5]));
  100. expect (packets.data()[2] == Utils::bytesToWord (0x30, 0x26, sysEx[6], sysEx[7]));
  101. expect (packets.data()[3] == Utils::bytesToWord (sysEx[8], sysEx[9], sysEx[10], sysEx[11]));
  102. expect (packets.data()[4] == Utils::bytesToWord (0x30, 0x31, sysEx[12], 0));
  103. expect (packets.data()[5] == 0x00000000);
  104. }
  105. }
  106. ToBytestreamDispatcher converter (0);
  107. Packets packets;
  108. const auto checkRoundTrip = [&] (const MidiBuffer& expected)
  109. {
  110. for (const auto meta : expected)
  111. Conversion::toMidi1 (meta.getMessage(), packets);
  112. MidiBuffer output;
  113. converter.dispatch (packets.data(),
  114. packets.data() + packets.size(),
  115. 0,
  116. [&] (const MidiMessage& roundTripped)
  117. {
  118. output.addEvent (roundTripped, int (roundTripped.getTimeStamp()));
  119. });
  120. packets.clear();
  121. expect (equal (expected, output));
  122. };
  123. beginTest ("Long SysEx bytestream midi messages can be round-tripped through the UMP converter");
  124. {
  125. for (auto length : { 0, 1, 2, 3, 4, 5, 6, 7, 13, 20, 100, 1000 })
  126. {
  127. MidiBuffer expected;
  128. expected.addEvent (createRandomSysEx (random, size_t (length)), 0);
  129. checkRoundTrip (expected);
  130. }
  131. }
  132. beginTest ("UMP SysEx7 messages interspersed with utility messages convert to bytestream");
  133. {
  134. const auto sysEx = createRandomSysEx (random, 100);
  135. Packets originalPackets;
  136. Conversion::toMidi1 (sysEx, originalPackets);
  137. Packets modifiedPackets;
  138. const auto addRandomUtilityUMP = [&]
  139. {
  140. const auto newPacket = createRandomUtilityUMP (random);
  141. modifiedPackets.add (View (newPacket.data()));
  142. };
  143. for (const auto& packet : originalPackets)
  144. {
  145. addRandomUtilityUMP();
  146. modifiedPackets.add (packet);
  147. addRandomUtilityUMP();
  148. }
  149. MidiBuffer output;
  150. converter.dispatch (modifiedPackets.data(),
  151. modifiedPackets.data() + modifiedPackets.size(),
  152. 0,
  153. [&] (const MidiMessage& roundTripped)
  154. {
  155. output.addEvent (roundTripped, int (roundTripped.getTimeStamp()));
  156. });
  157. // All Utility messages should have been ignored
  158. expect (output.getNumEvents() == 1);
  159. for (const auto meta : output)
  160. expect (equal (meta.getMessage(), sysEx));
  161. }
  162. beginTest ("UMP SysEx7 messages interspersed with System Realtime messages convert to bytestream");
  163. {
  164. const auto sysEx = createRandomSysEx (random, 200);
  165. Packets originalPackets;
  166. Conversion::toMidi1 (sysEx, originalPackets);
  167. Packets modifiedPackets;
  168. MidiBuffer realtimeMessages;
  169. const auto addRandomRealtimeUMP = [&]
  170. {
  171. const auto newPacket = createRandomRealtimeUMP (random);
  172. modifiedPackets.add (View (newPacket.data()));
  173. realtimeMessages.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket), 0);
  174. };
  175. for (const auto& packet : originalPackets)
  176. {
  177. addRandomRealtimeUMP();
  178. modifiedPackets.add (packet);
  179. addRandomRealtimeUMP();
  180. }
  181. MidiBuffer output;
  182. converter.dispatch (modifiedPackets.data(),
  183. modifiedPackets.data() + modifiedPackets.size(),
  184. 0,
  185. [&] (const MidiMessage& roundTripped)
  186. {
  187. output.addEvent (roundTripped, int (roundTripped.getTimeStamp()));
  188. });
  189. const auto numOutputs = output.getNumEvents();
  190. const auto numInputs = realtimeMessages.getNumEvents();
  191. expect (numOutputs == numInputs + 1);
  192. if (numOutputs == numInputs + 1)
  193. {
  194. const auto isMetadataEquivalent = [] (const MidiMessageMetadata& a,
  195. const MidiMessageMetadata& b)
  196. {
  197. return equal (a.getMessage(), b.getMessage());
  198. };
  199. auto it = output.begin();
  200. for (const auto meta : realtimeMessages)
  201. {
  202. if (! isMetadataEquivalent (*it, meta))
  203. {
  204. expect (equal ((*it).getMessage(), sysEx));
  205. ++it;
  206. }
  207. expect (isMetadataEquivalent (*it, meta));
  208. ++it;
  209. }
  210. }
  211. }
  212. beginTest ("UMP SysEx7 messages interspersed with System Realtime and Utility messages convert to bytestream");
  213. {
  214. const auto sysEx = createRandomSysEx (random, 300);
  215. Packets originalPackets;
  216. Conversion::toMidi1 (sysEx, originalPackets);
  217. Packets modifiedPackets;
  218. MidiBuffer realtimeMessages;
  219. const auto addRandomRealtimeUMP = [&]
  220. {
  221. const auto newPacket = createRandomRealtimeUMP (random);
  222. modifiedPackets.add (View (newPacket.data()));
  223. realtimeMessages.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket), 0);
  224. };
  225. const auto addRandomUtilityUMP = [&]
  226. {
  227. const auto newPacket = createRandomUtilityUMP (random);
  228. modifiedPackets.add (View (newPacket.data()));
  229. };
  230. for (const auto& packet : originalPackets)
  231. {
  232. addRandomRealtimeUMP();
  233. addRandomUtilityUMP();
  234. modifiedPackets.add (packet);
  235. addRandomRealtimeUMP();
  236. addRandomUtilityUMP();
  237. }
  238. MidiBuffer output;
  239. converter.dispatch (modifiedPackets.data(),
  240. modifiedPackets.data() + modifiedPackets.size(),
  241. 0,
  242. [&] (const MidiMessage& roundTripped)
  243. {
  244. output.addEvent (roundTripped, int (roundTripped.getTimeStamp()));
  245. });
  246. const auto numOutputs = output.getNumEvents();
  247. const auto numInputs = realtimeMessages.getNumEvents();
  248. expect (numOutputs == numInputs + 1);
  249. if (numOutputs == numInputs + 1)
  250. {
  251. const auto isMetadataEquivalent = [] (const MidiMessageMetadata& a, const MidiMessageMetadata& b)
  252. {
  253. return equal (a.getMessage(), b.getMessage());
  254. };
  255. auto it = output.begin();
  256. for (const auto meta : realtimeMessages)
  257. {
  258. if (! isMetadataEquivalent (*it, meta))
  259. {
  260. expect (equal ((*it).getMessage(), sysEx));
  261. ++it;
  262. }
  263. expect (isMetadataEquivalent (*it, meta));
  264. ++it;
  265. }
  266. }
  267. }
  268. beginTest ("SysEx messages are terminated by non-Utility, non-Realtime messages");
  269. {
  270. const auto noteOn = [&]
  271. {
  272. MidiBuffer b;
  273. b.addEvent (MidiMessage::noteOn (1, uint8_t (64), uint8_t (64)), 0);
  274. return b;
  275. }();
  276. const auto noteOnPackets = [&]
  277. {
  278. Packets p;
  279. for (const auto meta : noteOn)
  280. Conversion::toMidi1 (meta.getMessage(), p);
  281. return p;
  282. }();
  283. const auto sysEx = createRandomSysEx (random, 300);
  284. const auto originalPackets = [&]
  285. {
  286. Packets p;
  287. Conversion::toMidi1 (sysEx, p);
  288. return p;
  289. }();
  290. const auto modifiedPackets = [&]
  291. {
  292. Packets p;
  293. const auto insertionPoint = std::next (originalPackets.begin(), 10);
  294. std::for_each (originalPackets.begin(),
  295. insertionPoint,
  296. [&] (const View& view) { p.add (view); });
  297. for (const auto& view : noteOnPackets)
  298. p.add (view);
  299. std::for_each (insertionPoint,
  300. originalPackets.end(),
  301. [&] (const View& view) { p.add (view); });
  302. return p;
  303. }();
  304. // modifiedPackets now contains some SysEx packets interrupted by a MIDI 1 noteOn
  305. MidiBuffer output;
  306. const auto pushToOutput = [&] (const Packets& p)
  307. {
  308. converter.dispatch (p.data(),
  309. p.data() + p.size(),
  310. 0,
  311. [&] (const MidiMessage& roundTripped)
  312. {
  313. output.addEvent (roundTripped, int (roundTripped.getTimeStamp()));
  314. });
  315. };
  316. pushToOutput (modifiedPackets);
  317. // Interrupted sysEx shouldn't be present
  318. expect (equal (output, noteOn));
  319. const auto newSysEx = createRandomSysEx (random, 300);
  320. Packets newSysExPackets;
  321. Conversion::toMidi1 (newSysEx, newSysExPackets);
  322. // If we push another midi event without interrupting it,
  323. // it should get through without being modified,
  324. // and it shouldn't be affected by the previous (interrupted) sysex.
  325. output.clear();
  326. pushToOutput (newSysExPackets);
  327. expect (output.getNumEvents() == 1);
  328. for (const auto meta : output)
  329. expect (equal (meta.getMessage(), newSysEx));
  330. }
  331. beginTest ("Widening conversions work");
  332. {
  333. // This is similar to the 'slow' example code from the MIDI 2.0 spec
  334. const auto baselineScale = [] (uint32_t srcVal, uint32_t srcBits, uint32_t dstBits)
  335. {
  336. const auto scaleBits = (uint32_t) (dstBits - srcBits);
  337. auto bitShiftedValue = (uint32_t) (srcVal << scaleBits);
  338. const auto srcCenter = (uint32_t) (1 << (srcBits - 1));
  339. if (srcVal <= srcCenter)
  340. return bitShiftedValue;
  341. const auto repeatBits = (uint32_t) (srcBits - 1);
  342. const auto repeatMask = (uint32_t) ((1 << repeatBits) - 1);
  343. auto repeatValue = (uint32_t) (srcVal & repeatMask);
  344. if (scaleBits > repeatBits)
  345. repeatValue <<= scaleBits - repeatBits;
  346. else
  347. repeatValue >>= repeatBits - scaleBits;
  348. while (repeatValue != 0)
  349. {
  350. bitShiftedValue |= repeatValue;
  351. repeatValue >>= repeatBits;
  352. }
  353. return bitShiftedValue;
  354. };
  355. const auto baselineScale7To8 = [&] (uint8_t in)
  356. {
  357. return baselineScale (in, 7, 8);
  358. };
  359. const auto baselineScale7To16 = [&] (uint8_t in)
  360. {
  361. return baselineScale (in, 7, 16);
  362. };
  363. const auto baselineScale14To16 = [&] (uint16_t in)
  364. {
  365. return baselineScale (in, 14, 16);
  366. };
  367. const auto baselineScale7To32 = [&] (uint8_t in)
  368. {
  369. return baselineScale (in, 7, 32);
  370. };
  371. const auto baselineScale14To32 = [&] (uint16_t in)
  372. {
  373. return baselineScale (in, 14, 32);
  374. };
  375. for (auto i = 0; i != 100; ++i)
  376. {
  377. const auto rand = (uint8_t) random.nextInt (0x80);
  378. expectEquals ((int64_t) Conversion::scaleTo8 (rand),
  379. (int64_t) baselineScale7To8 (rand));
  380. }
  381. expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x00), (int64_t) 0x0000);
  382. expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x0a), (int64_t) 0x1400);
  383. expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x40), (int64_t) 0x8000);
  384. expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x57), (int64_t) 0xaeba);
  385. expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x7f), (int64_t) 0xffff);
  386. for (auto i = 0; i != 100; ++i)
  387. {
  388. const auto rand = (uint8_t) random.nextInt (0x80);
  389. expectEquals ((int64_t) Conversion::scaleTo16 (rand),
  390. (int64_t) baselineScale7To16 (rand));
  391. }
  392. for (auto i = 0; i != 100; ++i)
  393. {
  394. const auto rand = (uint16_t) random.nextInt (0x4000);
  395. expectEquals ((int64_t) Conversion::scaleTo16 (rand),
  396. (int64_t) baselineScale14To16 (rand));
  397. }
  398. for (auto i = 0; i != 100; ++i)
  399. {
  400. const auto rand = (uint8_t) random.nextInt (0x80);
  401. expectEquals ((int64_t) Conversion::scaleTo32 (rand),
  402. (int64_t) baselineScale7To32 (rand));
  403. }
  404. expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x0000), (int64_t) 0x00000000);
  405. expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x2000), (int64_t) 0x80000000);
  406. expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x3fff), (int64_t) 0xffffffff);
  407. for (auto i = 0; i != 100; ++i)
  408. {
  409. const auto rand = (uint16_t) random.nextInt (0x4000);
  410. expectEquals ((int64_t) Conversion::scaleTo32 (rand),
  411. (int64_t) baselineScale14To32 (rand));
  412. }
  413. }
  414. beginTest ("Round-trip widening/narrowing conversions work");
  415. {
  416. for (auto i = 0; i != 100; ++i)
  417. {
  418. {
  419. const auto rand = (uint8_t) random.nextInt (0x80);
  420. expectEquals (Conversion::scaleTo7 (Conversion::scaleTo8 (rand)), rand);
  421. }
  422. {
  423. const auto rand = (uint8_t) random.nextInt (0x80);
  424. expectEquals (Conversion::scaleTo7 (Conversion::scaleTo16 (rand)), rand);
  425. }
  426. {
  427. const auto rand = (uint8_t) random.nextInt (0x80);
  428. expectEquals (Conversion::scaleTo7 (Conversion::scaleTo32 (rand)), rand);
  429. }
  430. {
  431. const auto rand = (uint16_t) random.nextInt (0x4000);
  432. expectEquals ((uint64_t) Conversion::scaleTo14 (Conversion::scaleTo16 (rand)), (uint64_t) rand);
  433. }
  434. {
  435. const auto rand = (uint16_t) random.nextInt (0x4000);
  436. expectEquals ((uint64_t) Conversion::scaleTo14 (Conversion::scaleTo32 (rand)), (uint64_t) rand);
  437. }
  438. }
  439. }
  440. beginTest ("MIDI 2 -> 1 note on conversions");
  441. {
  442. {
  443. Packets midi2;
  444. midi2.add (PacketX2 { 0x41946410, 0x12345678 });
  445. Packets midi1;
  446. midi1.add (PacketX1 { 0x21946409 });
  447. checkMidi2ToMidi1Conversion (midi2, midi1);
  448. }
  449. {
  450. // If the velocity is close to 0, the output velocity should still be 1
  451. Packets midi2;
  452. midi2.add (PacketX2 { 0x4295327f, 0x00345678 });
  453. Packets midi1;
  454. midi1.add (PacketX1 { 0x22953201 });
  455. checkMidi2ToMidi1Conversion (midi2, midi1);
  456. }
  457. }
  458. beginTest ("MIDI 2 -> 1 note off conversion");
  459. {
  460. Packets midi2;
  461. midi2.add (PacketX2 { 0x448b0520, 0xfedcba98 });
  462. Packets midi1;
  463. midi1.add (PacketX1 { 0x248b057f });
  464. checkMidi2ToMidi1Conversion (midi2, midi1);
  465. }
  466. beginTest ("MIDI 2 -> 1 poly pressure conversion");
  467. {
  468. Packets midi2;
  469. midi2.add (PacketX2 { 0x49af0520, 0x80dcba98 });
  470. Packets midi1;
  471. midi1.add (PacketX1 { 0x29af0540 });
  472. checkMidi2ToMidi1Conversion (midi2, midi1);
  473. }
  474. beginTest ("MIDI 2 -> 1 control change conversion");
  475. {
  476. Packets midi2;
  477. midi2.add (PacketX2 { 0x49b00520, 0x80dcba98 });
  478. Packets midi1;
  479. midi1.add (PacketX1 { 0x29b00540 });
  480. checkMidi2ToMidi1Conversion (midi2, midi1);
  481. }
  482. beginTest ("MIDI 2 -> 1 channel pressure conversion");
  483. {
  484. Packets midi2;
  485. midi2.add (PacketX2 { 0x40d20520, 0x80dcba98 });
  486. Packets midi1;
  487. midi1.add (PacketX1 { 0x20d24000 });
  488. checkMidi2ToMidi1Conversion (midi2, midi1);
  489. }
  490. beginTest ("MIDI 2 -> 1 nrpn rpn conversion");
  491. {
  492. {
  493. Packets midi2;
  494. midi2.add (PacketX2 { 0x44240123, 0x456789ab });
  495. Packets midi1;
  496. midi1.add (PacketX1 { 0x24b46501 });
  497. midi1.add (PacketX1 { 0x24b46423 });
  498. midi1.add (PacketX1 { 0x24b40622 });
  499. midi1.add (PacketX1 { 0x24b42659 });
  500. checkMidi2ToMidi1Conversion (midi2, midi1);
  501. }
  502. {
  503. Packets midi2;
  504. midi2.add (PacketX2 { 0x48347f7f, 0xffffffff });
  505. Packets midi1;
  506. midi1.add (PacketX1 { 0x28b4637f });
  507. midi1.add (PacketX1 { 0x28b4627f });
  508. midi1.add (PacketX1 { 0x28b4067f });
  509. midi1.add (PacketX1 { 0x28b4267f });
  510. checkMidi2ToMidi1Conversion (midi2, midi1);
  511. }
  512. }
  513. beginTest ("MIDI 2 -> 1 program change and bank select conversion");
  514. {
  515. {
  516. // If the bank valid bit is 0, just emit a program change
  517. Packets midi2;
  518. midi2.add (PacketX2 { 0x4cc10000, 0x70004020 });
  519. Packets midi1;
  520. midi1.add (PacketX1 { 0x2cc17000 });
  521. checkMidi2ToMidi1Conversion (midi2, midi1);
  522. }
  523. {
  524. // If the bank valid bit is 1, emit bank select control changes and a program change
  525. Packets midi2;
  526. midi2.add (PacketX2 { 0x4bc20001, 0x70004020 });
  527. Packets midi1;
  528. midi1.add (PacketX1 { 0x2bb20040 });
  529. midi1.add (PacketX1 { 0x2bb22020 });
  530. midi1.add (PacketX1 { 0x2bc27000 });
  531. checkMidi2ToMidi1Conversion (midi2, midi1);
  532. }
  533. }
  534. beginTest ("MIDI 2 -> 1 pitch bend conversion");
  535. {
  536. Packets midi2;
  537. midi2.add (PacketX2 { 0x4eee0000, 0x12340000 });
  538. Packets midi1;
  539. midi1.add (PacketX1 { 0x2eee0d09 });
  540. checkMidi2ToMidi1Conversion (midi2, midi1);
  541. }
  542. beginTest ("MIDI 2 -> 1 messages which don't convert");
  543. {
  544. const uint8_t opcodes[] { 0x0, 0x1, 0x4, 0x5, 0x6, 0xf };
  545. for (const auto opcode : opcodes)
  546. {
  547. Packets midi2;
  548. midi2.add (PacketX2 { Utils::bytesToWord (0x40, (uint8_t) (opcode << 0x4), 0, 0), 0x0 });
  549. checkMidi2ToMidi1Conversion (midi2, {});
  550. }
  551. }
  552. beginTest ("MIDI 2 -> 1 messages which are passed through");
  553. {
  554. const uint8_t typecodesX1[] { 0x0, 0x1, 0x2 };
  555. for (const auto typecode : typecodesX1)
  556. {
  557. Packets p;
  558. p.add (PacketX1 { (uint32_t) ((int64_t) typecode << 0x1c | (random.nextInt64() & 0xffffff)) });
  559. checkMidi2ToMidi1Conversion (p, p);
  560. }
  561. {
  562. Packets p;
  563. p.add (PacketX2 { (uint32_t) (0x3 << 0x1c | (random.nextInt64() & 0xffffff)),
  564. (uint32_t) (random.nextInt64() & 0xffffffff) });
  565. checkMidi2ToMidi1Conversion (p, p);
  566. }
  567. {
  568. Packets p;
  569. p.add (PacketX4 { (uint32_t) (0x5 << 0x1c | (random.nextInt64() & 0xffffff)),
  570. (uint32_t) (random.nextInt64() & 0xffffffff),
  571. (uint32_t) (random.nextInt64() & 0xffffffff),
  572. (uint32_t) (random.nextInt64() & 0xffffffff) });
  573. checkMidi2ToMidi1Conversion (p, p);
  574. }
  575. }
  576. beginTest ("MIDI 2 -> 1 control changes which should be ignored");
  577. {
  578. const uint8_t CCs[] { 6, 38, 98, 99, 100, 101, 0, 32 };
  579. for (const auto cc : CCs)
  580. {
  581. Packets midi2;
  582. midi2.add (PacketX2 { (uint32_t) (0x40b00000 | (cc << 0x8)), 0x00000000 });
  583. checkMidi2ToMidi1Conversion (midi2, {});
  584. }
  585. }
  586. beginTest ("MIDI 1 -> 2 note on conversions");
  587. {
  588. {
  589. Packets midi1;
  590. midi1.add (PacketX1 { 0x20904040 });
  591. Packets midi2;
  592. midi2.add (PacketX2 { 0x40904000, static_cast<uint32_t> (Conversion::scaleTo16 (0x40_u8)) << 0x10 });
  593. checkMidi1ToMidi2Conversion (midi1, midi2);
  594. }
  595. // If velocity is 0, convert to a note-off
  596. {
  597. Packets midi1;
  598. midi1.add (PacketX1 { 0x23935100 });
  599. Packets midi2;
  600. midi2.add (PacketX2 { 0x43835100, 0x0 });
  601. checkMidi1ToMidi2Conversion (midi1, midi2);
  602. }
  603. }
  604. beginTest ("MIDI 1 -> 2 note off conversions");
  605. {
  606. Packets midi1;
  607. midi1.add (PacketX1 { 0x21831020 });
  608. Packets midi2;
  609. midi2.add (PacketX2 { 0x41831000, static_cast<uint32_t> (Conversion::scaleTo16 (0x20_u8)) << 0x10 });
  610. checkMidi1ToMidi2Conversion (midi1, midi2);
  611. }
  612. beginTest ("MIDI 1 -> 2 poly pressure conversions");
  613. {
  614. Packets midi1;
  615. midi1.add (PacketX1 { 0x20af7330 });
  616. Packets midi2;
  617. midi2.add (PacketX2 { 0x40af7300, Conversion::scaleTo32 (0x30_u8) });
  618. checkMidi1ToMidi2Conversion (midi1, midi2);
  619. }
  620. beginTest ("individual MIDI 1 -> 2 control changes which should be ignored");
  621. {
  622. const uint8_t CCs[] { 6, 38, 98, 99, 100, 101, 0, 32 };
  623. for (const auto cc : CCs)
  624. {
  625. Packets midi1;
  626. midi1.add (PacketX1 { Utils::bytesToWord (0x20, 0xb0, cc, 0x00) });
  627. checkMidi1ToMidi2Conversion (midi1, {});
  628. }
  629. }
  630. beginTest ("MIDI 1 -> 2 control change conversions");
  631. {
  632. // normal control change
  633. {
  634. Packets midi1;
  635. midi1.add (PacketX1 { 0x29b1017f });
  636. Packets midi2;
  637. midi2.add (PacketX2 { 0x49b10100, Conversion::scaleTo32 (0x7f_u8) });
  638. checkMidi1ToMidi2Conversion (midi1, midi2);
  639. }
  640. // nrpn
  641. {
  642. Packets midi1;
  643. midi1.add (PacketX1 { 0x20b06301 });
  644. midi1.add (PacketX1 { 0x20b06223 });
  645. midi1.add (PacketX1 { 0x20b00645 });
  646. midi1.add (PacketX1 { 0x20b02667 });
  647. Packets midi2;
  648. midi2.add (PacketX2 { 0x40300123, Conversion::scaleTo32 (static_cast<uint16_t> ((0x45 << 7) | 0x67)) });
  649. checkMidi1ToMidi2Conversion (midi1, midi2);
  650. }
  651. // rpn
  652. {
  653. Packets midi1;
  654. midi1.add (PacketX1 { 0x20b06543 });
  655. midi1.add (PacketX1 { 0x20b06421 });
  656. midi1.add (PacketX1 { 0x20b00601 });
  657. midi1.add (PacketX1 { 0x20b02623 });
  658. Packets midi2;
  659. midi2.add (PacketX2 { 0x40204321, Conversion::scaleTo32 (static_cast<uint16_t> ((0x01 << 7) | 0x23)) });
  660. checkMidi1ToMidi2Conversion (midi1, midi2);
  661. }
  662. }
  663. beginTest ("MIDI 1 -> MIDI 2 program change and bank select");
  664. {
  665. Packets midi1;
  666. // program change with bank
  667. midi1.add (PacketX1 { 0x2bb20030 });
  668. midi1.add (PacketX1 { 0x2bb22010 });
  669. midi1.add (PacketX1 { 0x2bc24000 });
  670. // program change without bank (different group and channel)
  671. midi1.add (PacketX1 { 0x20c01000 });
  672. Packets midi2;
  673. midi2.add (PacketX2 { 0x4bc20001, 0x40003010 });
  674. midi2.add (PacketX2 { 0x40c00000, 0x10000000 });
  675. checkMidi1ToMidi2Conversion (midi1, midi2);
  676. }
  677. beginTest ("MIDI 1 -> MIDI 2 channel pressure conversions");
  678. {
  679. Packets midi1;
  680. midi1.add (PacketX1 { 0x20df3000 });
  681. Packets midi2;
  682. midi2.add (PacketX2 { 0x40df0000, Conversion::scaleTo32 (0x30_u8) });
  683. checkMidi1ToMidi2Conversion (midi1, midi2);
  684. }
  685. beginTest ("MIDI 1 -> MIDI 2 pitch bend conversions");
  686. {
  687. Packets midi1;
  688. midi1.add (PacketX1 { 0x20e74567 });
  689. Packets midi2;
  690. midi2.add (PacketX2 { 0x40e70000, Conversion::scaleTo32 (static_cast<uint16_t> ((0x67 << 7) | 0x45)) });
  691. checkMidi1ToMidi2Conversion (midi1, midi2);
  692. }
  693. }
  694. private:
  695. static Packets convertMidi2ToMidi1 (const Packets& midi2)
  696. {
  697. Packets r;
  698. for (const auto& packet : midi2)
  699. Conversion::midi2ToMidi1DefaultTranslation (packet, [&r] (const View& v) { r.add (v); });
  700. return r;
  701. }
  702. static Packets convertMidi1ToMidi2 (const Packets& midi1)
  703. {
  704. Packets r;
  705. Midi1ToMidi2DefaultTranslator translator;
  706. for (const auto& packet : midi1)
  707. translator.dispatch (packet, [&r] (const View& v) { r.add (v); });
  708. return r;
  709. }
  710. void checkBytestreamConversion (const Packets& actual, const Packets& expected)
  711. {
  712. expectEquals ((int) actual.size(), (int) expected.size());
  713. if (actual.size() != expected.size())
  714. return;
  715. auto actualPtr = actual.data();
  716. std::for_each (expected.data(),
  717. expected.data() + expected.size(),
  718. [&] (const uint32_t word) { expectEquals ((uint64_t) *actualPtr++, (uint64_t) word); });
  719. }
  720. void checkMidi2ToMidi1Conversion (const Packets& midi2, const Packets& expected)
  721. {
  722. checkBytestreamConversion (convertMidi2ToMidi1 (midi2), expected);
  723. }
  724. void checkMidi1ToMidi2Conversion (const Packets& midi1, const Packets& expected)
  725. {
  726. checkBytestreamConversion (convertMidi1ToMidi2 (midi1), expected);
  727. }
  728. MidiMessage createRandomSysEx (Random& random, size_t sysExBytes)
  729. {
  730. std::vector<uint8_t> data;
  731. data.reserve (sysExBytes);
  732. for (size_t i = 0; i != sysExBytes; ++i)
  733. data.push_back (uint8_t (random.nextInt (0x80)));
  734. return MidiMessage::createSysExMessage (data.data(), int (data.size()));
  735. }
  736. PacketX1 createRandomUtilityUMP (Random& random)
  737. {
  738. const auto status = random.nextInt (3);
  739. return PacketX1 { Utils::bytesToWord (0,
  740. uint8_t (status << 0x4),
  741. uint8_t (status == 0 ? 0 : random.nextInt (0x100)),
  742. uint8_t (status == 0 ? 0 : random.nextInt (0x100))) };
  743. }
  744. PacketX1 createRandomRealtimeUMP (Random& random)
  745. {
  746. const auto status = [&]
  747. {
  748. switch (random.nextInt (6))
  749. {
  750. case 0: return 0xf8;
  751. case 1: return 0xfa;
  752. case 2: return 0xfb;
  753. case 3: return 0xfc;
  754. case 4: return 0xfe;
  755. case 5: return 0xff;
  756. }
  757. jassertfalse;
  758. return 0x00;
  759. }();
  760. return PacketX1 { Utils::bytesToWord (0x10, uint8_t (status), 0x00, 0x00) };
  761. }
  762. template <typename Fn>
  763. void forEachNonSysExTestMessage (Random& random, Fn&& fn)
  764. {
  765. for (uint16_t counter = 0x80; counter != 0x100; ++counter)
  766. {
  767. const auto firstByte = (uint8_t) counter;
  768. if (firstByte == 0xf0 || firstByte == 0xf7)
  769. continue; // sysEx is tested separately
  770. const auto length = MidiMessage::getMessageLengthFromFirstByte (firstByte);
  771. const auto getDataByte = [&] { return uint8_t (random.nextInt (256) & 0x7f); };
  772. const auto message = [&]
  773. {
  774. switch (length)
  775. {
  776. case 1: return MidiMessage (firstByte);
  777. case 2: return MidiMessage (firstByte, getDataByte());
  778. case 3: return MidiMessage (firstByte, getDataByte(), getDataByte());
  779. }
  780. return MidiMessage();
  781. }();
  782. fn (message);
  783. }
  784. }
  785. #if JUCE_WINDOWS && ! JUCE_MINGW
  786. #define JUCE_CHECKED_ITERATOR(msg, size) \
  787. stdext::checked_array_iterator<typename std::remove_reference<decltype (msg)>::type> ((msg), (size_t) (size))
  788. #else
  789. #define JUCE_CHECKED_ITERATOR(msg, size) (msg)
  790. #endif
  791. static bool equal (const MidiMessage& a, const MidiMessage& b) noexcept
  792. {
  793. return a.getRawDataSize() == b.getRawDataSize()
  794. && std::equal (a.getRawData(), a.getRawData() + a.getRawDataSize(),
  795. JUCE_CHECKED_ITERATOR (b.getRawData(), b.getRawDataSize()));
  796. }
  797. #undef JUCE_CHECKED_ITERATOR
  798. static bool equal (const MidiBuffer& a, const MidiBuffer& b) noexcept
  799. {
  800. return a.data == b.data;
  801. }
  802. };
  803. static UniversalMidiPacketTests universalMidiPacketTests;
  804. }
  805. }