29 : lowerZone (
other.lowerZone),
30 upperZone (
other.upperZone)
36 lowerZone =
other.lowerZone;
37 upperZone =
other.upperZone;
39 sendLayoutChangeMessage();
44void MPEZoneLayout::sendLayoutChangeMessage()
46 listeners.call ([
this] (Listener&
l) {
l.zoneLayoutChanged (*
this); });
50void MPEZoneLayout::setZone (
bool isLower,
int numMemberChannels,
int perNotePitchbendRange,
int masterPitchbendRange)
noexcept
52 checkAndLimitZoneParameters (0, 15, numMemberChannels);
53 checkAndLimitZoneParameters (0, 96, perNotePitchbendRange);
54 checkAndLimitZoneParameters (0, 96, masterPitchbendRange);
57 lowerZone = {
true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
59 upperZone = {
false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
61 if (numMemberChannels > 0)
63 auto totalChannels = lowerZone.numMemberChannels + upperZone.numMemberChannels;
65 if (totalChannels >= 15)
68 upperZone.numMemberChannels = 14 - numMemberChannels;
70 lowerZone.numMemberChannels = 14 - numMemberChannels;
74 sendLayoutChangeMessage();
79 setZone (
true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
84 setZone (
false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
89 lowerZone = {
true, 0 };
90 upperZone = {
false, 0 };
92 sendLayoutChangeMessage();
108 processRpnMessage (
rpn);
115 processZoneLayoutRpnMessage (
rpn);
116 else if (
rpn.parameterNumber == 0)
117 processPitchbendRangeRpnMessage (
rpn);
120void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn)
124 if (rpn.channel == 1)
126 else if (rpn.channel == 16)
131void MPEZoneLayout::updateMasterPitchbend (Zone& zone,
int value)
133 if (zone.masterPitchbendRange != value)
135 checkAndLimitZoneParameters (0, 96, zone.masterPitchbendRange);
136 zone.masterPitchbendRange = value;
137 sendLayoutChangeMessage();
141void MPEZoneLayout::updatePerNotePitchbendRange (Zone& zone,
int value)
143 if (zone.perNotePitchbendRange != value)
145 checkAndLimitZoneParameters (0, 96, zone.perNotePitchbendRange);
146 zone.perNotePitchbendRange = value;
147 sendLayoutChangeMessage();
151void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn)
153 if (rpn.channel == 1)
155 updateMasterPitchbend (lowerZone, rpn.value);
157 else if (rpn.channel == 16)
159 updateMasterPitchbend (upperZone, rpn.value);
163 if (lowerZone.isUsingChannelAsMemberChannel (rpn.channel))
164 updatePerNotePitchbendRange (lowerZone, rpn.value);
165 else if (upperZone.isUsingChannelAsMemberChannel (rpn.channel))
166 updatePerNotePitchbendRange (upperZone, rpn.value);
192void MPEZoneLayout::checkAndLimitZoneParameters (
int minValue,
int maxValue,
212class MPEZoneLayoutTests :
public UnitTest
217 void runTest()
override
219 beginTest (
"initialisation");
222 expect (!
layout.getLowerZone().isActive());
223 expect (!
layout.getUpperZone().isActive());
226 beginTest (
"adding zones");
232 expect (
layout.getLowerZone().isActive());
233 expect (!
layout.getUpperZone().isActive());
234 expectEquals (
layout.getLowerZone().getMasterChannel(), 1);
235 expectEquals (
layout.getLowerZone().numMemberChannels, 7);
239 expect (
layout.getLowerZone().isActive());
240 expect (
layout.getUpperZone().isActive());
241 expectEquals (
layout.getLowerZone().getMasterChannel(), 1);
242 expectEquals (
layout.getLowerZone().numMemberChannels, 7);
243 expectEquals (
layout.getUpperZone().getMasterChannel(), 16);
244 expectEquals (
layout.getUpperZone().numMemberChannels, 7);
248 expect (
layout.getLowerZone().isActive());
249 expect (
layout.getUpperZone().isActive());
250 expectEquals (
layout.getLowerZone().getMasterChannel(), 1);
251 expectEquals (
layout.getLowerZone().numMemberChannels, 3);
252 expectEquals (
layout.getUpperZone().getMasterChannel(), 16);
253 expectEquals (
layout.getUpperZone().numMemberChannels, 7);
257 expect (
layout.getLowerZone().isActive());
258 expect (
layout.getUpperZone().isActive());
259 expectEquals (
layout.getLowerZone().getMasterChannel(), 1);
260 expectEquals (
layout.getLowerZone().numMemberChannels, 3);
261 expectEquals (
layout.getUpperZone().getMasterChannel(), 16);
262 expectEquals (
layout.getUpperZone().numMemberChannels, 3);
266 expect (
layout.getLowerZone().isActive());
267 expect (!
layout.getUpperZone().isActive());
268 expectEquals (
layout.getLowerZone().getMasterChannel(), 1);
269 expectEquals (
layout.getLowerZone().numMemberChannels, 15);
272 beginTest (
"clear all zones");
276 expect (!
layout.getLowerZone().isActive());
277 expect (!
layout.getUpperZone().isActive());
282 expect (
layout.getLowerZone().isActive());
283 expect (
layout.getUpperZone().isActive());
287 expect (!
layout.getLowerZone().isActive());
288 expect (!
layout.getUpperZone().isActive());
291 beginTest (
"process MIDI buffers");
297 layout.processNextMidiBuffer (buffer);
299 expect (
layout.getLowerZone().isActive());
300 expect (!
layout.getUpperZone().isActive());
301 expectEquals (
layout.getLowerZone().getMasterChannel(), 1);
302 expectEquals (
layout.getLowerZone().numMemberChannels, 7);
305 layout.processNextMidiBuffer (buffer);
307 expect (
layout.getLowerZone().isActive());
308 expect (
layout.getUpperZone().isActive());
309 expectEquals (
layout.getLowerZone().getMasterChannel(), 1);
310 expectEquals (
layout.getLowerZone().numMemberChannels, 7);
311 expectEquals (
layout.getUpperZone().getMasterChannel(), 16);
312 expectEquals (
layout.getUpperZone().numMemberChannels, 7);
316 layout.processNextMidiBuffer (buffer);
318 expect (
layout.getLowerZone().isActive());
319 expect (
layout.getUpperZone().isActive());
320 expectEquals (
layout.getLowerZone().getMasterChannel(), 1);
321 expectEquals (
layout.getLowerZone().numMemberChannels, 10);
322 expectEquals (
layout.getUpperZone().getMasterChannel(), 16);
323 expectEquals (
layout.getUpperZone().numMemberChannels, 4);
327 layout.processNextMidiBuffer (buffer);
329 expectEquals (
layout.getLowerZone().numMemberChannels, 10);
330 expectEquals (
layout.getLowerZone().perNotePitchbendRange, 33);
331 expectEquals (
layout.getLowerZone().masterPitchbendRange, 44);
336 layout.processNextMidiBuffer (buffer);
338 expect (
layout.getLowerZone().isActive());
339 expect (
layout.getUpperZone().isActive());
340 expectEquals (
layout.getLowerZone().getMasterChannel(), 1);
341 expectEquals (
layout.getLowerZone().numMemberChannels, 4);
342 expectEquals (
layout.getUpperZone().getMasterChannel(), 16);
343 expectEquals (
layout.getUpperZone().numMemberChannels, 10);
347 layout.processNextMidiBuffer (buffer);
349 expectEquals (
layout.getUpperZone().numMemberChannels, 10);
350 expectEquals (
layout.getUpperZone().perNotePitchbendRange, 33);
351 expectEquals (
layout.getUpperZone().masterPitchbendRange, 44);
355 layout.processNextMidiBuffer (buffer);
357 expect (!
layout.getLowerZone().isActive());
358 expect (!
layout.getUpperZone().isActive());
361 beginTest (
"process individual MIDI messages");
365 layout.processNextMidiEvent ({ 0x80, 0x59, 0xd0 });
366 layout.processNextMidiEvent ({ 0xb0, 0x64, 0x06 });
367 layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 });
368 layout.processNextMidiEvent ({ 0xb8, 0x0b, 0x66 });
369 layout.processNextMidiEvent ({ 0xb0, 0x06, 0x03 });
370 layout.processNextMidiEvent ({ 0x90, 0x60, 0x00 });
372 expect (
layout.getLowerZone().isActive());
373 expect (!
layout.getUpperZone().isActive());
374 expectEquals (
layout.getLowerZone().getMasterChannel(), 1);
375 expectEquals (
layout.getLowerZone().numMemberChannels, 3);
376 expectEquals (
layout.getLowerZone().perNotePitchbendRange, 48);
377 expectEquals (
layout.getLowerZone().masterPitchbendRange, 2);
382static MPEZoneLayoutTests MPEZoneLayoutUnitTests;
Holds a resizable array of primitive or copy-by-value objects.
void remove(int indexToRemove)
Removes an element from the array.
Array()=default
Creates an empty array.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
static MidiBuffer setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2)
Returns the sequence of MIDI messages that, if sent to an Expressive MIDI device, will set the upper ...
static MidiBuffer clearAllZones()
Returns the sequence of MIDI messages that, if sent to an Expressive MIDI device, will clear the lowe...
static MidiBuffer setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2)
Returns the sequence of MIDI messages that, if sent to an Expressive MIDI device, will set the lower ...
static const int zoneLayoutMessagesRpnNumber
The RPN number used for MPE zone layout messages.
This class represents the current MPE zone layout of a device capable of handling MPE.
MPEZoneLayout() noexcept
Default constructor.
void processNextMidiBuffer(const MidiBuffer &buffer)
Pass incoming MIDI buffers to an object of this class if you want the zone layout to properly react t...
void clearAllZones()
Clears the lower and upper zones of this layout, making them both inactive and disabling MPE mode.
void setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
Sets the upper zone of this layout.
void removeListener(Listener *const listenerToRemove) noexcept
Removes a listener.
void addListener(Listener *const listenerToAdd) noexcept
Adds a listener.
MPEZoneLayout & operator=(const MPEZoneLayout &other)
Copy assignment operator.
void setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
Sets the lower zone of this layout.
void processNextMidiEvent(const MidiMessage &message)
Pass incoming MIDI messages to an object of this class if you want the zone layout to properly react ...
Used to iterate through the events in a MidiBuffer.
Holds a sequence of time-stamped midi events.
Encapsulates a MIDI message.
int getChannel() const noexcept
Returns the midi channel associated with the message.
bool isController() const noexcept
Returns true if this is a midi controller message.
int getControllerNumber() const noexcept
Returns the controller number of a controller message.
int getControllerValue() const noexcept
Returns the controller value from a controller message.
bool parseControllerMessage(int midiChannel, int controllerNumber, int controllerValue, MidiRPNMessage &result) noexcept
Takes the next in a stream of incoming MIDI CC messages and returns true if it forms the last of a se...
Represents a MIDI RPN (registered parameter number) or NRPN (non-registered parameter number) message...