/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ MPEZoneLayout::MPEZoneLayout() noexcept { } MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) : zones (other.zones) { } MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) { zones = other.zones; return *this; } //============================================================================== bool MPEZoneLayout::addZone (MPEZone newZone) { bool noOtherZonesModified = true; for (int i = zones.size(); --i >= 0;) { MPEZone& zone = zones.getReference (i); if (zone.overlapsWith (newZone)) { if (! zone.truncateToFit (newZone)) zones.removeRange (i, 1); // can't use zones.remove (i) because that requires a default c'tor :-( noOtherZonesModified = false; } } zones.add (newZone); listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); return noOtherZonesModified; } //============================================================================== int MPEZoneLayout::getNumZones() const noexcept { return zones.size(); } MPEZone* MPEZoneLayout::getZoneByIndex (int index) const noexcept { if (zones.size() < index) return nullptr; return &(zones.getReference (index)); } void MPEZoneLayout::clearAllZones() { zones.clear(); listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); } //============================================================================== 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) addZone (MPEZone (rpn.channel - 1, rpn.value)); else clearAllZones(); } //============================================================================== void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn) { if (MPEZone* zone = getZoneByFirstNoteChannel (rpn.channel)) { if (zone->getPerNotePitchbendRange() != rpn.value) { zone->setPerNotePitchbendRange (rpn.value); listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); return; } } if (MPEZone* zone = getZoneByMasterChannel (rpn.channel)) { if (zone->getMasterPitchbendRange() != rpn.value) { zone->setMasterPitchbendRange (rpn.value); listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); return; } } } //============================================================================== void MPEZoneLayout::processNextMidiBuffer (const MidiBuffer& buffer) { MidiBuffer::Iterator iter (buffer); MidiMessage message; int samplePosition; // not actually used, so no need to initialise. while (iter.getNextEvent (message, samplePosition)) processNextMidiEvent (message); } //============================================================================== MPEZone* MPEZoneLayout::getZoneByChannel (int channel) const noexcept { for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) if (zone->isUsingChannel (channel)) return zone; return nullptr; } MPEZone* MPEZoneLayout::getZoneByMasterChannel (int channel) const noexcept { for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) if (zone->getMasterChannel() == channel) return zone; return nullptr; } MPEZone* MPEZoneLayout::getZoneByFirstNoteChannel (int channel) const noexcept { for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) if (zone->getFirstNoteChannel() == channel) return zone; return nullptr; } MPEZone* MPEZoneLayout::getZoneByNoteChannel (int channel) const noexcept { for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone) if (zone->isUsingChannelAsNoteChannel (channel)) return zone; return nullptr; } //============================================================================== void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept { listeners.add (listenerToAdd); } void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept { listeners.remove (listenerToRemove); } //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS class MPEZoneLayoutTests : public UnitTest { public: MPEZoneLayoutTests() : UnitTest ("MPEZoneLayout class") {} void runTest() override { beginTest ("initialisation"); { MPEZoneLayout layout; expectEquals (layout.getNumZones(), 0); } beginTest ("adding zones"); { MPEZoneLayout layout; expect (layout.addZone (MPEZone (1, 7))); expectEquals (layout.getNumZones(), 1); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); expect (layout.addZone (MPEZone (9, 7))); expectEquals (layout.getNumZones(), 2); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); expect (! layout.addZone (MPEZone (5, 3))); expectEquals (layout.getNumZones(), 3); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); expectEquals (layout.getZoneByIndex (2)->getMasterChannel(), 5); expectEquals (layout.getZoneByIndex (2)->getNumNoteChannels(), 3); expect (! layout.addZone (MPEZone (5, 4))); expectEquals (layout.getNumZones(), 2); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 5); expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 4); expect (! layout.addZone (MPEZone (6, 4))); expectEquals (layout.getNumZones(), 2); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 6); expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 4); } beginTest ("querying zones"); { MPEZoneLayout layout; layout.addZone (MPEZone (2, 5)); layout.addZone (MPEZone (9, 4)); expect (layout.getZoneByMasterChannel (1) == nullptr); expect (layout.getZoneByMasterChannel (2) != nullptr); expect (layout.getZoneByMasterChannel (3) == nullptr); expect (layout.getZoneByMasterChannel (8) == nullptr); expect (layout.getZoneByMasterChannel (9) != nullptr); expect (layout.getZoneByMasterChannel (10) == nullptr); expectEquals (layout.getZoneByMasterChannel (2)->getNumNoteChannels(), 5); expectEquals (layout.getZoneByMasterChannel (9)->getNumNoteChannels(), 4); expect (layout.getZoneByFirstNoteChannel (2) == nullptr); expect (layout.getZoneByFirstNoteChannel (3) != nullptr); expect (layout.getZoneByFirstNoteChannel (4) == nullptr); expect (layout.getZoneByFirstNoteChannel (9) == nullptr); expect (layout.getZoneByFirstNoteChannel (10) != nullptr); expect (layout.getZoneByFirstNoteChannel (11) == nullptr); expectEquals (layout.getZoneByFirstNoteChannel (3)->getNumNoteChannels(), 5); expectEquals (layout.getZoneByFirstNoteChannel (10)->getNumNoteChannels(), 4); expect (layout.getZoneByNoteChannel (2) == nullptr); expect (layout.getZoneByNoteChannel (3) != nullptr); expect (layout.getZoneByNoteChannel (4) != nullptr); expect (layout.getZoneByNoteChannel (6) != nullptr); expect (layout.getZoneByNoteChannel (7) != nullptr); expect (layout.getZoneByNoteChannel (8) == nullptr); expect (layout.getZoneByNoteChannel (9) == nullptr); expect (layout.getZoneByNoteChannel (10) != nullptr); expect (layout.getZoneByNoteChannel (11) != nullptr); expect (layout.getZoneByNoteChannel (12) != nullptr); expect (layout.getZoneByNoteChannel (13) != nullptr); expect (layout.getZoneByNoteChannel (14) == nullptr); expectEquals (layout.getZoneByNoteChannel (5)->getNumNoteChannels(), 5); expectEquals (layout.getZoneByNoteChannel (13)->getNumNoteChannels(), 4); } beginTest ("clear all zones"); { MPEZoneLayout layout; expect (layout.addZone (MPEZone (1, 7))); expect (layout.addZone (MPEZone (10, 2))); layout.clearAllZones(); expectEquals (layout.getNumZones(), 0); } beginTest ("process MIDI buffers"); { MPEZoneLayout layout; MidiBuffer buffer; buffer = MPEMessages::addZone (MPEZone (1, 7)); layout.processNextMidiBuffer (buffer); expectEquals (layout.getNumZones(), 1); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); buffer = MPEMessages::addZone (MPEZone (9, 7)); layout.processNextMidiBuffer (buffer); expectEquals (layout.getNumZones(), 2); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); MPEZone zone (1, 10); buffer = MPEMessages::addZone (zone); layout.processNextMidiBuffer (buffer); expectEquals (layout.getNumZones(), 1); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 10); zone.setPerNotePitchbendRange (33); zone.setMasterPitchbendRange (44); buffer = MPEMessages::masterPitchbendRange (zone); buffer.addEvents (MPEMessages::perNotePitchbendRange (zone), 0, -1, 0); layout.processNextMidiBuffer (buffer); expectEquals (layout.getZoneByIndex (0)->getPerNotePitchbendRange(), 33); expectEquals (layout.getZoneByIndex (0)->getMasterPitchbendRange(), 44); } beginTest ("process individual MIDI messages"); { MPEZoneLayout layout; layout.processNextMidiEvent (MidiMessage (0x80, 0x59, 0xd0)); // unrelated note-off msg layout.processNextMidiEvent (MidiMessage (0xb1, 0x64, 0x06)); // RPN part 1 layout.processNextMidiEvent (MidiMessage (0xb1, 0x65, 0x00)); // RPN part 2 layout.processNextMidiEvent (MidiMessage (0xb8, 0x0b, 0x66)); // unrelated CC msg layout.processNextMidiEvent (MidiMessage (0xb1, 0x06, 0x03)); // RPN part 3 layout.processNextMidiEvent (MidiMessage (0x90, 0x60, 0x00)); // unrelated note-on msg expectEquals (layout.getNumZones(), 1); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); } } }; static MPEZoneLayoutTests MPEZoneLayoutUnitTests; #endif // JUCE_UNIT_TESTS