2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
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.
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
20 ==============================================================================
25 namespace universal_midi_packets
28 constexpr uint8_t operator""_u8 (unsigned long long int i
) { return static_cast<uint8_t> (i
); }
29 constexpr uint16_t operator""_u16 (unsigned long long int i
) { return static_cast<uint16_t> (i
); }
30 constexpr uint32_t operator""_u32 (unsigned long long int i
) { return static_cast<uint32_t> (i
); }
31 constexpr uint64_t operator""_u64 (unsigned long long int i
) { return static_cast<uint64_t> (i
); }
33 class UniversalMidiPacketTests
: public UnitTest
36 UniversalMidiPacketTests()
37 : UnitTest ("Universal MIDI Packet", UnitTestCategories::midi
)
41 void runTest() override
43 auto random
= getRandom();
45 beginTest ("Short bytestream midi messages can be round-tripped through the UMP converter");
47 Midi1ToBytestreamTranslator
translator (0);
49 forEachNonSysExTestMessage (random
, [&] (const MidiMessage
& m
)
52 Conversion::toMidi1 (m
, packets
);
53 expect (packets
.size() == 1);
55 // Make sure that the message type is correct
56 expect (Utils::getMessageType (packets
.data()[0]) == ((m
.getRawData()[0] >> 0x4) == 0xf ? 0x1 : 0x2));
58 translator
.dispatch (View
{packets
.data() },
60 [&] (const MidiMessage
& roundTripped
)
62 expect (equal (m
, roundTripped
));
67 beginTest ("Bytestream SysEx converts to universal packets");
70 // Zero length message
72 Conversion::toMidi1 (createRandomSysEx (random
, 0), packets
);
73 expect (packets
.size() == 2);
75 expect (packets
.data()[0] == 0x30000000);
76 expect (packets
.data()[1] == 0x00000000);
80 const auto message
= createRandomSysEx (random
, 1);
82 Conversion::toMidi1 (message
, packets
);
83 expect (packets
.size() == 2);
85 const auto* sysEx
= message
.getSysExData();
86 expect (packets
.data()[0] == Utils::bytesToWord (0x30, 0x01, sysEx
[0], 0));
87 expect (packets
.data()[1] == 0x00000000);
91 const auto message
= createRandomSysEx (random
, 6);
93 Conversion::toMidi1 (message
, packets
);
94 expect (packets
.size() == 2);
96 const auto* sysEx
= message
.getSysExData();
97 expect (packets
.data()[0] == Utils::bytesToWord (0x30, 0x06, sysEx
[0], sysEx
[1]));
98 expect (packets
.data()[1] == Utils::bytesToWord (sysEx
[2], sysEx
[3], sysEx
[4], sysEx
[5]));
102 const auto message
= createRandomSysEx (random
, 12);
104 Conversion::toMidi1 (message
, packets
);
105 expect (packets
.size() == 4);
107 const auto* sysEx
= message
.getSysExData();
108 expect (packets
.data()[0] == Utils::bytesToWord (0x30, 0x16, sysEx
[0], sysEx
[1]));
109 expect (packets
.data()[1] == Utils::bytesToWord (sysEx
[2], sysEx
[3], sysEx
[4], sysEx
[5]));
110 expect (packets
.data()[2] == Utils::bytesToWord (0x30, 0x36, sysEx
[6], sysEx
[7]));
111 expect (packets
.data()[3] == Utils::bytesToWord (sysEx
[8], sysEx
[9], sysEx
[10], sysEx
[11]));
115 const auto message
= createRandomSysEx (random
, 13);
117 Conversion::toMidi1 (message
, packets
);
118 expect (packets
.size() == 6);
120 const auto* sysEx
= message
.getSysExData();
121 expect (packets
.data()[0] == Utils::bytesToWord (0x30, 0x16, sysEx
[0], sysEx
[1]));
122 expect (packets
.data()[1] == Utils::bytesToWord (sysEx
[2], sysEx
[3], sysEx
[4], sysEx
[5]));
123 expect (packets
.data()[2] == Utils::bytesToWord (0x30, 0x26, sysEx
[6], sysEx
[7]));
124 expect (packets
.data()[3] == Utils::bytesToWord (sysEx
[8], sysEx
[9], sysEx
[10], sysEx
[11]));
125 expect (packets
.data()[4] == Utils::bytesToWord (0x30, 0x31, sysEx
[12], 0));
126 expect (packets
.data()[5] == 0x00000000);
130 ToBytestreamDispatcher
converter (0);
133 const auto checkRoundTrip
= [&] (const MidiBuffer
& expected
)
135 for (const auto meta
: expected
)
136 Conversion::toMidi1 (meta
.getMessage(), packets
);
139 converter
.dispatch (packets
.data(),
140 packets
.data() + packets
.size(),
142 [&] (const MidiMessage
& roundTripped
)
144 output
.addEvent (roundTripped
, int (roundTripped
.getTimeStamp()));
148 expect (equal (expected
, output
));
151 beginTest ("Long SysEx bytestream midi messages can be round-tripped through the UMP converter");
153 for (auto length
: { 0, 1, 2, 3, 4, 5, 6, 7, 13, 20, 100, 1000 })
156 expected
.addEvent (createRandomSysEx (random
, size_t (length
)), 0);
157 checkRoundTrip (expected
);
161 beginTest ("UMP SysEx7 messages interspersed with utility messages convert to bytestream");
163 const auto sysEx
= createRandomSysEx (random
, 100);
164 Packets originalPackets
;
165 Conversion::toMidi1 (sysEx
, originalPackets
);
167 Packets modifiedPackets
;
169 const auto addRandomUtilityUMP
= [&]
171 const auto newPacket
= createRandomUtilityUMP (random
);
172 modifiedPackets
.add (View (newPacket
.data()));
175 for (const auto& packet
: originalPackets
)
177 addRandomUtilityUMP();
178 modifiedPackets
.add (packet
);
179 addRandomUtilityUMP();
183 converter
.dispatch (modifiedPackets
.data(),
184 modifiedPackets
.data() + modifiedPackets
.size(),
186 [&] (const MidiMessage
& roundTripped
)
188 output
.addEvent (roundTripped
, int (roundTripped
.getTimeStamp()));
191 // All Utility messages should have been ignored
192 expect (output
.getNumEvents() == 1);
194 for (const auto meta
: output
)
195 expect (equal (meta
.getMessage(), sysEx
));
198 beginTest ("UMP SysEx7 messages interspersed with System Realtime messages convert to bytestream");
200 const auto sysEx
= createRandomSysEx (random
, 200);
201 Packets originalPackets
;
202 Conversion::toMidi1 (sysEx
, originalPackets
);
204 Packets modifiedPackets
;
205 MidiBuffer realtimeMessages
;
207 const auto addRandomRealtimeUMP
= [&]
209 const auto newPacket
= createRandomRealtimeUMP (random
);
210 modifiedPackets
.add (View (newPacket
.data()));
211 realtimeMessages
.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket
), 0);
214 for (const auto& packet
: originalPackets
)
216 addRandomRealtimeUMP();
217 modifiedPackets
.add (packet
);
218 addRandomRealtimeUMP();
222 converter
.dispatch (modifiedPackets
.data(),
223 modifiedPackets
.data() + modifiedPackets
.size(),
225 [&] (const MidiMessage
& roundTripped
)
227 output
.addEvent (roundTripped
, int (roundTripped
.getTimeStamp()));
230 const auto numOutputs
= output
.getNumEvents();
231 const auto numInputs
= realtimeMessages
.getNumEvents();
232 expect (numOutputs
== numInputs
+ 1);
234 if (numOutputs
== numInputs
+ 1)
236 const auto isMetadataEquivalent
= [] (const MidiMessageMetadata
& a
,
237 const MidiMessageMetadata
& b
)
239 return equal (a
.getMessage(), b
.getMessage());
242 auto it
= output
.begin();
244 for (const auto meta
: realtimeMessages
)
246 if (! isMetadataEquivalent (*it
, meta
))
248 expect (equal ((*it
).getMessage(), sysEx
));
252 expect (isMetadataEquivalent (*it
, meta
));
258 beginTest ("UMP SysEx7 messages interspersed with System Realtime and Utility messages convert to bytestream");
260 const auto sysEx
= createRandomSysEx (random
, 300);
261 Packets originalPackets
;
262 Conversion::toMidi1 (sysEx
, originalPackets
);
264 Packets modifiedPackets
;
265 MidiBuffer realtimeMessages
;
267 const auto addRandomRealtimeUMP
= [&]
269 const auto newPacket
= createRandomRealtimeUMP (random
);
270 modifiedPackets
.add (View (newPacket
.data()));
271 realtimeMessages
.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket
), 0);
274 const auto addRandomUtilityUMP
= [&]
276 const auto newPacket
= createRandomUtilityUMP (random
);
277 modifiedPackets
.add (View (newPacket
.data()));
280 for (const auto& packet
: originalPackets
)
282 addRandomRealtimeUMP();
283 addRandomUtilityUMP();
284 modifiedPackets
.add (packet
);
285 addRandomRealtimeUMP();
286 addRandomUtilityUMP();
290 converter
.dispatch (modifiedPackets
.data(),
291 modifiedPackets
.data() + modifiedPackets
.size(),
293 [&] (const MidiMessage
& roundTripped
)
295 output
.addEvent (roundTripped
, int (roundTripped
.getTimeStamp()));
298 const auto numOutputs
= output
.getNumEvents();
299 const auto numInputs
= realtimeMessages
.getNumEvents();
300 expect (numOutputs
== numInputs
+ 1);
302 if (numOutputs
== numInputs
+ 1)
304 const auto isMetadataEquivalent
= [] (const MidiMessageMetadata
& a
, const MidiMessageMetadata
& b
)
306 return equal (a
.getMessage(), b
.getMessage());
309 auto it
= output
.begin();
311 for (const auto meta
: realtimeMessages
)
313 if (! isMetadataEquivalent (*it
, meta
))
315 expect (equal ((*it
).getMessage(), sysEx
));
319 expect (isMetadataEquivalent (*it
, meta
));
325 beginTest ("SysEx messages are terminated by non-Utility, non-Realtime messages");
327 const auto noteOn
= [&]
330 b
.addEvent (MidiMessage::noteOn (1, uint8_t (64), uint8_t (64)), 0);
334 const auto noteOnPackets
= [&]
338 for (const auto meta
: noteOn
)
339 Conversion::toMidi1 (meta
.getMessage(), p
);
344 const auto sysEx
= createRandomSysEx (random
, 300);
346 const auto originalPackets
= [&]
349 Conversion::toMidi1 (sysEx
, p
);
353 const auto modifiedPackets
= [&]
357 const auto insertionPoint
= std::next (originalPackets
.begin(), 10);
358 std::for_each (originalPackets
.begin(),
360 [&] (const View
& view
) { p
.add (view
); });
362 for (const auto& view
: noteOnPackets
)
365 std::for_each (insertionPoint
,
366 originalPackets
.end(),
367 [&] (const View
& view
) { p
.add (view
); });
372 // modifiedPackets now contains some SysEx packets interrupted by a MIDI 1 noteOn
376 const auto pushToOutput
= [&] (const Packets
& p
)
378 converter
.dispatch (p
.data(),
381 [&] (const MidiMessage
& roundTripped
)
383 output
.addEvent (roundTripped
, int (roundTripped
.getTimeStamp()));
387 pushToOutput (modifiedPackets
);
389 // Interrupted sysEx shouldn't be present
390 expect (equal (output
, noteOn
));
392 const auto newSysEx
= createRandomSysEx (random
, 300);
393 Packets newSysExPackets
;
394 Conversion::toMidi1 (newSysEx
, newSysExPackets
);
396 // If we push another midi event without interrupting it,
397 // it should get through without being modified,
398 // and it shouldn't be affected by the previous (interrupted) sysex.
401 pushToOutput (newSysExPackets
);
403 expect (output
.getNumEvents() == 1);
405 for (const auto meta
: output
)
406 expect (equal (meta
.getMessage(), newSysEx
));
409 beginTest ("Widening conversions work");
411 // This is similar to the 'slow' example code from the MIDI 2.0 spec
412 const auto baselineScale
= [] (uint32_t srcVal
, uint32_t srcBits
, uint32_t dstBits
)
414 const auto scaleBits
= (uint32_t) (dstBits
- srcBits
);
416 auto bitShiftedValue
= (uint32_t) (srcVal
<< scaleBits
);
418 const auto srcCenter
= (uint32_t) (1 << (srcBits
- 1));
420 if (srcVal
<= srcCenter
)
421 return bitShiftedValue
;
423 const auto repeatBits
= (uint32_t) (srcBits
- 1);
424 const auto repeatMask
= (uint32_t) ((1 << repeatBits
) - 1);
426 auto repeatValue
= (uint32_t) (srcVal
& repeatMask
);
428 if (scaleBits
> repeatBits
)
429 repeatValue
<<= scaleBits
- repeatBits
;
431 repeatValue
>>= repeatBits
- scaleBits
;
433 while (repeatValue
!= 0)
435 bitShiftedValue
|= repeatValue
;
436 repeatValue
>>= repeatBits
;
439 return bitShiftedValue
;
442 const auto baselineScale7To8
= [&] (uint8_t in
)
444 return baselineScale (in
, 7, 8);
447 const auto baselineScale7To16
= [&] (uint8_t in
)
449 return baselineScale (in
, 7, 16);
452 const auto baselineScale14To16
= [&] (uint16_t in
)
454 return baselineScale (in
, 14, 16);
457 const auto baselineScale7To32
= [&] (uint8_t in
)
459 return baselineScale (in
, 7, 32);
462 const auto baselineScale14To32
= [&] (uint16_t in
)
464 return baselineScale (in
, 14, 32);
467 for (auto i
= 0; i
!= 100; ++i
)
469 const auto rand
= (uint8_t) random
.nextInt (0x80);
470 expectEquals ((int64_t) Conversion::scaleTo8 (rand
),
471 (int64_t) baselineScale7To8 (rand
));
474 expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x00), (int64_t) 0x0000);
475 expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x0a), (int64_t) 0x1400);
476 expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x40), (int64_t) 0x8000);
477 expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x57), (int64_t) 0xaeba);
478 expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x7f), (int64_t) 0xffff);
480 for (auto i
= 0; i
!= 100; ++i
)
482 const auto rand
= (uint8_t) random
.nextInt (0x80);
483 expectEquals ((int64_t) Conversion::scaleTo16 (rand
),
484 (int64_t) baselineScale7To16 (rand
));
487 for (auto i
= 0; i
!= 100; ++i
)
489 const auto rand
= (uint16_t) random
.nextInt (0x4000);
490 expectEquals ((int64_t) Conversion::scaleTo16 (rand
),
491 (int64_t) baselineScale14To16 (rand
));
494 for (auto i
= 0; i
!= 100; ++i
)
496 const auto rand
= (uint8_t) random
.nextInt (0x80);
497 expectEquals ((int64_t) Conversion::scaleTo32 (rand
),
498 (int64_t) baselineScale7To32 (rand
));
501 expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x0000), (int64_t) 0x00000000);
502 expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x2000), (int64_t) 0x80000000);
503 expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x3fff), (int64_t) 0xffffffff);
505 for (auto i
= 0; i
!= 100; ++i
)
507 const auto rand
= (uint16_t) random
.nextInt (0x4000);
508 expectEquals ((int64_t) Conversion::scaleTo32 (rand
),
509 (int64_t) baselineScale14To32 (rand
));
513 beginTest ("Round-trip widening/narrowing conversions work");
515 for (auto i
= 0; i
!= 100; ++i
)
518 const auto rand
= (uint8_t) random
.nextInt (0x80);
519 expectEquals (Conversion::scaleTo7 (Conversion::scaleTo8 (rand
)), rand
);
523 const auto rand
= (uint8_t) random
.nextInt (0x80);
524 expectEquals (Conversion::scaleTo7 (Conversion::scaleTo16 (rand
)), rand
);
528 const auto rand
= (uint8_t) random
.nextInt (0x80);
529 expectEquals (Conversion::scaleTo7 (Conversion::scaleTo32 (rand
)), rand
);
533 const auto rand
= (uint16_t) random
.nextInt (0x4000);
534 expectEquals ((uint64_t) Conversion::scaleTo14 (Conversion::scaleTo16 (rand
)), (uint64_t) rand
);
538 const auto rand
= (uint16_t) random
.nextInt (0x4000);
539 expectEquals ((uint64_t) Conversion::scaleTo14 (Conversion::scaleTo32 (rand
)), (uint64_t) rand
);
544 beginTest ("MIDI 2 -> 1 note on conversions");
548 midi2
.add (PacketX2
{ 0x41946410, 0x12345678 });
551 midi1
.add (PacketX1
{ 0x21946409 });
553 checkMidi2ToMidi1Conversion (midi2
, midi1
);
557 // If the velocity is close to 0, the output velocity should still be 1
559 midi2
.add (PacketX2
{ 0x4295327f, 0x00345678 });
562 midi1
.add (PacketX1
{ 0x22953201 });
564 checkMidi2ToMidi1Conversion (midi2
, midi1
);
568 beginTest ("MIDI 2 -> 1 note off conversion");
571 midi2
.add (PacketX2
{ 0x448b0520, 0xfedcba98 });
574 midi1
.add (PacketX1
{ 0x248b057f });
576 checkMidi2ToMidi1Conversion (midi2
, midi1
);
579 beginTest ("MIDI 2 -> 1 poly pressure conversion");
582 midi2
.add (PacketX2
{ 0x49af0520, 0x80dcba98 });
585 midi1
.add (PacketX1
{ 0x29af0540 });
587 checkMidi2ToMidi1Conversion (midi2
, midi1
);
590 beginTest ("MIDI 2 -> 1 control change conversion");
593 midi2
.add (PacketX2
{ 0x49b00520, 0x80dcba98 });
596 midi1
.add (PacketX1
{ 0x29b00540 });
598 checkMidi2ToMidi1Conversion (midi2
, midi1
);
601 beginTest ("MIDI 2 -> 1 channel pressure conversion");
604 midi2
.add (PacketX2
{ 0x40d20520, 0x80dcba98 });
607 midi1
.add (PacketX1
{ 0x20d24000 });
609 checkMidi2ToMidi1Conversion (midi2
, midi1
);
612 beginTest ("MIDI 2 -> 1 nrpn rpn conversion");
616 midi2
.add (PacketX2
{ 0x44240123, 0x456789ab });
619 midi1
.add (PacketX1
{ 0x24b46501 });
620 midi1
.add (PacketX1
{ 0x24b46423 });
621 midi1
.add (PacketX1
{ 0x24b40622 });
622 midi1
.add (PacketX1
{ 0x24b42659 });
624 checkMidi2ToMidi1Conversion (midi2
, midi1
);
629 midi2
.add (PacketX2
{ 0x48347f7f, 0xffffffff });
632 midi1
.add (PacketX1
{ 0x28b4637f });
633 midi1
.add (PacketX1
{ 0x28b4627f });
634 midi1
.add (PacketX1
{ 0x28b4067f });
635 midi1
.add (PacketX1
{ 0x28b4267f });
637 checkMidi2ToMidi1Conversion (midi2
, midi1
);
641 beginTest ("MIDI 2 -> 1 program change and bank select conversion");
644 // If the bank valid bit is 0, just emit a program change
646 midi2
.add (PacketX2
{ 0x4cc10000, 0x70004020 });
649 midi1
.add (PacketX1
{ 0x2cc17000 });
651 checkMidi2ToMidi1Conversion (midi2
, midi1
);
655 // If the bank valid bit is 1, emit bank select control changes and a program change
657 midi2
.add (PacketX2
{ 0x4bc20001, 0x70004020 });
660 midi1
.add (PacketX1
{ 0x2bb20040 });
661 midi1
.add (PacketX1
{ 0x2bb22020 });
662 midi1
.add (PacketX1
{ 0x2bc27000 });
664 checkMidi2ToMidi1Conversion (midi2
, midi1
);
668 beginTest ("MIDI 2 -> 1 pitch bend conversion");
671 midi2
.add (PacketX2
{ 0x4eee0000, 0x12340000 });
674 midi1
.add (PacketX1
{ 0x2eee0d09 });
676 checkMidi2ToMidi1Conversion (midi2
, midi1
);
679 beginTest ("MIDI 2 -> 1 messages which don't convert");
681 const uint8_t opcodes
[] { 0x0, 0x1, 0x4, 0x5, 0x6, 0xf };
683 for (const auto opcode
: opcodes
)
686 midi2
.add (PacketX2
{ Utils::bytesToWord (0x40, (uint8_t) (opcode
<< 0x4), 0, 0), 0x0 });
687 checkMidi2ToMidi1Conversion (midi2
, {});
691 beginTest ("MIDI 2 -> 1 messages which are passed through");
693 const uint8_t typecodesX1
[] { 0x0, 0x1, 0x2 };
695 for (const auto typecode
: typecodesX1
)
698 p
.add (PacketX1
{ (uint32_t) ((int64_t) typecode
<< 0x1c | (random
.nextInt64() & 0xffffff)) });
700 checkMidi2ToMidi1Conversion (p
, p
);
705 p
.add (PacketX2
{ (uint32_t) (0x3 << 0x1c | (random
.nextInt64() & 0xffffff)),
706 (uint32_t) (random
.nextInt64() & 0xffffffff) });
708 checkMidi2ToMidi1Conversion (p
, p
);
713 p
.add (PacketX4
{ (uint32_t) (0x5 << 0x1c | (random
.nextInt64() & 0xffffff)),
714 (uint32_t) (random
.nextInt64() & 0xffffffff),
715 (uint32_t) (random
.nextInt64() & 0xffffffff),
716 (uint32_t) (random
.nextInt64() & 0xffffffff) });
718 checkMidi2ToMidi1Conversion (p
, p
);
722 beginTest ("MIDI 2 -> 1 control changes which should be ignored");
724 const uint8_t CCs
[] { 6, 38, 98, 99, 100, 101, 0, 32 };
726 for (const auto cc
: CCs
)
729 midi2
.add (PacketX2
{ (uint32_t) (0x40b00000 | (cc
<< 0x8)), 0x00000000 });
731 checkMidi2ToMidi1Conversion (midi2
, {});
735 beginTest ("MIDI 1 -> 2 note on conversions");
739 midi1
.add (PacketX1
{ 0x20904040 });
742 midi2
.add (PacketX2
{ 0x40904000, static_cast<uint32_t> (Conversion::scaleTo16 (0x40_u
8)) << 0x10 });
744 checkMidi1ToMidi2Conversion (midi1
, midi2
);
747 // If velocity is 0, convert to a note-off
750 midi1
.add (PacketX1
{ 0x23935100 });
753 midi2
.add (PacketX2
{ 0x43835100, 0x0 });
755 checkMidi1ToMidi2Conversion (midi1
, midi2
);
759 beginTest ("MIDI 1 -> 2 note off conversions");
762 midi1
.add (PacketX1
{ 0x21831020 });
765 midi2
.add (PacketX2
{ 0x41831000, static_cast<uint32_t> (Conversion::scaleTo16 (0x20_u
8)) << 0x10 });
767 checkMidi1ToMidi2Conversion (midi1
, midi2
);
770 beginTest ("MIDI 1 -> 2 poly pressure conversions");
773 midi1
.add (PacketX1
{ 0x20af7330 });
776 midi2
.add (PacketX2
{ 0x40af7300, Conversion::scaleTo32 (0x30_u
8) });
778 checkMidi1ToMidi2Conversion (midi1
, midi2
);
781 beginTest ("individual MIDI 1 -> 2 control changes which should be ignored");
783 const uint8_t CCs
[] { 6, 38, 98, 99, 100, 101, 0, 32 };
785 for (const auto cc
: CCs
)
788 midi1
.add (PacketX1
{ Utils::bytesToWord (0x20, 0xb0, cc
, 0x00) });
790 checkMidi1ToMidi2Conversion (midi1
, {});
794 beginTest ("MIDI 1 -> 2 control change conversions");
796 // normal control change
799 midi1
.add (PacketX1
{ 0x29b1017f });
802 midi2
.add (PacketX2
{ 0x49b10100, Conversion::scaleTo32 (0x7f_u
8) });
804 checkMidi1ToMidi2Conversion (midi1
, midi2
);
810 midi1
.add (PacketX1
{ 0x20b06301 });
811 midi1
.add (PacketX1
{ 0x20b06223 });
812 midi1
.add (PacketX1
{ 0x20b00645 });
813 midi1
.add (PacketX1
{ 0x20b02667 });
816 midi2
.add (PacketX2
{ 0x40300123, Conversion::scaleTo32 (static_cast<uint16_t> ((0x45 << 7) | 0x67)) });
818 checkMidi1ToMidi2Conversion (midi1
, midi2
);
824 midi1
.add (PacketX1
{ 0x20b06543 });
825 midi1
.add (PacketX1
{ 0x20b06421 });
826 midi1
.add (PacketX1
{ 0x20b00601 });
827 midi1
.add (PacketX1
{ 0x20b02623 });
830 midi2
.add (PacketX2
{ 0x40204321, Conversion::scaleTo32 (static_cast<uint16_t> ((0x01 << 7) | 0x23)) });
832 checkMidi1ToMidi2Conversion (midi1
, midi2
);
836 beginTest ("MIDI 1 -> MIDI 2 program change and bank select");
839 // program change with bank
840 midi1
.add (PacketX1
{ 0x2bb20030 });
841 midi1
.add (PacketX1
{ 0x2bb22010 });
842 midi1
.add (PacketX1
{ 0x2bc24000 });
843 // program change without bank (different group and channel)
844 midi1
.add (PacketX1
{ 0x20c01000 });
847 midi2
.add (PacketX2
{ 0x4bc20001, 0x40003010 });
848 midi2
.add (PacketX2
{ 0x40c00000, 0x10000000 });
850 checkMidi1ToMidi2Conversion (midi1
, midi2
);
853 beginTest ("MIDI 1 -> MIDI 2 channel pressure conversions");
856 midi1
.add (PacketX1
{ 0x20df3000 });
859 midi2
.add (PacketX2
{ 0x40df0000, Conversion::scaleTo32 (0x30_u
8) });
861 checkMidi1ToMidi2Conversion (midi1
, midi2
);
864 beginTest ("MIDI 1 -> MIDI 2 pitch bend conversions");
867 midi1
.add (PacketX1
{ 0x20e74567 });
870 midi2
.add (PacketX2
{ 0x40e70000, Conversion::scaleTo32 (static_cast<uint16_t> ((0x67 << 7) | 0x45)) });
872 checkMidi1ToMidi2Conversion (midi1
, midi2
);
877 static Packets
convertMidi2ToMidi1 (const Packets
& midi2
)
881 for (const auto& packet
: midi2
)
882 Conversion::midi2ToMidi1DefaultTranslation (packet
, [&r
] (const View
& v
) { r
.add (v
); });
887 static Packets
convertMidi1ToMidi2 (const Packets
& midi1
)
890 Midi1ToMidi2DefaultTranslator translator
;
892 for (const auto& packet
: midi1
)
893 translator
.dispatch (packet
, [&r
] (const View
& v
) { r
.add (v
); });
898 void checkBytestreamConversion (const Packets
& actual
, const Packets
& expected
)
900 expectEquals ((int) actual
.size(), (int) expected
.size());
902 if (actual
.size() != expected
.size())
905 auto actualPtr
= actual
.data();
907 std::for_each (expected
.data(),
908 expected
.data() + expected
.size(),
909 [&] (const uint32_t word
) { expectEquals ((uint64_t) *actualPtr
++, (uint64_t) word
); });
912 void checkMidi2ToMidi1Conversion (const Packets
& midi2
, const Packets
& expected
)
914 checkBytestreamConversion (convertMidi2ToMidi1 (midi2
), expected
);
917 void checkMidi1ToMidi2Conversion (const Packets
& midi1
, const Packets
& expected
)
919 checkBytestreamConversion (convertMidi1ToMidi2 (midi1
), expected
);
922 MidiMessage
createRandomSysEx (Random
& random
, size_t sysExBytes
)
924 std::vector
<uint8_t> data
;
925 data
.reserve (sysExBytes
);
927 for (size_t i
= 0; i
!= sysExBytes
; ++i
)
928 data
.push_back (uint8_t (random
.nextInt (0x80)));
930 return MidiMessage::createSysExMessage (data
.data(), int (data
.size()));
933 PacketX1
createRandomUtilityUMP (Random
& random
)
935 const auto status
= random
.nextInt (3);
937 return PacketX1
{ Utils::bytesToWord (0,
938 uint8_t (status
<< 0x4),
939 uint8_t (status
== 0 ? 0 : random
.nextInt (0x100)),
940 uint8_t (status
== 0 ? 0 : random
.nextInt (0x100))) };
943 PacketX1
createRandomRealtimeUMP (Random
& random
)
945 const auto status
= [&]
947 switch (random
.nextInt (6))
961 return PacketX1
{ Utils::bytesToWord (0x10, uint8_t (status
), 0x00, 0x00) };
964 template <typename Fn
>
965 void forEachNonSysExTestMessage (Random
& random
, Fn
&& fn
)
967 for (uint16_t counter
= 0x80; counter
!= 0x100; ++counter
)
969 const auto firstByte
= (uint8_t) counter
;
971 if (firstByte
== 0xf0 || firstByte
== 0xf7)
972 continue; // sysEx is tested separately
974 const auto length
= MidiMessage::getMessageLengthFromFirstByte (firstByte
);
975 const auto getDataByte
= [&] { return uint8_t (random
.nextInt (256) & 0x7f); };
977 const auto message
= [&]
981 case 1: return MidiMessage (firstByte
);
982 case 2: return MidiMessage (firstByte
, getDataByte());
983 case 3: return MidiMessage (firstByte
, getDataByte(), getDataByte());
986 return MidiMessage();
993 #if JUCE_WINDOWS && ! JUCE_MINGW
994 #define JUCE_CHECKED_ITERATOR(msg, size) \
995 stdext::checked_array_iterator<typename std::remove_reference<decltype (msg)>::type> ((msg), (size_t) (size))
997 #define JUCE_CHECKED_ITERATOR(msg, size) (msg)
1000 static bool equal (const MidiMessage
& a
, const MidiMessage
& b
) noexcept
1002 return a
.getRawDataSize() == b
.getRawDataSize()
1003 && std::equal (a
.getRawData(), a
.getRawData() + a
.getRawDataSize(),
1004 JUCE_CHECKED_ITERATOR (b
.getRawData(), b
.getRawDataSize()));
1007 #undef JUCE_CHECKED_ITERATOR
1009 static bool equal (const MidiBuffer
& a
, const MidiBuffer
& b
) noexcept
1011 return a
.data
== b
.data
;
1015 static UniversalMidiPacketTests universalMidiPacketTests
;