OpenShot Audio Library | OpenShotAudio 0.3.2
Loading...
Searching...
No Matches
juce_MPEUtils.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
23namespace juce
24{
25
27 : zone (new MPEZoneLayout::Zone (zoneToUse)),
28 channelIncrement (zone->isLowerZone() ? 1 : -1),
29 numChannels (zone->numMemberChannels),
30 firstChannel (zone->getFirstMemberChannel()),
31 lastChannel (zone->getLastMemberChannel()),
32 midiChannelLastAssigned (firstChannel - channelIncrement)
33{
34 // must be an active MPE zone!
35 jassert (numChannels > 0);
36}
37
39 : isLegacy (true),
40 channelIncrement (1),
41 numChannels (channelRange.getLength()),
42 firstChannel (channelRange.getStart()),
43 lastChannel (channelRange.getEnd() - 1),
44 midiChannelLastAssigned (firstChannel - channelIncrement)
45{
46 // must have at least one channel!
47 jassert (! channelRange.isEmpty());
48}
49
51{
52 if (numChannels <= 1)
53 return firstChannel;
54
55 for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
56 {
57 if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber)
58 {
59 midiChannelLastAssigned = ch;
60 midiChannels[ch].notes.add (noteNumber);
61 return ch;
62 }
63 }
64
65 for (auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
66 {
67 if (ch == lastChannel + channelIncrement) // loop wrap-around
68 ch = firstChannel;
69
70 if (midiChannels[ch].isFree())
71 {
72 midiChannelLastAssigned = ch;
73 midiChannels[ch].notes.add (noteNumber);
74 return ch;
75 }
76
77 if (ch == midiChannelLastAssigned)
78 break; // no free channels!
79 }
80
81 midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber);
82 midiChannels[midiChannelLastAssigned].notes.add (noteNumber);
83
84 return midiChannelLastAssigned;
85}
86
87void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel)
88{
89 const auto removeNote = [] (MidiChannel& ch, int noteNum)
90 {
91 if (ch.notes.removeAllInstancesOf (noteNum) > 0)
92 {
93 ch.lastNotePlayed = noteNum;
94 return true;
95 }
96
97 return false;
98 };
99
100 if (midiChannel >= 0 && midiChannel < 17)
101 {
102 removeNote (midiChannels[midiChannel], noteNumber);
103 return;
104 }
105
106 for (auto& ch : midiChannels)
107 {
108 if (removeNote (ch, noteNumber))
109 return;
110 }
111}
112
114{
115 for (auto& ch : midiChannels)
116 {
117 if (ch.notes.size() > 0)
118 ch.lastNotePlayed = ch.notes.getLast();
119
120 ch.notes.clear();
121 }
122}
123
124int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept
125{
126 auto channelWithClosestNote = firstChannel;
127 int closestNoteDistance = 127;
128
129 for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
130 {
131 for (auto note : midiChannels[ch].notes)
132 {
133 auto noteDistance = std::abs (note - noteNumber);
134
136 {
139 }
140 }
141 }
142
143 return channelWithClosestNote;
144}
145
146//==============================================================================
148 : zone (zoneToRemap),
149 channelIncrement (zone.isLowerZone() ? 1 : -1),
150 firstChannel (zone.getFirstMemberChannel()),
151 lastChannel (zone.getLastMemberChannel())
152{
153 // must be an active MPE zone!
154 jassert (zone.numMemberChannels > 0);
155 zeroArrays();
156}
157
159{
160 auto channel = message.getChannel();
161
162 if (! zone.isUsingChannelAsMemberChannel (channel))
163 return;
164
165 if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff()))
166 {
167 clearSource (mpeSourceID);
168 return;
169 }
170
171 auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel));
172
173 if (messageIsNoteData (message))
174 {
175 ++counter;
176
177 // fast path - no remap
178 if (applyRemapIfExisting (channel, sourceAndChannelID, message))
179 return;
180
181 // find existing remap
182 for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
183 if (applyRemapIfExisting (chan, sourceAndChannelID, message))
184 return;
185
186 // no remap necessary
187 if (sourceAndChannel[channel] == notMPE)
188 {
189 lastUsed[channel] = counter;
190 sourceAndChannel[channel] = sourceAndChannelID;
191 return;
192 }
193
194 // remap source & channel to new channel
195 auto chan = getBestChanToReuse();
196
197 sourceAndChannel[chan] = sourceAndChannelID;
198 lastUsed[chan] = counter;
199 message.setChannel (chan);
200 }
201}
202
204{
205 for (auto& s : sourceAndChannel)
206 s = notMPE;
207}
208
209void MPEChannelRemapper::clearChannel (int channel) noexcept
210{
211 sourceAndChannel[channel] = notMPE;
212}
213
215{
216 for (auto& s : sourceAndChannel)
217 {
218 if (uint32 (s >> 5) == mpeSourceID)
219 {
220 s = notMPE;
221 return;
222 }
223 }
224}
225
226bool MPEChannelRemapper::applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept
227{
228 if (sourceAndChannel[channel] == sourceAndChannelID)
229 {
230 if (m.isNoteOff())
231 sourceAndChannel[channel] = notMPE;
232 else
233 lastUsed[channel] = counter;
234
235 m.setChannel (channel);
236 return true;
237 }
238
239 return false;
240}
241
242int MPEChannelRemapper::getBestChanToReuse() const noexcept
243{
244 for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
245 if (sourceAndChannel[chan] == notMPE)
246 return chan;
247
248 auto bestChan = firstChannel;
249 auto bestLastUse = counter;
250
251 for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
252 {
253 if (lastUsed[chan] < bestLastUse)
254 {
255 bestLastUse = lastUsed[chan];
256 bestChan = chan;
257 }
258 }
259
260 return bestChan;
261}
262
263void MPEChannelRemapper::zeroArrays()
264{
265 for (int i = 0; i < 17; ++i)
266 {
267 sourceAndChannel[i] = 0;
268 lastUsed[i] = 0;
269 }
270}
271
272
273//==============================================================================
274//==============================================================================
275#if JUCE_UNIT_TESTS
276
277struct MPEUtilsUnitTests : public UnitTest
278{
280 : UnitTest ("MPE Utilities", UnitTestCategories::midi)
281 {}
282
283 void runTest() override
284 {
285 beginTest ("MPEChannelAssigner");
286 {
287 MPEZoneLayout layout;
288
289 // lower
290 {
291 layout.setLowerZone (15);
292
293 // lower zone
294 MPEChannelAssigner channelAssigner (layout.getLowerZone());
295
296 // check that channels are assigned in correct order
297 int noteNum = 60;
298 for (int ch = 2; ch <= 16; ++ch)
299 expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
300
301 // check that note-offs are processed
302 channelAssigner.noteOff (60);
303 expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
304
305 channelAssigner.noteOff (61);
306 expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
307
308 // check that assigned channel was last to play note
309 channelAssigner.noteOff (65);
310 channelAssigner.noteOff (66);
311 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
312 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
313
314 // find closest channel playing nonequal note
315 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
316 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
317
318 // all notes off
319 channelAssigner.allNotesOff();
320
321 // last note played
322 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
323 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
324 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
325 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
326
327 // normal assignment
328 expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
329 expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
330 }
331
332 // upper
333 {
334 layout.setUpperZone (15);
335
336 // upper zone
337 MPEChannelAssigner channelAssigner (layout.getUpperZone());
338
339 // check that channels are assigned in correct order
340 int noteNum = 60;
341 for (int ch = 15; ch >= 1; --ch)
342 expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
343
344 // check that note-offs are processed
345 channelAssigner.noteOff (60);
346 expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
347
348 channelAssigner.noteOff (61);
349 expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
350
351 // check that assigned channel was last to play note
352 channelAssigner.noteOff (65);
353 channelAssigner.noteOff (66);
354 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
355 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
356
357 // find closest channel playing nonequal note
358 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
359 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
360
361 // all notes off
362 channelAssigner.allNotesOff();
363
364 // last note played
365 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
366 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
367 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
368 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
369
370 // normal assignment
371 expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
372 expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
373 }
374
375 // legacy
376 {
377 MPEChannelAssigner channelAssigner;
378
379 // check that channels are assigned in correct order
380 int noteNum = 60;
381 for (int ch = 1; ch <= 16; ++ch)
382 expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
383
384 // check that note-offs are processed
385 channelAssigner.noteOff (60);
386 expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1);
387
388 channelAssigner.noteOff (61);
389 expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2);
390
391 // check that assigned channel was last to play note
392 channelAssigner.noteOff (65);
393 channelAssigner.noteOff (66);
394 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
395 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
396
397 // find closest channel playing nonequal note
398 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
399 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
400
401 // all notes off
402 channelAssigner.allNotesOff();
403
404 // last note played
405 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
406 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
407 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
408 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
409
410 // normal assignment
411 expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2);
412 expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3);
413 }
414 }
415
416 beginTest ("MPEChannelRemapper");
417 {
418 // 3 different MPE 'sources', constant IDs
419 const int sourceID1 = 0;
420 const int sourceID2 = 1;
421 const int sourceID3 = 2;
422
423 MPEZoneLayout layout;
424
425 {
426 layout.setLowerZone (15);
427
428 // lower zone
429 MPEChannelRemapper channelRemapper (layout.getLowerZone());
430
431 // first source, shouldn't remap
432 for (int ch = 2; ch <= 16; ++ch)
433 {
434 auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
435
436 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
437 expectEquals (noteOn.getChannel(), ch);
438 }
439
440 auto noteOn = MidiMessage::noteOn (2, 60, 1.0f);
441
442 // remap onto oldest last-used channel
443 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
444 expectEquals (noteOn.getChannel(), 2);
445
446 // remap onto oldest last-used channel
447 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
448 expectEquals (noteOn.getChannel(), 3);
449
450 // remap to correct channel for source ID
451 auto noteOff = MidiMessage::noteOff (2, 60, 1.0f);
452 channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
453 expectEquals (noteOff.getChannel(), 3);
454 }
455
456 {
457 layout.setUpperZone (15);
458
459 // upper zone
460 MPEChannelRemapper channelRemapper (layout.getUpperZone());
461
462 // first source, shouldn't remap
463 for (int ch = 15; ch >= 1; --ch)
464 {
465 auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
466
467 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
468 expectEquals (noteOn.getChannel(), ch);
469 }
470
471 auto noteOn = MidiMessage::noteOn (15, 60, 1.0f);
472
473 // remap onto oldest last-used channel
474 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
475 expectEquals (noteOn.getChannel(), 15);
476
477 // remap onto oldest last-used channel
478 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
479 expectEquals (noteOn.getChannel(), 14);
480
481 // remap to correct channel for source ID
482 auto noteOff = MidiMessage::noteOff (15, 60, 1.0f);
483 channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
484 expectEquals (noteOff.getChannel(), 14);
485 }
486 }
487 }
488};
489
490static MPEUtilsUnitTests MPEUtilsUnitTests;
491
492#endif
493
494} // namespace juce
bool isEmpty() const noexcept
Definition juce_Array.h:222
int removeAllInstancesOf(ParameterType valueToRemove)
Definition juce_Array.h:858
int size() const noexcept
Definition juce_Array.h:215
Array()=default
void add(const ElementType &newElement)
Definition juce_Array.h:418
void clear()
Definition juce_Array.h:188
ElementType getLast() const noexcept
Definition juce_Array.h:300
MPEChannelAssigner(MPEZoneLayout::Zone zoneToUse)
int findMidiChannelForNewNote(int noteNumber) noexcept
void noteOff(int noteNumber, int midiChannel=-1)
void remapMidiChannelIfNeeded(MidiMessage &message, uint32 mpeSourceID) noexcept
static const uint32 notMPE
void clearChannel(int channel) noexcept
void clearSource(uint32 mpeSourceID)
MPEChannelRemapper(MPEZoneLayout::Zone zoneToRemap)
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept