OpenShot Audio Library | OpenShotAudio 0.4.0

Loading...
Searching...
No Matches
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
26 MPEZoneLayout::MPEZoneLayout (MPEZone lower, MPEZone upper)
27 : lowerZone (lower), upperZone (upper)
28{
29}
30
31 MPEZoneLayout::MPEZoneLayout (MPEZone zone)
32 : lowerZone (zone.isLowerZone() ? zone : MPEZone()),
33 upperZone (! zone.isLowerZone() ? zone : MPEZone())
34{
35}
36
37
38 MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other)
39 : lowerZone (other.lowerZone),
40 upperZone (other.upperZone)
41{
42}
43
44MPEZoneLayout& 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
97 void MPEZoneLayout::clearAllZones()
98{
99 lowerZone = { MPEZone::Type::lower, 0 };
100 upperZone = { MPEZone::Type::upper, 0 };
101
102 sendLayoutChangeMessage();
103}
104
105//==============================================================================
106 void MPEZoneLayout::processNextMidiEvent (const MidiMessage& message)
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{
121 if (rpn.parameterNumber == MPEMessages::zoneLayoutMessagesRpnNumber)
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
177 void MPEZoneLayout::processNextMidiBuffer (const MidiBuffer& buffer)
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
juce::MPEMessages::zoneLayoutMessagesRpnNumber
static const int zoneLayoutMessagesRpnNumber
Definition juce_MPEMessages.h:113
juce::MPEZoneLayout::Listener
Definition juce_MPEZoneLayout.h:198
juce::MPEZoneLayout
Definition juce_MPEZoneLayout.h:123
juce::MPEZoneLayout::processNextMidiBuffer
void processNextMidiBuffer(const MidiBuffer &buffer)
Definition juce_MPEZoneLayout.cpp:177
juce::MPEZoneLayout::MPEZoneLayout
MPEZoneLayout()=default
juce::MPEZoneLayout::clearAllZones
void clearAllZones()
Definition juce_MPEZoneLayout.cpp:97
juce::MPEZoneLayout::setUpperZone
void setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
Definition juce_MPEZoneLayout.cpp:92
juce::MPEZoneLayout::removeListener
void removeListener(Listener *const listenerToRemove) noexcept
Definition juce_MPEZoneLayout.cpp:189
juce::MPEZoneLayout::addListener
void addListener(Listener *const listenerToAdd) noexcept
Definition juce_MPEZoneLayout.cpp:184
juce::MPEZoneLayout::setLowerZone
void setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
Definition juce_MPEZoneLayout.cpp:87
juce::MPEZoneLayout::processNextMidiEvent
void processNextMidiEvent(const MidiMessage &message)
Definition juce_MPEZoneLayout.cpp:106
juce::MidiBuffer
Definition juce_MidiBuffer.h:145
juce::MidiMessage
Definition juce_MidiMessage.h:35
juce::MidiMessage::getChannel
int getChannel() const noexcept
Definition juce_MidiMessage.cpp:379
juce::MidiMessage::isController
bool isController() const noexcept
Definition juce_MidiMessage.cpp:563
juce::MidiMessage::getControllerNumber
int getControllerNumber() const noexcept
Definition juce_MidiMessage.cpp:574
juce::MidiMessage::getControllerValue
int getControllerValue() const noexcept
Definition juce_MidiMessage.cpp:580
juce::MidiRPNDetector::tryParse
std::optional< MidiRPNMessage > tryParse(int midiChannel, int controllerNumber, int controllerValue)
Definition juce_MidiRPN.cpp:40
juce::MPEZone
Definition juce_MPEZoneLayout.h:42
juce::MidiRPNMessage
Definition juce_MidiRPN.h:33
juce::MidiRPNMessage::parameterNumber
int parameterNumber
Definition juce_MidiRPN.h:38