OpenShot Audio Library | OpenShotAudio 0.4.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
23namespace 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
44MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other)
45{
46 lowerZone = other.lowerZone;
47 upperZone = other.upperZone;
48
49 sendLayoutChangeMessage();
50
51 return *this;
52}
53
54void MPEZoneLayout::sendLayoutChangeMessage()
55{
56 listeners.call ([this] (Listener& l) { l.zoneLayoutChanged (*this); });
57}
58
59//==============================================================================
60void 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
87void MPEZoneLayout::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept
88{
89 setZone (true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange);
90}
91
92void 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
119void MPEZoneLayout::processRpnMessage (MidiRPNMessage rpn)
120{
122 processZoneLayoutRpnMessage (rpn);
123 else if (rpn.parameterNumber == 0)
124 processPitchbendRangeRpnMessage (rpn);
125}
126
127void 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
138void 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
148void 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
158void 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//==============================================================================
184void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept
185{
186 listeners.add (listenerToAdd);
187}
188
189void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept
190{
191 listeners.remove (listenerToRemove);
192}
193
194//==============================================================================
195void 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
216class MPEZoneLayoutTests final : public UnitTest
217{
218public:
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
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
399static 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)