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 
23 namespace 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 
44 void MPEZoneLayout::sendLayoutChangeMessage()
45 {
46  listeners.call ([this] (Listener& l) { l.zoneLayoutChanged (*this); });
47 }
48 
49 //==============================================================================
50 void 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 
77 void MPEZoneLayout::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
78 {
79  setZone (true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
80 }
81 
82 void 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 
112 void MPEZoneLayout::processRpnMessage (MidiRPNMessage rpn)
113 {
115  processZoneLayoutRpnMessage (rpn);
116  else if (rpn.parameterNumber == 0)
117  processPitchbendRangeRpnMessage (rpn);
118 }
119 
120 void 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 
131 void 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 
141 void 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 
151 void 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 //==============================================================================
181 void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept
182 {
183  listeners.add (listenerToAdd);
184 }
185 
186 void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept
187 {
188  listeners.remove (listenerToRemove);
189 }
190 
191 //==============================================================================
192 void 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 
212 class MPEZoneLayoutTests : public UnitTest
213 {
214 public:
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 
382 static MPEZoneLayoutTests MPEZoneLayoutUnitTests;
383 
384 
385 #endif // JUCE_UNIT_TESTS
386 
387 } // namespace juce
This class represents the current MPE zone layout of a device capable of handling MPE...
Encapsulates a MIDI message.
This struct represents an MPE zone.
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...
bool getNextEvent(MidiMessage &result, int &samplePosition) noexcept
Retrieves a copy of the next event from the buffer.
void processNextMidiEvent(const MidiMessage &message)
Pass incoming MIDI messages to an object of this class if you want the zone layout to properly react ...
virtual void zoneLayoutChanged(const MPEZoneLayout &layout)=0
Implement this callback to be notified about any changes to this MPEZoneLayout.
int channel
Midi channel of the message, in the range 1 to 16.
Definition: juce_MidiRPN.h:39
void setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
Sets the upper zone of this layout.
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 ...
int value
The parameter value, in the range 0 to 16383 (0x3fff).
Definition: juce_MidiRPN.h:48
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 ...
const Zone getUpperZone() const noexcept
Returns a struct representing the upper MPE zone.
static MidiBuffer clearAllZones()
Returns the sequence of MIDI messages that, if sent to an Expressive MIDI device, will clear the lowe...
This is a base class for classes that perform a unit test.
Definition: juce_UnitTest.h:73
void addListener(Listener *const listenerToAdd) noexcept
Adds a listener.
int getControllerNumber() const noexcept
Returns the controller number of a controller message.
int getChannel() const noexcept
Returns the midi channel associated with the message.
MPEZoneLayout() noexcept
Default constructor.
bool isController() const noexcept
Returns true if this is a midi controller message.
const Zone getLowerZone() const noexcept
Returns a struct representing the lower MPE zone.
Represents a MIDI RPN (registered parameter number) or NRPN (non-registered parameter number) message...
Definition: juce_MidiRPN.h:36
int parameterNumber
The 14-bit parameter index, in the range 0 to 16383 (0x3fff).
Definition: juce_MidiRPN.h:42
Holds a sequence of time-stamped midi events.
void clearAllZones()
Clears the lower and upper zones of this layout, making them both inactive and disabling MPE mode...
int getControllerValue() const noexcept
Returns the controller value from a controller message.
Used to iterate through the events in a MidiBuffer.
MPEZoneLayout & operator=(const MPEZoneLayout &other)
Copy assignment operator.
static const int zoneLayoutMessagesRpnNumber
The RPN number used for MPE zone layout messages.
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...
void removeListener(Listener *const listenerToRemove) noexcept
Removes a listener.
void setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
Sets the lower zone of this layout.