OpenShot Audio Library | OpenShotAudio  0.3.1
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 //==============================================================================
211 #if JUCE_UNIT_TESTS
212 
213 class MPEZoneLayoutTests : public UnitTest
214 {
215 public:
216  MPEZoneLayoutTests()
217  : UnitTest ("MPEZoneLayout class", UnitTestCategories::midi)
218  {}
219 
220  void runTest() override
221  {
222  beginTest ("initialisation");
223  {
224  MPEZoneLayout layout;
225  expect (! layout.getLowerZone().isActive());
226  expect (! layout.getUpperZone().isActive());
227  }
228 
229  beginTest ("adding zones");
230  {
231  MPEZoneLayout layout;
232 
233  layout.setLowerZone (7);
234 
235  expect (layout.getLowerZone().isActive());
236  expect (! layout.getUpperZone().isActive());
237  expectEquals (layout.getLowerZone().getMasterChannel(), 1);
238  expectEquals (layout.getLowerZone().numMemberChannels, 7);
239 
240  layout.setUpperZone (7);
241 
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);
248 
249  layout.setLowerZone (3);
250 
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);
257 
258  layout.setUpperZone (3);
259 
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);
266 
267  layout.setLowerZone (15);
268 
269  expect (layout.getLowerZone().isActive());
270  expect (! layout.getUpperZone().isActive());
271  expectEquals (layout.getLowerZone().getMasterChannel(), 1);
272  expectEquals (layout.getLowerZone().numMemberChannels, 15);
273  }
274 
275  beginTest ("clear all zones");
276  {
277  MPEZoneLayout layout;
278 
279  expect (! layout.getLowerZone().isActive());
280  expect (! layout.getUpperZone().isActive());
281 
282  layout.setLowerZone (7);
283  layout.setUpperZone (2);
284 
285  expect (layout.getLowerZone().isActive());
286  expect (layout.getUpperZone().isActive());
287 
288  layout.clearAllZones();
289 
290  expect (! layout.getLowerZone().isActive());
291  expect (! layout.getUpperZone().isActive());
292  }
293 
294  beginTest ("process MIDI buffers");
295  {
296  MPEZoneLayout layout;
297  MidiBuffer buffer;
298 
299  buffer = MPEMessages::setLowerZone (7);
300  layout.processNextMidiBuffer (buffer);
301 
302  expect (layout.getLowerZone().isActive());
303  expect (! layout.getUpperZone().isActive());
304  expectEquals (layout.getLowerZone().getMasterChannel(), 1);
305  expectEquals (layout.getLowerZone().numMemberChannels, 7);
306 
307  buffer = MPEMessages::setUpperZone (7);
308  layout.processNextMidiBuffer (buffer);
309 
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);
316 
317  {
318  buffer = MPEMessages::setLowerZone (10);
319  layout.processNextMidiBuffer (buffer);
320 
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);
327 
328 
329  buffer = MPEMessages::setLowerZone (10, 33, 44);
330  layout.processNextMidiBuffer (buffer);
331 
332  expectEquals (layout.getLowerZone().numMemberChannels, 10);
333  expectEquals (layout.getLowerZone().perNotePitchbendRange, 33);
334  expectEquals (layout.getLowerZone().masterPitchbendRange, 44);
335  }
336 
337  {
338  buffer = MPEMessages::setUpperZone (10);
339  layout.processNextMidiBuffer (buffer);
340 
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);
347 
348  buffer = MPEMessages::setUpperZone (10, 33, 44);
349 
350  layout.processNextMidiBuffer (buffer);
351 
352  expectEquals (layout.getUpperZone().numMemberChannels, 10);
353  expectEquals (layout.getUpperZone().perNotePitchbendRange, 33);
354  expectEquals (layout.getUpperZone().masterPitchbendRange, 44);
355  }
356 
357  buffer = MPEMessages::clearAllZones();
358  layout.processNextMidiBuffer (buffer);
359 
360  expect (! layout.getLowerZone().isActive());
361  expect (! layout.getUpperZone().isActive());
362  }
363 
364  beginTest ("process individual MIDI messages");
365  {
366  MPEZoneLayout layout;
367 
368  layout.processNextMidiEvent ({ 0x80, 0x59, 0xd0 }); // unrelated note-off msg
369  layout.processNextMidiEvent ({ 0xb0, 0x64, 0x06 }); // RPN part 1
370  layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 }); // RPN part 2
371  layout.processNextMidiEvent ({ 0xb8, 0x0b, 0x66 }); // unrelated CC msg
372  layout.processNextMidiEvent ({ 0xb0, 0x06, 0x03 }); // RPN part 3
373  layout.processNextMidiEvent ({ 0x90, 0x60, 0x00 }); // unrelated note-on msg
374 
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);
381  }
382  }
383 };
384 
385 static MPEZoneLayoutTests MPEZoneLayoutUnitTests;
386 
387 
388 #endif
389 
390 } // namespace juce
static MidiBuffer setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2)
static MidiBuffer clearAllZones()
static MidiBuffer setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2)
static const int zoneLayoutMessagesRpnNumber
void processNextMidiBuffer(const MidiBuffer &buffer)
void setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
void removeListener(Listener *const listenerToRemove) noexcept
void addListener(Listener *const listenerToAdd) noexcept
MPEZoneLayout & operator=(const MPEZoneLayout &other)
void setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
void processNextMidiEvent(const MidiMessage &message)
bool getNextEvent(MidiMessage &result, int &samplePosition) noexcept
int getChannel() const noexcept
bool isController() const noexcept
int getControllerNumber() const noexcept
int getControllerValue() const noexcept
bool parseControllerMessage(int midiChannel, int controllerNumber, int controllerValue, MidiRPNMessage &result) noexcept