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