27 : lowerZone (lower), upperZone (upper)
32 : lowerZone (zone.isLowerZone() ? zone :
MPEZone()),
33 upperZone (! zone.isLowerZone() ? zone :
MPEZone())
39 : lowerZone (
other.lowerZone),
40 upperZone (
other.upperZone)
44MPEZoneLayout& MPEZoneLayout::operator= (
const MPEZoneLayout&
other)
46 lowerZone =
other.lowerZone;
47 upperZone =
other.upperZone;
49 sendLayoutChangeMessage();
54void MPEZoneLayout::sendLayoutChangeMessage()
56 listeners.call ([
this] (Listener&
l) {
l.zoneLayoutChanged (*
this); });
60void MPEZoneLayout::setZone (
bool isLower,
int numMemberChannels,
int perNotePitchbendRange,
int masterPitchbendRange)
noexcept
62 checkAndLimitZoneParameters (0, 15, numMemberChannels);
63 checkAndLimitZoneParameters (0, 96, perNotePitchbendRange);
64 checkAndLimitZoneParameters (0, 96, masterPitchbendRange);
67 lowerZone = { MPEZone::Type::lower, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
69 upperZone = { MPEZone::Type::upper, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
71 if (numMemberChannels > 0)
73 auto totalChannels = lowerZone.numMemberChannels + upperZone.numMemberChannels;
78 upperZone.numMemberChannels = 14 - numMemberChannels;
80 lowerZone.numMemberChannels = 14 - numMemberChannels;
84 sendLayoutChangeMessage();
89 setZone (
true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
94 setZone (
false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
99 lowerZone = { MPEZone::Type::lower, 0 };
100 upperZone = { MPEZone::Type::upper, 0 };
102 sendLayoutChangeMessage();
115 processRpnMessage (*
parsed);
122 processZoneLayoutRpnMessage (rpn);
124 processPitchbendRangeRpnMessage (rpn);
127void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn)
131 if (rpn.channel == 1)
133 else if (rpn.channel == 16)
138void MPEZoneLayout::updateMasterPitchbend (MPEZone& zone,
int value)
140 if (zone.masterPitchbendRange != value)
142 checkAndLimitZoneParameters (0, 96, zone.masterPitchbendRange);
143 zone.masterPitchbendRange = value;
144 sendLayoutChangeMessage();
148void MPEZoneLayout::updatePerNotePitchbendRange (MPEZone& zone,
int value)
150 if (zone.perNotePitchbendRange != value)
152 checkAndLimitZoneParameters (0, 96, zone.perNotePitchbendRange);
153 zone.perNotePitchbendRange = value;
154 sendLayoutChangeMessage();
158void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn)
160 if (rpn.channel == 1)
162 updateMasterPitchbend (lowerZone, rpn.value);
164 else if (rpn.channel == 16)
166 updateMasterPitchbend (upperZone, rpn.value);
170 if (lowerZone.isUsingChannelAsMemberChannel (rpn.channel))
171 updatePerNotePitchbendRange (lowerZone, rpn.value);
172 else if (upperZone.isUsingChannelAsMemberChannel (rpn.channel))
173 updatePerNotePitchbendRange (upperZone, rpn.value);
195void MPEZoneLayout::checkAndLimitZoneParameters (
int minValue,
int maxValue,
223 void runTest()
override
225 beginTest (
"initialisation");
227 MPEZoneLayout layout;
228 expect (! layout.getLowerZone().isActive());
229 expect (! layout.getUpperZone().isActive());
232 beginTest (
"adding zones");
234 MPEZoneLayout layout;
236 layout.setLowerZone (7);
238 expect (layout.getLowerZone().isActive());
239 expect (! layout.getUpperZone().isActive());
240 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
241 expectEquals (layout.getLowerZone().numMemberChannels, 7);
243 layout.setUpperZone (7);
245 expect (layout.getLowerZone().isActive());
246 expect (layout.getUpperZone().isActive());
247 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
248 expectEquals (layout.getLowerZone().numMemberChannels, 7);
249 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
250 expectEquals (layout.getUpperZone().numMemberChannels, 7);
252 layout.setLowerZone (3);
254 expect (layout.getLowerZone().isActive());
255 expect (layout.getUpperZone().isActive());
256 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
257 expectEquals (layout.getLowerZone().numMemberChannels, 3);
258 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
259 expectEquals (layout.getUpperZone().numMemberChannels, 7);
261 layout.setUpperZone (3);
263 expect (layout.getLowerZone().isActive());
264 expect (layout.getUpperZone().isActive());
265 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
266 expectEquals (layout.getLowerZone().numMemberChannels, 3);
267 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
268 expectEquals (layout.getUpperZone().numMemberChannels, 3);
270 layout.setLowerZone (15);
272 expect (layout.getLowerZone().isActive());
273 expect (! layout.getUpperZone().isActive());
274 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
275 expectEquals (layout.getLowerZone().numMemberChannels, 15);
278 beginTest (
"clear all zones");
280 MPEZoneLayout layout;
282 expect (! layout.getLowerZone().isActive());
283 expect (! layout.getUpperZone().isActive());
285 layout.setLowerZone (7);
286 layout.setUpperZone (2);
288 expect (layout.getLowerZone().isActive());
289 expect (layout.getUpperZone().isActive());
291 layout.clearAllZones();
293 expect (! layout.getLowerZone().isActive());
294 expect (! layout.getUpperZone().isActive());
297 beginTest (
"process MIDI buffers");
299 MPEZoneLayout layout;
303 layout.processNextMidiBuffer (buffer);
305 expect (layout.getLowerZone().isActive());
306 expect (! layout.getUpperZone().isActive());
307 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
308 expectEquals (layout.getLowerZone().numMemberChannels, 7);
311 layout.processNextMidiBuffer (buffer);
313 expect (layout.getLowerZone().isActive());
314 expect (layout.getUpperZone().isActive());
315 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
316 expectEquals (layout.getLowerZone().numMemberChannels, 7);
317 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
318 expectEquals (layout.getUpperZone().numMemberChannels, 7);
322 layout.processNextMidiBuffer (buffer);
324 expect (layout.getLowerZone().isActive());
325 expect (layout.getUpperZone().isActive());
326 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
327 expectEquals (layout.getLowerZone().numMemberChannels, 10);
328 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
329 expectEquals (layout.getUpperZone().numMemberChannels, 4);
333 layout.processNextMidiBuffer (buffer);
335 expectEquals (layout.getLowerZone().numMemberChannels, 10);
336 expectEquals (layout.getLowerZone().perNotePitchbendRange, 33);
337 expectEquals (layout.getLowerZone().masterPitchbendRange, 44);
342 layout.processNextMidiBuffer (buffer);
344 expect (layout.getLowerZone().isActive());
345 expect (layout.getUpperZone().isActive());
346 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
347 expectEquals (layout.getLowerZone().numMemberChannels, 4);
348 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
349 expectEquals (layout.getUpperZone().numMemberChannels, 10);
353 layout.processNextMidiBuffer (buffer);
355 expectEquals (layout.getUpperZone().numMemberChannels, 10);
356 expectEquals (layout.getUpperZone().perNotePitchbendRange, 33);
357 expectEquals (layout.getUpperZone().masterPitchbendRange, 44);
361 layout.processNextMidiBuffer (buffer);
363 expect (! layout.getLowerZone().isActive());
364 expect (! layout.getUpperZone().isActive());
367 beginTest (
"process individual MIDI messages");
369 MPEZoneLayout layout;
371 layout.processNextMidiEvent ({ 0x80, 0x59, 0xd0 });
372 layout.processNextMidiEvent ({ 0xb0, 0x64, 0x06 });
373 layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 });
374 layout.processNextMidiEvent ({ 0xb8, 0x0b, 0x66 });
375 layout.processNextMidiEvent ({ 0xb0, 0x06, 0x03 });
376 layout.processNextMidiEvent ({ 0x90, 0x60, 0x00 });
378 expect (layout.getLowerZone().isActive());
379 expect (! layout.getUpperZone().isActive());
380 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
381 expectEquals (layout.getLowerZone().numMemberChannels, 3);
382 expectEquals (layout.getLowerZone().perNotePitchbendRange, 48);
383 expectEquals (layout.getLowerZone().masterPitchbendRange, 2);
386 layout.processNextMidiEvent ({ 0xb0, 0x64, 0x00 });
389 expectEquals (layout.getLowerZone().masterPitchbendRange,
masterPitchBend);
392 layout.processNextMidiEvent ({ 0xb0, 0x06,
newPitchBend });
394 expectEquals (layout.getLowerZone().masterPitchbendRange,
newPitchBend);
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.
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...
MPEZoneLayout()=default
Creates a layout with inactive upper and lower zones.
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.
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 ...
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.
std::optional< MidiRPNMessage > tryParse(int midiChannel, int controllerNumber, int controllerValue)
Takes the next in a stream of incoming MIDI CC messages and returns a MidiRPNMessage if the current m...
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
int parameterNumber
The 14-bit parameter index, in the range 0 to 16383 (0x3fff).
Represents a MIDI RPN (registered parameter number) or NRPN (non-registered parameter number) message...
This struct represents an MPE zone.