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);
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);
183 listeners.add (listenerToAdd);
188 listeners.remove (listenerToRemove);
192void MPEZoneLayout::checkAndLimitZoneParameters (
int minValue,
int maxValue,
193 int& valueToCheckAndLimit)
noexcept
195 if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue)
204 valueToCheckAndLimit = jlimit (minValue, maxValue, valueToCheckAndLimit);
213class MPEZoneLayoutTests :
public UnitTest
217 : UnitTest (
"MPEZoneLayout class", UnitTestCategories::midi)
220 void runTest()
override
222 beginTest (
"initialisation");
224 MPEZoneLayout layout;
225 expect (! layout.getLowerZone().isActive());
226 expect (! layout.getUpperZone().isActive());
229 beginTest (
"adding zones");
231 MPEZoneLayout layout;
233 layout.setLowerZone (7);
235 expect (layout.getLowerZone().isActive());
236 expect (! layout.getUpperZone().isActive());
237 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
238 expectEquals (layout.getLowerZone().numMemberChannels, 7);
240 layout.setUpperZone (7);
242 expect (layout.getLowerZone().isActive());
243 expect (layout.getUpperZone().isActive());
244 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
245 expectEquals (layout.getLowerZone().numMemberChannels, 7);
246 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
247 expectEquals (layout.getUpperZone().numMemberChannels, 7);
249 layout.setLowerZone (3);
251 expect (layout.getLowerZone().isActive());
252 expect (layout.getUpperZone().isActive());
253 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
254 expectEquals (layout.getLowerZone().numMemberChannels, 3);
255 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
256 expectEquals (layout.getUpperZone().numMemberChannels, 7);
258 layout.setUpperZone (3);
260 expect (layout.getLowerZone().isActive());
261 expect (layout.getUpperZone().isActive());
262 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
263 expectEquals (layout.getLowerZone().numMemberChannels, 3);
264 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
265 expectEquals (layout.getUpperZone().numMemberChannels, 3);
267 layout.setLowerZone (15);
269 expect (layout.getLowerZone().isActive());
270 expect (! layout.getUpperZone().isActive());
271 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
272 expectEquals (layout.getLowerZone().numMemberChannels, 15);
275 beginTest (
"clear all zones");
277 MPEZoneLayout layout;
279 expect (! layout.getLowerZone().isActive());
280 expect (! layout.getUpperZone().isActive());
282 layout.setLowerZone (7);
283 layout.setUpperZone (2);
285 expect (layout.getLowerZone().isActive());
286 expect (layout.getUpperZone().isActive());
288 layout.clearAllZones();
290 expect (! layout.getLowerZone().isActive());
291 expect (! layout.getUpperZone().isActive());
294 beginTest (
"process MIDI buffers");
296 MPEZoneLayout layout;
300 layout.processNextMidiBuffer (buffer);
302 expect (layout.getLowerZone().isActive());
303 expect (! layout.getUpperZone().isActive());
304 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
305 expectEquals (layout.getLowerZone().numMemberChannels, 7);
308 layout.processNextMidiBuffer (buffer);
310 expect (layout.getLowerZone().isActive());
311 expect (layout.getUpperZone().isActive());
312 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
313 expectEquals (layout.getLowerZone().numMemberChannels, 7);
314 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
315 expectEquals (layout.getUpperZone().numMemberChannels, 7);
319 layout.processNextMidiBuffer (buffer);
321 expect (layout.getLowerZone().isActive());
322 expect (layout.getUpperZone().isActive());
323 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
324 expectEquals (layout.getLowerZone().numMemberChannels, 10);
325 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
326 expectEquals (layout.getUpperZone().numMemberChannels, 4);
330 layout.processNextMidiBuffer (buffer);
332 expectEquals (layout.getLowerZone().numMemberChannels, 10);
333 expectEquals (layout.getLowerZone().perNotePitchbendRange, 33);
334 expectEquals (layout.getLowerZone().masterPitchbendRange, 44);
339 layout.processNextMidiBuffer (buffer);
341 expect (layout.getLowerZone().isActive());
342 expect (layout.getUpperZone().isActive());
343 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
344 expectEquals (layout.getLowerZone().numMemberChannels, 4);
345 expectEquals (layout.getUpperZone().getMasterChannel(), 16);
346 expectEquals (layout.getUpperZone().numMemberChannels, 10);
350 layout.processNextMidiBuffer (buffer);
352 expectEquals (layout.getUpperZone().numMemberChannels, 10);
353 expectEquals (layout.getUpperZone().perNotePitchbendRange, 33);
354 expectEquals (layout.getUpperZone().masterPitchbendRange, 44);
358 layout.processNextMidiBuffer (buffer);
360 expect (! layout.getLowerZone().isActive());
361 expect (! layout.getUpperZone().isActive());
364 beginTest (
"process individual MIDI messages");
366 MPEZoneLayout layout;
368 layout.processNextMidiEvent ({ 0x80, 0x59, 0xd0 });
369 layout.processNextMidiEvent ({ 0xb0, 0x64, 0x06 });
370 layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 });
371 layout.processNextMidiEvent ({ 0xb8, 0x0b, 0x66 });
372 layout.processNextMidiEvent ({ 0xb0, 0x06, 0x03 });
373 layout.processNextMidiEvent ({ 0x90, 0x60, 0x00 });
375 expect (layout.getLowerZone().isActive());
376 expect (! layout.getUpperZone().isActive());
377 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
378 expectEquals (layout.getLowerZone().numMemberChannels, 3);
379 expectEquals (layout.getLowerZone().perNotePitchbendRange, 48);
380 expectEquals (layout.getLowerZone().masterPitchbendRange, 2);
385static MPEZoneLayoutTests MPEZoneLayoutUnitTests;
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.
bool getNextEvent(MidiMessage &result, int &samplePosition) noexcept
Retrieves a copy of the next event from the buffer.
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...
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...