28 channelIncrement (zone->isLowerZone() ? 1 : -1),
29 numChannels (zone->numMemberChannels),
30 firstChannel (zone->getFirstMemberChannel()),
31 lastChannel (zone->getLastMemberChannel()),
32 midiChannelLastAssigned (firstChannel - channelIncrement)
35 jassert (numChannels > 0);
41 numChannels (channelRange.getLength()),
42 firstChannel (channelRange.getStart()),
43 lastChannel (channelRange.getEnd() - 1),
44 midiChannelLastAssigned (firstChannel - channelIncrement)
47 jassert (! channelRange.
isEmpty());
55 for (
int ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
57 if (midiChannels[(
size_t) ch].isFree() && midiChannels[(
size_t) ch].lastNotePlayed == noteNumber)
59 midiChannelLastAssigned = ch;
60 midiChannels[(size_t) ch].notes.add (noteNumber);
65 for (
int ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
67 if (ch == lastChannel + channelIncrement)
70 if (midiChannels[(
size_t) ch].isFree())
72 midiChannelLastAssigned = ch;
73 midiChannels[(size_t) ch].notes.add (noteNumber);
77 if (ch == midiChannelLastAssigned)
81 midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber);
82 midiChannels[(size_t) midiChannelLastAssigned].notes.add (noteNumber);
84 return midiChannelLastAssigned;
89 const auto iter = std::find_if (midiChannels.cbegin(), midiChannels.cend(), [&] (
auto& ch)
91 return std::find (ch.notes.begin(), ch.notes.end(), noteNumber) != ch.notes.end();
94 return iter != midiChannels.cend() ? (int) std::distance (midiChannels.cbegin(), iter) : -1;
99 const auto removeNote = [] (MidiChannel& ch,
int noteNum)
103 ch.lastNotePlayed = noteNum;
110 if (midiChannel >= 0 && midiChannel <= 16)
112 removeNote (midiChannels[(
size_t) midiChannel], noteNumber);
116 for (
auto& ch : midiChannels)
118 if (removeNote (ch, noteNumber))
125 for (
auto& ch : midiChannels)
127 if (ch.notes.size() > 0)
128 ch.lastNotePlayed = ch.notes.getLast();
134int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (
int noteNumber)
noexcept
136 auto channelWithClosestNote = firstChannel;
137 int closestNoteDistance = 127;
139 for (
int ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
141 for (
auto note : midiChannels[(
size_t) ch].notes)
143 auto noteDistance = std::abs (note - noteNumber);
145 if (noteDistance > 0 && noteDistance < closestNoteDistance)
147 closestNoteDistance = noteDistance;
148 channelWithClosestNote = ch;
153 return channelWithClosestNote;
158 : zone (zoneToRemap),
159 channelIncrement (zone.isLowerZone() ? 1 : -1),
160 firstChannel (zone.getFirstMemberChannel()),
161 lastChannel (zone.getLastMemberChannel())
164 jassert (zone.numMemberChannels > 0);
170 auto channel = message.getChannel();
172 if (! zone.isUsingChannelAsMemberChannel (channel))
175 if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff()))
177 clearSource (mpeSourceID);
181 auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel));
183 if (messageIsNoteData (message))
188 if (applyRemapIfExisting (channel, sourceAndChannelID, message))
192 for (
int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
193 if (applyRemapIfExisting (chan, sourceAndChannelID, message))
197 if (sourceAndChannel[channel] == notMPE)
199 lastUsed[channel] = counter;
200 sourceAndChannel[channel] = sourceAndChannelID;
205 auto chan = getBestChanToReuse();
207 sourceAndChannel[chan] = sourceAndChannelID;
208 lastUsed[chan] = counter;
209 message.setChannel (chan);
215 for (
auto& s : sourceAndChannel)
221 sourceAndChannel[channel] = notMPE;
226 for (
auto& s : sourceAndChannel)
228 if (uint32 (s >> 5) == mpeSourceID)
236bool MPEChannelRemapper::applyRemapIfExisting (
int channel, uint32 sourceAndChannelID,
MidiMessage& m)
noexcept
238 if (sourceAndChannel[channel] == sourceAndChannelID)
241 sourceAndChannel[channel] = notMPE;
243 lastUsed[channel] = counter;
245 m.setChannel (channel);
252int MPEChannelRemapper::getBestChanToReuse() const noexcept
254 for (
int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
255 if (sourceAndChannel[chan] ==
notMPE)
258 auto bestChan = firstChannel;
259 auto bestLastUse = counter;
261 for (
int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
263 if (lastUsed[chan] < bestLastUse)
265 bestLastUse = lastUsed[chan];
273void MPEChannelRemapper::zeroArrays()
275 for (
int i = 0; i < 17; ++i)
277 sourceAndChannel[i] = 0;
287struct MPEUtilsUnitTests final :
public UnitTest
290 : UnitTest (
"MPE Utilities", UnitTestCategories::midi)
293 void runTest()
override
295 beginTest (
"MPEChannelAssigner");
297 MPEZoneLayout layout;
301 layout.setLowerZone (15);
304 MPEChannelAssigner channelAssigner (layout.getLowerZone());
308 for (
int ch = 2; ch <= 16; ++ch)
310 expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch);
311 expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch);
317 channelAssigner.noteOff (60);
318 expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
319 expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 2);
321 channelAssigner.noteOff (61);
322 expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
323 expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 3);
326 channelAssigner.noteOff (65);
327 channelAssigner.noteOff (66);
328 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
329 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
330 expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 8);
331 expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 7);
334 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
335 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
336 expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
337 expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 2);
340 channelAssigner.allNotesOff();
343 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
344 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
345 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
346 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
347 expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 8);
348 expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 7);
349 expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
350 expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 2);
353 expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
354 expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
355 expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 3);
356 expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 4);
361 layout.setUpperZone (15);
364 MPEChannelAssigner channelAssigner (layout.getUpperZone());
368 for (
int ch = 15; ch >= 1; --ch)
370 expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch);
371 expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch);
377 channelAssigner.noteOff (60);
378 expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
379 expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 15);
381 channelAssigner.noteOff (61);
382 expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
383 expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 14);
386 channelAssigner.noteOff (65);
387 channelAssigner.noteOff (66);
388 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
389 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
390 expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 9);
391 expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 10);
394 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
395 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
396 expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 1);
397 expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 15);
400 channelAssigner.allNotesOff();
403 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
404 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
405 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
406 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
407 expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 9);
408 expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 10);
409 expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 1);
410 expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 15);
413 expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
414 expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
415 expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 14);
416 expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 13);
421 MPEChannelAssigner channelAssigner;
425 for (
int ch = 1; ch <= 16; ++ch)
427 expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch);
428 expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch);
434 channelAssigner.noteOff (60);
435 expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1);
436 expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 1);
438 channelAssigner.noteOff (61);
439 expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2);
440 expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 2);
443 channelAssigner.noteOff (65);
444 channelAssigner.noteOff (66);
445 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
446 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
447 expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 7);
448 expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 6);
451 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
452 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
453 expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
454 expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 1);
457 channelAssigner.allNotesOff();
460 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
461 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
462 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
463 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
464 expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 7);
465 expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 6);
466 expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
467 expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 1);
470 expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2);
471 expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3);
472 expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 2);
473 expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 3);
477 beginTest (
"MPEChannelRemapper");
480 const int sourceID1 = 0;
481 const int sourceID2 = 1;
482 const int sourceID3 = 2;
484 MPEZoneLayout layout;
487 layout.setLowerZone (15);
490 MPEChannelRemapper channelRemapper (layout.getLowerZone());
493 for (
int ch = 2; ch <= 16; ++ch)
495 auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
497 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
498 expectEquals (noteOn.getChannel(), ch);
501 auto noteOn = MidiMessage::noteOn (2, 60, 1.0f);
504 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
505 expectEquals (noteOn.getChannel(), 2);
508 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
509 expectEquals (noteOn.getChannel(), 3);
512 auto noteOff = MidiMessage::noteOff (2, 60, 1.0f);
513 channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
514 expectEquals (noteOff.getChannel(), 3);
518 layout.setUpperZone (15);
521 MPEChannelRemapper channelRemapper (layout.getUpperZone());
524 for (
int ch = 15; ch >= 1; --ch)
526 auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f);
528 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
529 expectEquals (noteOn.getChannel(), ch);
532 auto noteOn = MidiMessage::noteOn (15, 60, 1.0f);
535 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
536 expectEquals (noteOn.getChannel(), 15);
539 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
540 expectEquals (noteOn.getChannel(), 14);
543 auto noteOff = MidiMessage::noteOff (15, 60, 1.0f);
544 channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
545 expectEquals (noteOff.getChannel(), 14);
551static MPEUtilsUnitTests MPEUtilsUnitTests;
int removeAllInstancesOf(ParameterType valueToRemove)
int findMidiChannelForExistingNote(int initialNoteOnNumber) noexcept
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)
constexpr bool isEmpty() const noexcept