diff --git a/modules/juce_audio_basics/juce_audio_basics.cpp b/modules/juce_audio_basics/juce_audio_basics.cpp index 3d0ab44da8..3d8bc76220 100644 --- a/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/modules/juce_audio_basics/juce_audio_basics.cpp @@ -95,5 +95,5 @@ #if JUCE_UNIT_TESTS #include "utilities/juce_ADSR_test.cpp" - #include "midi/ump/juce_UMPTests.cpp" + #include "midi/ump/juce_UMP_test.cpp" #endif diff --git a/modules/juce_audio_basics/midi/ump/juce_UMPTests.cpp b/modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp similarity index 100% rename from modules/juce_audio_basics/midi/ump/juce_UMPTests.cpp rename to modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp diff --git a/modules/juce_audio_devices/midi_io/ump/juce_UMPTests.cpp b/modules/juce_audio_devices/midi_io/ump/juce_UMPTests.cpp deleted file mode 100644 index cc69b23c29..0000000000 --- a/modules/juce_audio_devices/midi_io/ump/juce_UMPTests.cpp +++ /dev/null @@ -1,1022 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2020 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ -namespace universal_midi_packets -{ - -#if JUCE_UNIT_TESTS - -constexpr uint8_t operator""_u8 (unsigned long long int i) { return static_cast (i); } -constexpr uint16_t operator""_u16 (unsigned long long int i) { return static_cast (i); } -constexpr uint32_t operator""_u32 (unsigned long long int i) { return static_cast (i); } -constexpr uint64_t operator""_u64 (unsigned long long int i) { return static_cast (i); } - -class UniversalMidiPacketTests : public UnitTest -{ -public: - UniversalMidiPacketTests() - : UnitTest ("Universal MIDI Packet", UnitTestCategories::midi) - { - } - - void runTest() override - { - auto random = getRandom(); - - beginTest ("Short bytestream midi messages can be round-tripped through the UMP converter"); - { - Midi1ToBytestreamTranslator translator (0); - - forEachNonSysExTestMessage (random, [&] (const MidiMessage& m) - { - Packets packets; - Conversion::toMidi1 (m, packets); - expect (packets.size() == 1); - - // Make sure that the message type is correct - expect (Utils::getMessageType (packets.data()[0]) == ((m.getRawData()[0] >> 0x4) == 0xf ? 0x1 : 0x2)); - - translator.dispatch (View {packets.data() }, - 0, - [&] (const MidiMessage& roundTripped) - { - expect (equal (m, roundTripped)); - }); - }); - } - - beginTest ("Bytestream SysEx converts to universal packets"); - { - { - // Zero length message - Packets packets; - Conversion::toMidi1 (createRandomSysEx (random, 0), packets); - expect (packets.size() == 2); - - expect (packets.data()[0] == 0x30000000); - expect (packets.data()[1] == 0x00000000); - } - - { - const auto message = createRandomSysEx (random, 1); - Packets packets; - Conversion::toMidi1 (message, packets); - expect (packets.size() == 2); - - const auto* sysEx = message.getSysExData(); - expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x01, sysEx[0], 0)); - expect (packets.data()[1] == 0x00000000); - } - - { - const auto message = createRandomSysEx (random, 6); - Packets packets; - Conversion::toMidi1 (message, packets); - expect (packets.size() == 2); - - const auto* sysEx = message.getSysExData(); - expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x06, sysEx[0], sysEx[1])); - expect (packets.data()[1] == Utils::bytesToWord (sysEx[2], sysEx[3], sysEx[4], sysEx[5])); - } - - { - const auto message = createRandomSysEx (random, 12); - Packets packets; - Conversion::toMidi1 (message, packets); - expect (packets.size() == 4); - - const auto* sysEx = message.getSysExData(); - expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x16, sysEx[0], sysEx[1])); - expect (packets.data()[1] == Utils::bytesToWord (sysEx[2], sysEx[3], sysEx[4], sysEx[5])); - expect (packets.data()[2] == Utils::bytesToWord (0x30, 0x36, sysEx[6], sysEx[7])); - expect (packets.data()[3] == Utils::bytesToWord (sysEx[8], sysEx[9], sysEx[10], sysEx[11])); - } - - { - const auto message = createRandomSysEx (random, 13); - Packets packets; - Conversion::toMidi1 (message, packets); - expect (packets.size() == 6); - - const auto* sysEx = message.getSysExData(); - expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x16, sysEx[0], sysEx[1])); - expect (packets.data()[1] == Utils::bytesToWord (sysEx[2], sysEx[3], sysEx[4], sysEx[5])); - expect (packets.data()[2] == Utils::bytesToWord (0x30, 0x26, sysEx[6], sysEx[7])); - expect (packets.data()[3] == Utils::bytesToWord (sysEx[8], sysEx[9], sysEx[10], sysEx[11])); - expect (packets.data()[4] == Utils::bytesToWord (0x30, 0x31, sysEx[12], 0)); - expect (packets.data()[5] == 0x00000000); - } - } - - ToBytestreamDispatcher converter (0); - Packets packets; - - const auto checkRoundTrip = [&] (const MidiBuffer& expected) - { - for (const auto meta : expected) - Conversion::toMidi1 (meta.getMessage(), packets); - - MidiBuffer output; - converter.dispatch (packets.data(), - packets.data() + packets.size(), - 0, - [&] (const MidiMessage& roundTripped) - { - output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); - }); - packets.clear(); - - expect (equal (expected, output)); - }; - - beginTest ("Long SysEx bytestream midi messages can be round-tripped through the UMP converter"); - { - for (auto length : { 0, 1, 2, 3, 4, 5, 6, 7, 13, 20, 100, 1000 }) - { - MidiBuffer expected; - expected.addEvent (createRandomSysEx (random, size_t (length)), 0); - checkRoundTrip (expected); - } - } - - beginTest ("UMP SysEx7 messages interspersed with utility messages convert to bytestream"); - { - const auto sysEx = createRandomSysEx (random, 100); - Packets originalPackets; - Conversion::toMidi1 (sysEx, originalPackets); - - Packets modifiedPackets; - - const auto addRandomUtilityUMP = [&] - { - const auto newPacket = createRandomUtilityUMP (random); - modifiedPackets.add (View (newPacket.data())); - }; - - for (const auto& packet : originalPackets) - { - addRandomUtilityUMP(); - modifiedPackets.add (packet); - addRandomUtilityUMP(); - } - - MidiBuffer output; - converter.dispatch (modifiedPackets.data(), - modifiedPackets.data() + modifiedPackets.size(), - 0, - [&] (const MidiMessage& roundTripped) - { - output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); - }); - - // All Utility messages should have been ignored - expect (output.getNumEvents() == 1); - - for (const auto meta : output) - expect (equal (meta.getMessage(), sysEx)); - } - - beginTest ("UMP SysEx7 messages interspersed with System Realtime messages convert to bytestream"); - { - const auto sysEx = createRandomSysEx (random, 200); - Packets originalPackets; - Conversion::toMidi1 (sysEx, originalPackets); - - Packets modifiedPackets; - MidiBuffer realtimeMessages; - - const auto addRandomRealtimeUMP = [&] - { - const auto newPacket = createRandomRealtimeUMP (random); - modifiedPackets.add (View (newPacket.data())); - realtimeMessages.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket), 0); - }; - - for (const auto& packet : originalPackets) - { - addRandomRealtimeUMP(); - modifiedPackets.add (packet); - addRandomRealtimeUMP(); - } - - MidiBuffer output; - converter.dispatch (modifiedPackets.data(), - modifiedPackets.data() + modifiedPackets.size(), - 0, - [&] (const MidiMessage& roundTripped) - { - output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); - }); - - const auto numOutputs = output.getNumEvents(); - const auto numInputs = realtimeMessages.getNumEvents(); - expect (numOutputs == numInputs + 1); - - if (numOutputs == numInputs + 1) - { - const auto isMetadataEquivalent = [] (const MidiMessageMetadata& a, - const MidiMessageMetadata& b) - { - return equal (a.getMessage(), b.getMessage()); - }; - - auto it = output.begin(); - - for (const auto meta : realtimeMessages) - { - if (! isMetadataEquivalent (*it, meta)) - { - expect (equal ((*it).getMessage(), sysEx)); - ++it; - } - - expect (isMetadataEquivalent (*it, meta)); - ++it; - } - } - } - - beginTest ("UMP SysEx7 messages interspersed with System Realtime and Utility messages convert to bytestream"); - { - const auto sysEx = createRandomSysEx (random, 300); - Packets originalPackets; - Conversion::toMidi1 (sysEx, originalPackets); - - Packets modifiedPackets; - MidiBuffer realtimeMessages; - - const auto addRandomRealtimeUMP = [&] - { - const auto newPacket = createRandomRealtimeUMP (random); - modifiedPackets.add (View (newPacket.data())); - realtimeMessages.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket), 0); - }; - - const auto addRandomUtilityUMP = [&] - { - const auto newPacket = createRandomUtilityUMP (random); - modifiedPackets.add (View (newPacket.data())); - }; - - for (const auto& packet : originalPackets) - { - addRandomRealtimeUMP(); - addRandomUtilityUMP(); - modifiedPackets.add (packet); - addRandomRealtimeUMP(); - addRandomUtilityUMP(); - } - - MidiBuffer output; - converter.dispatch (modifiedPackets.data(), - modifiedPackets.data() + modifiedPackets.size(), - 0, - [&] (const MidiMessage& roundTripped) - { - output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); - }); - - const auto numOutputs = output.getNumEvents(); - const auto numInputs = realtimeMessages.getNumEvents(); - expect (numOutputs == numInputs + 1); - - if (numOutputs == numInputs + 1) - { - const auto isMetadataEquivalent = [] (const MidiMessageMetadata& a, const MidiMessageMetadata& b) - { - return equal (a.getMessage(), b.getMessage()); - }; - - auto it = output.begin(); - - for (const auto meta : realtimeMessages) - { - if (! isMetadataEquivalent (*it, meta)) - { - expect (equal ((*it).getMessage(), sysEx)); - ++it; - } - - expect (isMetadataEquivalent (*it, meta)); - ++it; - } - } - } - - beginTest ("SysEx messages are terminated by non-Utility, non-Realtime messages"); - { - const auto noteOn = [&] - { - MidiBuffer b; - b.addEvent (MidiMessage::noteOn (1, uint8_t (64), uint8_t (64)), 0); - return b; - }(); - - const auto noteOnPackets = [&] - { - Packets p; - - for (const auto meta : noteOn) - Conversion::toMidi1 (meta.getMessage(), p); - - return p; - }(); - - const auto sysEx = createRandomSysEx (random, 300); - - const auto originalPackets = [&] - { - Packets p; - Conversion::toMidi1 (sysEx, p); - return p; - }(); - - const auto modifiedPackets = [&] - { - Packets p; - - const auto insertionPoint = std::next (originalPackets.begin(), 10); - std::for_each (originalPackets.begin(), - insertionPoint, - [&] (const View& view) { p.add (view); }); - - for (const auto& view : noteOnPackets) - p.add (view); - - std::for_each (insertionPoint, - originalPackets.end(), - [&] (const View& view) { p.add (view); }); - - return p; - }(); - - // modifiedPackets now contains some SysEx packets interrupted by a MIDI 1 noteOn - - MidiBuffer output; - - const auto pushToOutput = [&] (const Packets& p) - { - converter.dispatch (p.data(), - p.data() + p.size(), - 0, - [&] (const MidiMessage& roundTripped) - { - output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); - }); - }; - - pushToOutput (modifiedPackets); - - // Interrupted sysEx shouldn't be present - expect (equal (output, noteOn)); - - const auto newSysEx = createRandomSysEx (random, 300); - Packets newSysExPackets; - Conversion::toMidi1 (newSysEx, newSysExPackets); - - // If we push another midi event without interrupting it, - // it should get through without being modified, - // and it shouldn't be affected by the previous (interrupted) sysex. - - output.clear(); - pushToOutput (newSysExPackets); - - expect (output.getNumEvents() == 1); - - for (const auto meta : output) - expect (equal (meta.getMessage(), newSysEx)); - } - - beginTest ("Widening conversions work"); - { - // This is similar to the 'slow' example code from the MIDI 2.0 spec - const auto baselineScale = [] (uint32_t srcVal, uint32_t srcBits, uint32_t dstBits) - { - const auto scaleBits = (uint32_t) (dstBits - srcBits); - - auto bitShiftedValue = (uint32_t) (srcVal << scaleBits); - - const auto srcCenter = (uint32_t) (1 << (srcBits - 1)); - - if (srcVal <= srcCenter) - return bitShiftedValue; - - const auto repeatBits = (uint32_t) (srcBits - 1); - const auto repeatMask = (uint32_t) ((1 << repeatBits) - 1); - - auto repeatValue = (uint32_t) (srcVal & repeatMask); - - if (scaleBits > repeatBits) - repeatValue <<= scaleBits - repeatBits; - else - repeatValue >>= repeatBits - scaleBits; - - while (repeatValue != 0) - { - bitShiftedValue |= repeatValue; - repeatValue >>= repeatBits; - } - - return bitShiftedValue; - }; - - const auto baselineScale7To8 = [&] (uint8_t in) - { - return baselineScale (in, 7, 8); - }; - - const auto baselineScale7To16 = [&] (uint8_t in) - { - return baselineScale (in, 7, 16); - }; - - const auto baselineScale14To16 = [&] (uint16_t in) - { - return baselineScale (in, 14, 16); - }; - - const auto baselineScale7To32 = [&] (uint8_t in) - { - return baselineScale (in, 7, 32); - }; - - const auto baselineScale14To32 = [&] (uint16_t in) - { - return baselineScale (in, 14, 32); - }; - - for (auto i = 0; i != 100; ++i) - { - const auto rand = (uint8_t) random.nextInt (0x80); - expectEquals ((int64_t) Conversion::scaleTo8 (rand), - (int64_t) baselineScale7To8 (rand)); - } - - expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x00), (int64_t) 0x0000); - expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x0a), (int64_t) 0x1400); - expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x40), (int64_t) 0x8000); - expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x57), (int64_t) 0xaeba); - expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x7f), (int64_t) 0xffff); - - for (auto i = 0; i != 100; ++i) - { - const auto rand = (uint8_t) random.nextInt (0x80); - expectEquals ((int64_t) Conversion::scaleTo16 (rand), - (int64_t) baselineScale7To16 (rand)); - } - - for (auto i = 0; i != 100; ++i) - { - const auto rand = (uint16_t) random.nextInt (0x4000); - expectEquals ((int64_t) Conversion::scaleTo16 (rand), - (int64_t) baselineScale14To16 (rand)); - } - - for (auto i = 0; i != 100; ++i) - { - const auto rand = (uint8_t) random.nextInt (0x80); - expectEquals ((int64_t) Conversion::scaleTo32 (rand), - (int64_t) baselineScale7To32 (rand)); - } - - expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x0000), (int64_t) 0x00000000); - expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x2000), (int64_t) 0x80000000); - expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x3fff), (int64_t) 0xffffffff); - - for (auto i = 0; i != 100; ++i) - { - const auto rand = (uint16_t) random.nextInt (0x4000); - expectEquals ((int64_t) Conversion::scaleTo32 (rand), - (int64_t) baselineScale14To32 (rand)); - } - } - - beginTest ("Round-trip widening/narrowing conversions work"); - { - for (auto i = 0; i != 100; ++i) - { - { - const auto rand = (uint8_t) random.nextInt (0x80); - expectEquals (Conversion::scaleTo7 (Conversion::scaleTo8 (rand)), rand); - } - - { - const auto rand = (uint8_t) random.nextInt (0x80); - expectEquals (Conversion::scaleTo7 (Conversion::scaleTo16 (rand)), rand); - } - - { - const auto rand = (uint8_t) random.nextInt (0x80); - expectEquals (Conversion::scaleTo7 (Conversion::scaleTo32 (rand)), rand); - } - - { - const auto rand = (uint16_t) random.nextInt (0x4000); - expectEquals ((uint64_t) Conversion::scaleTo14 (Conversion::scaleTo16 (rand)), (uint64_t) rand); - } - - { - const auto rand = (uint16_t) random.nextInt (0x4000); - expectEquals ((uint64_t) Conversion::scaleTo14 (Conversion::scaleTo32 (rand)), (uint64_t) rand); - } - } - } - - beginTest ("MIDI 2 -> 1 note on conversions"); - { - { - Packets midi2; - midi2.add (PacketX2 { 0x41946410, 0x12345678 }); - - Packets midi1; - midi1.add (PacketX1 { 0x21946409 }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - - { - // If the velocity is close to 0, the output velocity should still be 1 - Packets midi2; - midi2.add (PacketX2 { 0x4295327f, 0x00345678 }); - - Packets midi1; - midi1.add (PacketX1 { 0x22953201 }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - } - - beginTest ("MIDI 2 -> 1 note off conversion"); - { - Packets midi2; - midi2.add (PacketX2 { 0x448b0520, 0xfedcba98 }); - - Packets midi1; - midi1.add (PacketX1 { 0x248b057f }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - - beginTest ("MIDI 2 -> 1 poly pressure conversion"); - { - Packets midi2; - midi2.add (PacketX2 { 0x49af0520, 0x80dcba98 }); - - Packets midi1; - midi1.add (PacketX1 { 0x29af0540 }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - - beginTest ("MIDI 2 -> 1 control change conversion"); - { - Packets midi2; - midi2.add (PacketX2 { 0x49b00520, 0x80dcba98 }); - - Packets midi1; - midi1.add (PacketX1 { 0x29b00540 }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - - beginTest ("MIDI 2 -> 1 channel pressure conversion"); - { - Packets midi2; - midi2.add (PacketX2 { 0x40d20520, 0x80dcba98 }); - - Packets midi1; - midi1.add (PacketX1 { 0x20d24000 }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - - beginTest ("MIDI 2 -> 1 nrpn rpn conversion"); - { - { - Packets midi2; - midi2.add (PacketX2 { 0x44240123, 0x456789ab }); - - Packets midi1; - midi1.add (PacketX1 { 0x24b46501 }); - midi1.add (PacketX1 { 0x24b46423 }); - midi1.add (PacketX1 { 0x24b40622 }); - midi1.add (PacketX1 { 0x24b42659 }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - - { - Packets midi2; - midi2.add (PacketX2 { 0x48347f7f, 0xffffffff }); - - Packets midi1; - midi1.add (PacketX1 { 0x28b4637f }); - midi1.add (PacketX1 { 0x28b4627f }); - midi1.add (PacketX1 { 0x28b4067f }); - midi1.add (PacketX1 { 0x28b4267f }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - } - - beginTest ("MIDI 2 -> 1 program change and bank select conversion"); - { - { - // If the bank valid bit is 0, just emit a program change - Packets midi2; - midi2.add (PacketX2 { 0x4cc10000, 0x70004020 }); - - Packets midi1; - midi1.add (PacketX1 { 0x2cc17000 }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - - { - // If the bank valid bit is 1, emit bank select control changes and a program change - Packets midi2; - midi2.add (PacketX2 { 0x4bc20001, 0x70004020 }); - - Packets midi1; - midi1.add (PacketX1 { 0x2bb20040 }); - midi1.add (PacketX1 { 0x2bb22020 }); - midi1.add (PacketX1 { 0x2bc27000 }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - } - - beginTest ("MIDI 2 -> 1 pitch bend conversion"); - { - Packets midi2; - midi2.add (PacketX2 { 0x4eee0000, 0x12340000 }); - - Packets midi1; - midi1.add (PacketX1 { 0x2eee0d09 }); - - checkMidi2ToMidi1Conversion (midi2, midi1); - } - - beginTest ("MIDI 2 -> 1 messages which don't convert"); - { - const uint8_t opcodes[] { 0x0, 0x1, 0x4, 0x5, 0x6, 0xf }; - - for (const auto opcode : opcodes) - { - Packets midi2; - midi2.add (PacketX2 { Utils::bytesToWord (0x40, (uint8_t) (opcode << 0x4), 0, 0), 0x0 }); - checkMidi2ToMidi1Conversion (midi2, {}); - } - } - - beginTest ("MIDI 2 -> 1 messages which are passed through"); - { - const uint8_t typecodesX1[] { 0x0, 0x1, 0x2 }; - - for (const auto typecode : typecodesX1) - { - Packets p; - p.add (PacketX1 { (uint32_t) ((int64_t) typecode << 0x1c | (random.nextInt64() & 0xffffff)) }); - - checkMidi2ToMidi1Conversion (p, p); - } - - { - Packets p; - p.add (PacketX2 { (uint32_t) (0x3 << 0x1c | (random.nextInt64() & 0xffffff)), - (uint32_t) (random.nextInt64() & 0xffffffff) }); - - checkMidi2ToMidi1Conversion (p, p); - } - - { - Packets p; - p.add (PacketX4 { (uint32_t) (0x5 << 0x1c | (random.nextInt64() & 0xffffff)), - (uint32_t) (random.nextInt64() & 0xffffffff), - (uint32_t) (random.nextInt64() & 0xffffffff), - (uint32_t) (random.nextInt64() & 0xffffffff) }); - - checkMidi2ToMidi1Conversion (p, p); - } - } - - beginTest ("MIDI 2 -> 1 control changes which should be ignored"); - { - const uint8_t CCs[] { 6, 38, 98, 99, 100, 101, 0, 32 }; - - for (const auto cc : CCs) - { - Packets midi2; - midi2.add (PacketX2 { (uint32_t) (0x40b00000 | (cc << 0x8)), 0x00000000 }); - - checkMidi2ToMidi1Conversion (midi2, {}); - } - } - - beginTest ("MIDI 1 -> 2 note on conversions"); - { - { - Packets midi1; - midi1.add (PacketX1 { 0x20904040 }); - - Packets midi2; - midi2.add (PacketX2 { 0x40904000, static_cast (Conversion::scaleTo16 (0x40_u8)) << 0x10 }); - - checkMidi1ToMidi2Conversion (midi1, midi2); - } - - // If velocity is 0, convert to a note-off - { - Packets midi1; - midi1.add (PacketX1 { 0x23935100 }); - - Packets midi2; - midi2.add (PacketX2 { 0x43835100, 0x0 }); - - checkMidi1ToMidi2Conversion (midi1, midi2); - } - } - - beginTest ("MIDI 1 -> 2 note off conversions"); - { - Packets midi1; - midi1.add (PacketX1 { 0x21831020 }); - - Packets midi2; - midi2.add (PacketX2 { 0x41831000, static_cast (Conversion::scaleTo16 (0x20_u8)) << 0x10 }); - - checkMidi1ToMidi2Conversion (midi1, midi2); - } - - beginTest ("MIDI 1 -> 2 poly pressure conversions"); - { - Packets midi1; - midi1.add (PacketX1 { 0x20af7330 }); - - Packets midi2; - midi2.add (PacketX2 { 0x40af7300, Conversion::scaleTo32 (0x30_u8) }); - - checkMidi1ToMidi2Conversion (midi1, midi2); - } - - beginTest ("individual MIDI 1 -> 2 control changes which should be ignored"); - { - const uint8_t CCs[] { 6, 38, 98, 99, 100, 101, 0, 32 }; - - for (const auto cc : CCs) - { - Packets midi1; - midi1.add (PacketX1 { Utils::bytesToWord (0x20, 0xb0, cc, 0x00) }); - - checkMidi1ToMidi2Conversion (midi1, {}); - } - } - - beginTest ("MIDI 1 -> 2 control change conversions"); - { - // normal control change - { - Packets midi1; - midi1.add (PacketX1 { 0x29b1017f }); - - Packets midi2; - midi2.add (PacketX2 { 0x49b10100, Conversion::scaleTo32 (0x7f_u8) }); - - checkMidi1ToMidi2Conversion (midi1, midi2); - } - - // nrpn - { - Packets midi1; - midi1.add (PacketX1 { 0x20b06301 }); - midi1.add (PacketX1 { 0x20b06223 }); - midi1.add (PacketX1 { 0x20b00645 }); - midi1.add (PacketX1 { 0x20b02667 }); - - Packets midi2; - midi2.add (PacketX2 { 0x40300123, Conversion::scaleTo32 (static_cast ((0x45 << 7) | 0x67)) }); - - checkMidi1ToMidi2Conversion (midi1, midi2); - } - - // rpn - { - Packets midi1; - midi1.add (PacketX1 { 0x20b06543 }); - midi1.add (PacketX1 { 0x20b06421 }); - midi1.add (PacketX1 { 0x20b00601 }); - midi1.add (PacketX1 { 0x20b02623 }); - - Packets midi2; - midi2.add (PacketX2 { 0x40204321, Conversion::scaleTo32 (static_cast ((0x01 << 7) | 0x23)) }); - - checkMidi1ToMidi2Conversion (midi1, midi2); - } - } - - beginTest ("MIDI 1 -> MIDI 2 program change and bank select"); - { - Packets midi1; - // program change with bank - midi1.add (PacketX1 { 0x2bb20030 }); - midi1.add (PacketX1 { 0x2bb22010 }); - midi1.add (PacketX1 { 0x2bc24000 }); - // program change without bank (different group and channel) - midi1.add (PacketX1 { 0x20c01000 }); - - Packets midi2; - midi2.add (PacketX2 { 0x4bc20001, 0x40003010 }); - midi2.add (PacketX2 { 0x40c00000, 0x10000000 }); - - checkMidi1ToMidi2Conversion (midi1, midi2); - } - - beginTest ("MIDI 1 -> MIDI 2 channel pressure conversions"); - { - Packets midi1; - midi1.add (PacketX1 { 0x20df3000 }); - - Packets midi2; - midi2.add (PacketX2 { 0x40df0000, Conversion::scaleTo32 (0x30_u8) }); - - checkMidi1ToMidi2Conversion (midi1, midi2); - } - - beginTest ("MIDI 1 -> MIDI 2 pitch bend conversions"); - { - Packets midi1; - midi1.add (PacketX1 { 0x20e74567 }); - - Packets midi2; - midi2.add (PacketX2 { 0x40e70000, Conversion::scaleTo32 (static_cast ((0x67 << 7) | 0x45)) }); - - checkMidi1ToMidi2Conversion (midi1, midi2); - } - } - -private: - static Packets convertMidi2ToMidi1 (const Packets& midi2) - { - Packets r; - - for (const auto& packet : midi2) - Conversion::midi2ToMidi1DefaultTranslation (packet, [&r] (const View& v) { r.add (v); }); - - return r; - } - - static Packets convertMidi1ToMidi2 (const Packets& midi1) - { - Packets r; - Midi1ToMidi2DefaultTranslator translator; - - for (const auto& packet : midi1) - translator.dispatch (packet, [&r] (const View& v) { r.add (v); }); - - return r; - } - - void checkBytestreamConversion (const Packets& actual, const Packets& expected) - { - expectEquals ((int) actual.size(), (int) expected.size()); - - if (actual.size() != expected.size()) - return; - - auto actualPtr = actual.data(); - - std::for_each (expected.data(), - expected.data() + expected.size(), - [&] (const uint32_t word) { expectEquals ((uint64_t) *actualPtr++, (uint64_t) word); }); - } - - void checkMidi2ToMidi1Conversion (const Packets& midi2, const Packets& expected) - { - checkBytestreamConversion (convertMidi2ToMidi1 (midi2), expected); - } - - void checkMidi1ToMidi2Conversion (const Packets& midi1, const Packets& expected) - { - checkBytestreamConversion (convertMidi1ToMidi2 (midi1), expected); - } - - MidiMessage createRandomSysEx (Random& random, size_t sysExBytes) - { - std::vector data; - data.reserve (sysExBytes); - - for (size_t i = 0; i != sysExBytes; ++i) - data.push_back (uint8_t (random.nextInt (0x80))); - - return MidiMessage::createSysExMessage (data.data(), int (data.size())); - } - - PacketX1 createRandomUtilityUMP (Random& random) - { - const auto status = random.nextInt (3); - - return PacketX1 { Utils::bytesToWord (0, - uint8_t (status << 0x4), - uint8_t (status == 0 ? 0 : random.nextInt (0x100)), - uint8_t (status == 0 ? 0 : random.nextInt (0x100))) }; - } - - PacketX1 createRandomRealtimeUMP (Random& random) - { - const auto status = [&] - { - switch (random.nextInt (6)) - { - case 0: return 0xf8; - case 1: return 0xfa; - case 2: return 0xfb; - case 3: return 0xfc; - case 4: return 0xfe; - case 5: return 0xff; - } - - jassertfalse; - return 0x00; - }(); - - return PacketX1 { Utils::bytesToWord (0x10, uint8_t (status), 0x00, 0x00) }; - } - - template - void forEachNonSysExTestMessage (Random& random, Fn&& fn) - { - for (uint16_t counter = 0x80; counter != 0x100; ++counter) - { - const auto firstByte = (uint8_t) counter; - - if (firstByte == 0xf0 || firstByte == 0xf7) - continue; // sysEx is tested separately - - const auto length = MidiMessage::getMessageLengthFromFirstByte (firstByte); - const auto getDataByte = [&] { return uint8_t (random.nextInt (256) & 0x7f); }; - - const auto message = [&] - { - switch (length) - { - case 1: return MidiMessage (firstByte); - case 2: return MidiMessage (firstByte, getDataByte()); - case 3: return MidiMessage (firstByte, getDataByte(), getDataByte()); - } - - return MidiMessage(); - }(); - - fn (message); - } - } - - #if JUCE_WINDOWS && ! JUCE_MINGW - #define JUCE_CHECKED_ITERATOR(msg, size) \ - stdext::checked_array_iterator::type> ((msg), (size_t) (size)) - #else - #define JUCE_CHECKED_ITERATOR(msg, size) (msg) - #endif - - static bool equal (const MidiMessage& a, const MidiMessage& b) noexcept - { - return a.getRawDataSize() == b.getRawDataSize() - && std::equal (a.getRawData(), a.getRawData() + a.getRawDataSize(), - JUCE_CHECKED_ITERATOR (b.getRawData(), b.getRawDataSize())); - } - - #undef JUCE_CHECKED_ITERATOR - - static bool equal (const MidiBuffer& a, const MidiBuffer& b) noexcept - { - return a.data == b.data; - } -}; - -static UniversalMidiPacketTests universalMidiPacketTests; - -#endif - -} -}