OpenShot Audio Library | OpenShotAudio  0.6.0
juce_MPEZoneLayout.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2022 - Raw Material Software Limited
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  : lowerZone (lower), upperZone (upper)
28 {
29 }
30 
32  : lowerZone (zone.isLowerZone() ? zone : MPEZone()),
33  upperZone (! zone.isLowerZone() ? zone : MPEZone())
34 {
35 }
36 
37 
39  : lowerZone (other.lowerZone),
40  upperZone (other.upperZone)
41 {
42 }
43 
44 MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other)
45 {
46  lowerZone = other.lowerZone;
47  upperZone = other.upperZone;
48 
49  sendLayoutChangeMessage();
50 
51  return *this;
52 }
53 
54 void MPEZoneLayout::sendLayoutChangeMessage()
55 {
56  listeners.call ([this] (Listener& l) { l.zoneLayoutChanged (*this); });
57 }
58 
59 //==============================================================================
60 void MPEZoneLayout::setZone (bool isLower, int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
61 {
62  checkAndLimitZoneParameters (0, 15, numMemberChannels);
63  checkAndLimitZoneParameters (0, 96, perNotePitchbendRange);
64  checkAndLimitZoneParameters (0, 96, masterPitchbendRange);
65 
66  if (isLower)
67  lowerZone = { MPEZone::Type::lower, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
68  else
69  upperZone = { MPEZone::Type::upper, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
70 
71  if (numMemberChannels > 0)
72  {
73  auto totalChannels = lowerZone.numMemberChannels + upperZone.numMemberChannels;
74 
75  if (totalChannels >= 15)
76  {
77  if (isLower)
78  upperZone.numMemberChannels = 14 - numMemberChannels;
79  else
80  lowerZone.numMemberChannels = 14 - numMemberChannels;
81  }
82  }
83 
84  sendLayoutChangeMessage();
85 }
86 
87 void MPEZoneLayout::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
88 {
89  setZone (true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
90 }
91 
92 void MPEZoneLayout::setUpperZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
93 {
94  setZone (false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
95 }
96 
98 {
99  lowerZone = { MPEZone::Type::lower, 0 };
100  upperZone = { MPEZone::Type::upper, 0 };
101 
102  sendLayoutChangeMessage();
103 }
104 
105 //==============================================================================
107 {
108  if (! message.isController())
109  return;
110 
111  if (auto parsed = rpnDetector.tryParse (message.getChannel(),
112  message.getControllerNumber(),
113  message.getControllerValue()))
114  {
115  processRpnMessage (*parsed);
116  }
117 }
118 
119 void MPEZoneLayout::processRpnMessage (MidiRPNMessage rpn)
120 {
122  processZoneLayoutRpnMessage (rpn);
123  else if (rpn.parameterNumber == 0)
124  processPitchbendRangeRpnMessage (rpn);
125 }
126 
127 void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn)
128 {
129  if (rpn.value < 16)
130  {
131  if (rpn.channel == 1)
132  setLowerZone (rpn.value);
133  else if (rpn.channel == 16)
134  setUpperZone (rpn.value);
135  }
136 }
137 
138 void MPEZoneLayout::updateMasterPitchbend (MPEZone& zone, int value)
139 {
140  if (zone.masterPitchbendRange != value)
141  {
142  checkAndLimitZoneParameters (0, 96, zone.masterPitchbendRange);
143  zone.masterPitchbendRange = value;
144  sendLayoutChangeMessage();
145  }
146 }
147 
148 void MPEZoneLayout::updatePerNotePitchbendRange (MPEZone& zone, int value)
149 {
150  if (zone.perNotePitchbendRange != value)
151  {
152  checkAndLimitZoneParameters (0, 96, zone.perNotePitchbendRange);
153  zone.perNotePitchbendRange = value;
154  sendLayoutChangeMessage();
155  }
156 }
157 
158 void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn)
159 {
160  if (rpn.channel == 1)
161  {
162  updateMasterPitchbend (lowerZone, rpn.value);
163  }
164  else if (rpn.channel == 16)
165  {
166  updateMasterPitchbend (upperZone, rpn.value);
167  }
168  else
169  {
170  if (lowerZone.isUsingChannelAsMemberChannel (rpn.channel))
171  updatePerNotePitchbendRange (lowerZone, rpn.value);
172  else if (upperZone.isUsingChannelAsMemberChannel (rpn.channel))
173  updatePerNotePitchbendRange (upperZone, rpn.value);
174  }
175 }
176 
178 {
179  for (const auto metadata : buffer)
180  processNextMidiEvent (metadata.getMessage());
181 }
182 
183 //==============================================================================
184 void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept
185 {
186  listeners.add (listenerToAdd);
187 }
188 
189 void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept
190 {
191  listeners.remove (listenerToRemove);
192 }
193 
194 //==============================================================================
195 void MPEZoneLayout::checkAndLimitZoneParameters (int minValue, int maxValue,
196  int& valueToCheckAndLimit) noexcept
197 {
198  if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue)
199  {
200  // if you hit this, one of the parameters you supplied for this zone
201  // was not within the allowed range!
202  // we fit this back into the allowed range here to maintain a valid
203  // state for the zone, but probably the resulting zone is not what you
204  // wanted it to be!
205  jassertfalse;
206 
207  valueToCheckAndLimit = jlimit (minValue, maxValue, valueToCheckAndLimit);
208  }
209 }
210 
211 
212 //==============================================================================
213 //==============================================================================
214 #if JUCE_UNIT_TESTS
215 
216 class MPEZoneLayoutTests final : public UnitTest
217 {
218 public:
219  MPEZoneLayoutTests()
220  : UnitTest ("MPEZoneLayout class", UnitTestCategories::midi)
221  {}
222 
223  void runTest() override
224  {
225  beginTest ("initialisation");
226  {
227  MPEZoneLayout layout;
228  expect (! layout.getLowerZone().isActive());
229  expect (! layout.getUpperZone().isActive());
230  }
231 
232  beginTest ("adding zones");
233  {
234  MPEZoneLayout layout;
235 
236  layout.setLowerZone (7);
237 
238  expect (layout.getLowerZone().isActive());
239  expect (! layout.getUpperZone().isActive());
240  expectEquals (layout.getLowerZone().getMasterChannel(), 1);
241  expectEquals (layout.getLowerZone().numMemberChannels, 7);
242 
243  layout.setUpperZone (7);
244 
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);
251 
252  layout.setLowerZone (3);
253 
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);
260 
261  layout.setUpperZone (3);
262 
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);
269 
270  layout.setLowerZone (15);
271 
272  expect (layout.getLowerZone().isActive());
273  expect (! layout.getUpperZone().isActive());
274  expectEquals (layout.getLowerZone().getMasterChannel(), 1);
275  expectEquals (layout.getLowerZone().numMemberChannels, 15);
276  }
277 
278  beginTest ("clear all zones");
279  {
280  MPEZoneLayout layout;
281 
282  expect (! layout.getLowerZone().isActive());
283  expect (! layout.getUpperZone().isActive());
284 
285  layout.setLowerZone (7);
286  layout.setUpperZone (2);
287 
288  expect (layout.getLowerZone().isActive());
289  expect (layout.getUpperZone().isActive());
290 
291  layout.clearAllZones();
292 
293  expect (! layout.getLowerZone().isActive());
294  expect (! layout.getUpperZone().isActive());
295  }
296 
297  beginTest ("process MIDI buffers");
298  {
299  MPEZoneLayout layout;
300  MidiBuffer buffer;
301 
302  buffer = MPEMessages::setLowerZone (7);
303  layout.processNextMidiBuffer (buffer);
304 
305  expect (layout.getLowerZone().isActive());
306  expect (! layout.getUpperZone().isActive());
307  expectEquals (layout.getLowerZone().getMasterChannel(), 1);
308  expectEquals (layout.getLowerZone().numMemberChannels, 7);
309 
310  buffer = MPEMessages::setUpperZone (7);
311  layout.processNextMidiBuffer (buffer);
312 
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);
319 
320  {
321  buffer = MPEMessages::setLowerZone (10);
322  layout.processNextMidiBuffer (buffer);
323 
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);
330 
331 
332  buffer = MPEMessages::setLowerZone (10, 33, 44);
333  layout.processNextMidiBuffer (buffer);
334 
335  expectEquals (layout.getLowerZone().numMemberChannels, 10);
336  expectEquals (layout.getLowerZone().perNotePitchbendRange, 33);
337  expectEquals (layout.getLowerZone().masterPitchbendRange, 44);
338  }
339 
340  {
341  buffer = MPEMessages::setUpperZone (10);
342  layout.processNextMidiBuffer (buffer);
343 
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);
350 
351  buffer = MPEMessages::setUpperZone (10, 33, 44);
352 
353  layout.processNextMidiBuffer (buffer);
354 
355  expectEquals (layout.getUpperZone().numMemberChannels, 10);
356  expectEquals (layout.getUpperZone().perNotePitchbendRange, 33);
357  expectEquals (layout.getUpperZone().masterPitchbendRange, 44);
358  }
359 
360  buffer = MPEMessages::clearAllZones();
361  layout.processNextMidiBuffer (buffer);
362 
363  expect (! layout.getLowerZone().isActive());
364  expect (! layout.getUpperZone().isActive());
365  }
366 
367  beginTest ("process individual MIDI messages");
368  {
369  MPEZoneLayout layout;
370 
371  layout.processNextMidiEvent ({ 0x80, 0x59, 0xd0 }); // unrelated note-off msg
372  layout.processNextMidiEvent ({ 0xb0, 0x64, 0x06 }); // RPN part 1
373  layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 }); // RPN part 2
374  layout.processNextMidiEvent ({ 0xb8, 0x0b, 0x66 }); // unrelated CC msg
375  layout.processNextMidiEvent ({ 0xb0, 0x06, 0x03 }); // RPN part 3
376  layout.processNextMidiEvent ({ 0x90, 0x60, 0x00 }); // unrelated note-on msg
377 
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);
384 
385  const auto masterPitchBend = 0x0c;
386  layout.processNextMidiEvent ({ 0xb0, 0x64, 0x00 });
387  layout.processNextMidiEvent ({ 0xb0, 0x06, masterPitchBend });
388 
389  expectEquals (layout.getLowerZone().masterPitchbendRange, masterPitchBend);
390 
391  const auto newPitchBend = 0x0d;
392  layout.processNextMidiEvent ({ 0xb0, 0x06, newPitchBend });
393 
394  expectEquals (layout.getLowerZone().masterPitchbendRange, newPitchBend);
395  }
396  }
397 };
398 
399 static MPEZoneLayoutTests MPEZoneLayoutUnitTests;
400 
401 
402 #endif
403 
404 } // 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)
MPEZoneLayout()=default
void setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
void removeListener(Listener *const listenerToRemove) noexcept
void addListener(Listener *const listenerToAdd) noexcept
void setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
void processNextMidiEvent(const MidiMessage &message)
int getChannel() const noexcept
bool isController() const noexcept
int getControllerNumber() const noexcept
int getControllerValue() const noexcept
std::optional< MidiRPNMessage > tryParse(int midiChannel, int controllerNumber, int controllerValue)