/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2017 - ROLI Ltd. 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 { MPEZoneLayout::MPEZoneLayout() noexcept {} MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) : lowerZone (other.lowerZone), upperZone (other.upperZone) { } MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) { lowerZone = other.lowerZone; upperZone = other.upperZone; sendLayoutChangeMessage(); return *this; } void MPEZoneLayout::sendLayoutChangeMessage() { listeners.call ([this] (Listener& l) { l.zoneLayoutChanged (*this); }); } //============================================================================== void MPEZoneLayout::setZone (bool isLower, int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept { checkAndLimitZoneParameters (0, 15, numMemberChannels); checkAndLimitZoneParameters (0, 96, perNotePitchbendRange); checkAndLimitZoneParameters (0, 96, masterPitchbendRange); if (isLower) lowerZone = { true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange }; else upperZone = { false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange }; if (numMemberChannels > 0) { auto totalChannels = lowerZone.numMemberChannels + upperZone.numMemberChannels; if (totalChannels >= 15) { if (isLower) upperZone.numMemberChannels = 14 - numMemberChannels; else lowerZone.numMemberChannels = 14 - numMemberChannels; } } sendLayoutChangeMessage(); } void MPEZoneLayout::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept { setZone (true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange); } void MPEZoneLayout::setUpperZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept { setZone (false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange); } void MPEZoneLayout::clearAllZones() { lowerZone = { true, 0 }; upperZone = { false, 0 }; sendLayoutChangeMessage(); } //============================================================================== void MPEZoneLayout::processNextMidiEvent (const MidiMessage& message) { if (! message.isController()) return; MidiRPNMessage rpn; if (rpnDetector.parseControllerMessage (message.getChannel(), message.getControllerNumber(), message.getControllerValue(), rpn)) { processRpnMessage (rpn); } } void MPEZoneLayout::processRpnMessage (MidiRPNMessage rpn) { if (rpn.parameterNumber == MPEMessages::zoneLayoutMessagesRpnNumber) processZoneLayoutRpnMessage (rpn); else if (rpn.parameterNumber == 0) processPitchbendRangeRpnMessage (rpn); } void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn) { if (rpn.value < 16) { if (rpn.channel == 1) setLowerZone (rpn.value); else if (rpn.channel == 16) setUpperZone (rpn.value); } } void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value) { if (zone.masterPitchbendRange != value) { checkAndLimitZoneParameters (0, 96, zone.masterPitchbendRange); zone.masterPitchbendRange = value; sendLayoutChangeMessage(); } } void MPEZoneLayout::updatePerNotePitchbendRange (Zone& zone, int value) { if (zone.perNotePitchbendRange != value) { checkAndLimitZoneParameters (0, 96, zone.perNotePitchbendRange); zone.perNotePitchbendRange = value; sendLayoutChangeMessage(); } } void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn) { if (rpn.channel == 1) { updateMasterPitchbend (lowerZone, rpn.value); } else if (rpn.channel == 16) { updateMasterPitchbend (upperZone, rpn.value); } else { if (lowerZone.isUsingChannelAsMemberChannel (rpn.channel)) updatePerNotePitchbendRange (lowerZone, rpn.value); else if (upperZone.isUsingChannelAsMemberChannel (rpn.channel)) updatePerNotePitchbendRange (upperZone, rpn.value); } } void MPEZoneLayout::processNextMidiBuffer (const MidiBuffer& buffer) { for (const auto metadata : buffer) processNextMidiEvent (metadata.getMessage()); } //============================================================================== void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept { listeners.add (listenerToAdd); } void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept { listeners.remove (listenerToRemove); } //============================================================================== void MPEZoneLayout::checkAndLimitZoneParameters (int minValue, int maxValue, int& valueToCheckAndLimit) noexcept { if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue) { // if you hit this, one of the parameters you supplied for this zone // was not within the allowed range! // we fit this back into the allowed range here to maintain a valid // state for the zone, but probably the resulting zone is not what you // wanted it to be! jassertfalse; valueToCheckAndLimit = jlimit (minValue, maxValue, valueToCheckAndLimit); } } //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS class MPEZoneLayoutTests : public UnitTest { public: MPEZoneLayoutTests() : UnitTest ("MPEZoneLayout class", UnitTestCategories::midi) {} void runTest() override { beginTest ("initialisation"); { MPEZoneLayout layout; expect (! layout.getLowerZone().isActive()); expect (! layout.getUpperZone().isActive()); } beginTest ("adding zones"); { MPEZoneLayout layout; layout.setLowerZone (7); expect (layout.getLowerZone().isActive()); expect (! layout.getUpperZone().isActive()); expectEquals (layout.getLowerZone().getMasterChannel(), 1); expectEquals (layout.getLowerZone().numMemberChannels, 7); layout.setUpperZone (7); expect (layout.getLowerZone().isActive()); expect (layout.getUpperZone().isActive()); expectEquals (layout.getLowerZone().getMasterChannel(), 1); expectEquals (layout.getLowerZone().numMemberChannels, 7); expectEquals (layout.getUpperZone().getMasterChannel(), 16); expectEquals (layout.getUpperZone().numMemberChannels, 7); layout.setLowerZone (3); expect (layout.getLowerZone().isActive()); expect (layout.getUpperZone().isActive()); expectEquals (layout.getLowerZone().getMasterChannel(), 1); expectEquals (layout.getLowerZone().numMemberChannels, 3); expectEquals (layout.getUpperZone().getMasterChannel(), 16); expectEquals (layout.getUpperZone().numMemberChannels, 7); layout.setUpperZone (3); expect (layout.getLowerZone().isActive()); expect (layout.getUpperZone().isActive()); expectEquals (layout.getLowerZone().getMasterChannel(), 1); expectEquals (layout.getLowerZone().numMemberChannels, 3); expectEquals (layout.getUpperZone().getMasterChannel(), 16); expectEquals (layout.getUpperZone().numMemberChannels, 3); layout.setLowerZone (15); expect (layout.getLowerZone().isActive()); expect (! layout.getUpperZone().isActive()); expectEquals (layout.getLowerZone().getMasterChannel(), 1); expectEquals (layout.getLowerZone().numMemberChannels, 15); } beginTest ("clear all zones"); { MPEZoneLayout layout; expect (! layout.getLowerZone().isActive()); expect (! layout.getUpperZone().isActive()); layout.setLowerZone (7); layout.setUpperZone (2); expect (layout.getLowerZone().isActive()); expect (layout.getUpperZone().isActive()); layout.clearAllZones(); expect (! layout.getLowerZone().isActive()); expect (! layout.getUpperZone().isActive()); } beginTest ("process MIDI buffers"); { MPEZoneLayout layout; MidiBuffer buffer; buffer = MPEMessages::setLowerZone (7); layout.processNextMidiBuffer (buffer); expect (layout.getLowerZone().isActive()); expect (! layout.getUpperZone().isActive()); expectEquals (layout.getLowerZone().getMasterChannel(), 1); expectEquals (layout.getLowerZone().numMemberChannels, 7); buffer = MPEMessages::setUpperZone (7); layout.processNextMidiBuffer (buffer); expect (layout.getLowerZone().isActive()); expect (layout.getUpperZone().isActive()); expectEquals (layout.getLowerZone().getMasterChannel(), 1); expectEquals (layout.getLowerZone().numMemberChannels, 7); expectEquals (layout.getUpperZone().getMasterChannel(), 16); expectEquals (layout.getUpperZone().numMemberChannels, 7); { buffer = MPEMessages::setLowerZone (10); layout.processNextMidiBuffer (buffer); expect (layout.getLowerZone().isActive()); expect (layout.getUpperZone().isActive()); expectEquals (layout.getLowerZone().getMasterChannel(), 1); expectEquals (layout.getLowerZone().numMemberChannels, 10); expectEquals (layout.getUpperZone().getMasterChannel(), 16); expectEquals (layout.getUpperZone().numMemberChannels, 4); buffer = MPEMessages::setLowerZone (10, 33, 44); layout.processNextMidiBuffer (buffer); expectEquals (layout.getLowerZone().numMemberChannels, 10); expectEquals (layout.getLowerZone().perNotePitchbendRange, 33); expectEquals (layout.getLowerZone().masterPitchbendRange, 44); } { buffer = MPEMessages::setUpperZone (10); layout.processNextMidiBuffer (buffer); expect (layout.getLowerZone().isActive()); expect (layout.getUpperZone().isActive()); expectEquals (layout.getLowerZone().getMasterChannel(), 1); expectEquals (layout.getLowerZone().numMemberChannels, 4); expectEquals (layout.getUpperZone().getMasterChannel(), 16); expectEquals (layout.getUpperZone().numMemberChannels, 10); buffer = MPEMessages::setUpperZone (10, 33, 44); layout.processNextMidiBuffer (buffer); expectEquals (layout.getUpperZone().numMemberChannels, 10); expectEquals (layout.getUpperZone().perNotePitchbendRange, 33); expectEquals (layout.getUpperZone().masterPitchbendRange, 44); } buffer = MPEMessages::clearAllZones(); layout.processNextMidiBuffer (buffer); expect (! layout.getLowerZone().isActive()); expect (! layout.getUpperZone().isActive()); } beginTest ("process individual MIDI messages"); { MPEZoneLayout layout; layout.processNextMidiEvent ({ 0x80, 0x59, 0xd0 }); // unrelated note-off msg layout.processNextMidiEvent ({ 0xb0, 0x64, 0x06 }); // RPN part 1 layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 }); // RPN part 2 layout.processNextMidiEvent ({ 0xb8, 0x0b, 0x66 }); // unrelated CC msg layout.processNextMidiEvent ({ 0xb0, 0x06, 0x03 }); // RPN part 3 layout.processNextMidiEvent ({ 0x90, 0x60, 0x00 }); // unrelated note-on msg expect (layout.getLowerZone().isActive()); expect (! layout.getUpperZone().isActive()); expectEquals (layout.getLowerZone().getMasterChannel(), 1); expectEquals (layout.getLowerZone().numMemberChannels, 3); expectEquals (layout.getLowerZone().perNotePitchbendRange, 48); expectEquals (layout.getLowerZone().masterPitchbendRange, 2); } } }; static MPEZoneLayoutTests MPEZoneLayoutUnitTests; #endif } // namespace juce