|  | /*
  ==============================================================================
   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;
    listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this);
    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
 |