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.

1017 lines
38KB

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