OpenShot Library | libopenshot-audio 0.2.0
juce_MPEZoneLayout.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2017 - ROLI Ltd.
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
27
29 : lowerZone (other.lowerZone),
30 upperZone (other.upperZone)
31{
32}
33
35{
36 lowerZone = other.lowerZone;
37 upperZone = other.upperZone;
38
39 sendLayoutChangeMessage();
40
41 return *this;
42}
43
44void MPEZoneLayout::sendLayoutChangeMessage()
45{
46 listeners.call ([this] (Listener& l) { l.zoneLayoutChanged (*this); });
47}
48
49//==============================================================================
50void MPEZoneLayout::setZone (bool isLower, int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
51{
52 checkAndLimitZoneParameters (0, 15, numMemberChannels);
53 checkAndLimitZoneParameters (0, 96, perNotePitchbendRange);
54 checkAndLimitZoneParameters (0, 96, masterPitchbendRange);
55
56 if (isLower)
57 lowerZone = { true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
58 else
59 upperZone = { false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
60
61 if (numMemberChannels > 0)
62 {
63 auto totalChannels = lowerZone.numMemberChannels + upperZone.numMemberChannels;
64
65 if (totalChannels >= 15)
66 {
67 if (isLower)
68 upperZone.numMemberChannels = 14 - numMemberChannels;
69 else
70 lowerZone.numMemberChannels = 14 - numMemberChannels;
71 }
72 }
73
74 sendLayoutChangeMessage();
75}
76
77void MPEZoneLayout::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
78{
79 setZone (true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
80}
81
82void MPEZoneLayout::setUpperZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
83{
84 setZone (false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
85}
86
88{
89 lowerZone = { true, 0 };
90 upperZone = { false, 0 };
91
92 sendLayoutChangeMessage();
93}
94
95//==============================================================================
97{
98 if (! message.isController())
99 return;
100
101 MidiRPNMessage rpn;
102
103 if (rpnDetector.parseControllerMessage (message.getChannel(),
104 message.getControllerNumber(),
105 message.getControllerValue(),
106 rpn))
107 {
108 processRpnMessage (rpn);
109 }
110}
111
112void MPEZoneLayout::processRpnMessage (MidiRPNMessage rpn)
113{
115 processZoneLayoutRpnMessage (rpn);
116 else if (rpn.parameterNumber == 0)
117 processPitchbendRangeRpnMessage (rpn);
118}
119
120void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn)
121{
122 if (rpn.value < 16)
123 {
124 if (rpn.channel == 1)
125 setLowerZone (rpn.value);
126 else if (rpn.channel == 16)
127 setUpperZone (rpn.value);
128 }
129}
130
131void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value)
132{
133 if (zone.masterPitchbendRange != value)
134 {
135 checkAndLimitZoneParameters (0, 96, zone.masterPitchbendRange);
136 zone.masterPitchbendRange = value;
137 sendLayoutChangeMessage();
138 }
139}
140
141void MPEZoneLayout::updatePerNotePitchbendRange (Zone& zone, int value)
142{
143 if (zone.perNotePitchbendRange != value)
144 {
145 checkAndLimitZoneParameters (0, 96, zone.perNotePitchbendRange);
146 zone.perNotePitchbendRange = value;
147 sendLayoutChangeMessage();
148 }
149}
150
151void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn)
152{
153 if (rpn.channel == 1)
154 {
155 updateMasterPitchbend (lowerZone, rpn.value);
156 }
157 else if (rpn.channel == 16)
158 {
159 updateMasterPitchbend (upperZone, rpn.value);
160 }
161 else
162 {
163 if (lowerZone.isUsingChannelAsMemberChannel (rpn.channel))
164 updatePerNotePitchbendRange (lowerZone, rpn.value);
165 else if (upperZone.isUsingChannelAsMemberChannel (rpn.channel))
166 updatePerNotePitchbendRange (upperZone, rpn.value);
167 }
168}
169
171{
172 MidiBuffer::Iterator iter (buffer);
173 MidiMessage message;
174 int samplePosition; // not actually used, so no need to initialise.
175
176 while (iter.getNextEvent (message, samplePosition))
177 processNextMidiEvent (message);
178}
179
180//==============================================================================
181void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept
182{
183 listeners.add (listenerToAdd);
184}
185
186void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept
187{
188 listeners.remove (listenerToRemove);
189}
190
191//==============================================================================
192void MPEZoneLayout::checkAndLimitZoneParameters (int minValue, int maxValue,
193 int& valueToCheckAndLimit) noexcept
194{
195 if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue)
196 {
197 // if you hit this, one of the parameters you supplied for this zone
198 // was not within the allowed range!
199 // we fit this back into the allowed range here to maintain a valid
200 // state for the zone, but probably the resulting zone is not what you
201 // wanted it to be!
202 jassertfalse;
203
204 valueToCheckAndLimit = jlimit (minValue, maxValue, valueToCheckAndLimit);
205 }
206}
207
208//==============================================================================
209//==============================================================================
210#if JUCE_UNIT_TESTS
211
212class MPEZoneLayoutTests : public UnitTest
213{
214public:
215 MPEZoneLayoutTests() : UnitTest ("MPEZoneLayout class", "MIDI/MPE") {}
216
217 void runTest() override
218 {
219 beginTest ("initialisation");
220 {
221 MPEZoneLayout layout;
222 expect (! layout.getLowerZone().isActive());
223 expect (! layout.getUpperZone().isActive());
224 }
225
226 beginTest ("adding zones");
227 {
228 MPEZoneLayout layout;
229
230 layout.setLowerZone (7);
231
232 expect (layout.getLowerZone().isActive());
233 expect (! layout.getUpperZone().isActive());
234 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
235 expectEquals (layout.getLowerZone().numMemberChannels, 7);
236
237 layout.setUpperZone (7);
238
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);
245
246 layout.setLowerZone (3);
247
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);
254
255 layout.setUpperZone (3);
256
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);
263
264 layout.setLowerZone (15);
265
266 expect (layout.getLowerZone().isActive());
267 expect (! layout.getUpperZone().isActive());
268 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
269 expectEquals (layout.getLowerZone().numMemberChannels, 15);
270 }
271
272 beginTest ("clear all zones");
273 {
274 MPEZoneLayout layout;
275
276 expect (! layout.getLowerZone().isActive());
277 expect (! layout.getUpperZone().isActive());
278
279 layout.setLowerZone (7);
280 layout.setUpperZone (2);
281
282 expect (layout.getLowerZone().isActive());
283 expect (layout.getUpperZone().isActive());
284
285 layout.clearAllZones();
286
287 expect (! layout.getLowerZone().isActive());
288 expect (! layout.getUpperZone().isActive());
289 }
290
291 beginTest ("process MIDI buffers");
292 {
293 MPEZoneLayout layout;
294 MidiBuffer buffer;
295
296 buffer = MPEMessages::setLowerZone (7);
297 layout.processNextMidiBuffer (buffer);
298
299 expect (layout.getLowerZone().isActive());
300 expect (! layout.getUpperZone().isActive());
301 expectEquals (layout.getLowerZone().getMasterChannel(), 1);
302 expectEquals (layout.getLowerZone().numMemberChannels, 7);
303
304 buffer = MPEMessages::setUpperZone (7);
305 layout.processNextMidiBuffer (buffer);
306
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);
313
314 {
315 buffer = MPEMessages::setLowerZone (10);
316 layout.processNextMidiBuffer (buffer);
317
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);
324
325
326 buffer = MPEMessages::setLowerZone (10, 33, 44);
327 layout.processNextMidiBuffer (buffer);
328
329 expectEquals (layout.getLowerZone().numMemberChannels, 10);
330 expectEquals (layout.getLowerZone().perNotePitchbendRange, 33);
331 expectEquals (layout.getLowerZone().masterPitchbendRange, 44);
332 }
333
334 {
335 buffer = MPEMessages::setUpperZone (10);
336 layout.processNextMidiBuffer (buffer);
337
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);
344
345 buffer = MPEMessages::setUpperZone (10, 33, 44);
346
347 layout.processNextMidiBuffer (buffer);
348
349 expectEquals (layout.getUpperZone().numMemberChannels, 10);
350 expectEquals (layout.getUpperZone().perNotePitchbendRange, 33);
351 expectEquals (layout.getUpperZone().masterPitchbendRange, 44);
352 }
353
354 buffer = MPEMessages::clearAllZones();
355 layout.processNextMidiBuffer (buffer);
356
357 expect (! layout.getLowerZone().isActive());
358 expect (! layout.getUpperZone().isActive());
359 }
360
361 beginTest ("process individual MIDI messages");
362 {
363 MPEZoneLayout layout;
364
365 layout.processNextMidiEvent ({ 0x80, 0x59, 0xd0 }); // unrelated note-off msg
366 layout.processNextMidiEvent ({ 0xb0, 0x64, 0x06 }); // RPN part 1
367 layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 }); // RPN part 2
368 layout.processNextMidiEvent ({ 0xb8, 0x0b, 0x66 }); // unrelated CC msg
369 layout.processNextMidiEvent ({ 0xb0, 0x06, 0x03 }); // RPN part 3
370 layout.processNextMidiEvent ({ 0x90, 0x60, 0x00 }); // unrelated note-on msg
371
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);
378 }
379 }
380};
381
382static MPEZoneLayoutTests MPEZoneLayoutUnitTests;
383
384
385#endif // JUCE_UNIT_TESTS
386
387} // namespace juce
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...