388 lines
14 KiB
C++
388 lines
14 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
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)
|
|
{
|
|
MidiBuffer::Iterator iter (buffer);
|
|
MidiMessage message;
|
|
int samplePosition; // not actually used, so no need to initialise.
|
|
|
|
while (iter.getNextEvent (message, samplePosition))
|
|
processNextMidiEvent (message);
|
|
}
|
|
|
|
//==============================================================================
|
|
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", "MIDI/MPE") {}
|
|
|
|
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 // JUCE_UNIT_TESTS
|
|
|
|
} // namespace juce
|