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 (
auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
57 if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber)
59 midiChannelLastAssigned = ch;
60 midiChannels[ch].notes.add (noteNumber);
65 for (
auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
67 if (ch == lastChannel + channelIncrement)
70 if (midiChannels[ch].isFree())
72 midiChannelLastAssigned = ch;
73 midiChannels[ch].notes.add (noteNumber);
77 if (ch == midiChannelLastAssigned)
81 midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber);
82 midiChannels[midiChannelLastAssigned].notes.add (noteNumber);
84 return midiChannelLastAssigned;
89 const auto removeNote = [] (MidiChannel& ch,
int noteNum)
91 if (ch.notes.removeAllInstancesOf (noteNum) > 0)
93 ch.lastNotePlayed = noteNum;
100 if (midiChannel >= 0 && midiChannel < 17)
102 removeNote (midiChannels[midiChannel], noteNumber);
106 for (
auto& ch : midiChannels)
108 if (removeNote (ch, noteNumber))
115 for (
auto& ch : midiChannels)
117 if (ch.notes.size() > 0)
118 ch.lastNotePlayed = ch.notes.getLast();
124 int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (
int noteNumber) noexcept
126 auto channelWithClosestNote = firstChannel;
127 int closestNoteDistance = 127;
129 for (
auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
131 for (
auto note : midiChannels[ch].notes)
133 auto noteDistance = std::abs (note - noteNumber);
135 if (noteDistance > 0 && noteDistance < closestNoteDistance)
137 closestNoteDistance = noteDistance;
138 channelWithClosestNote = ch;
143 return channelWithClosestNote;
148 : zone (zoneToRemap),
149 channelIncrement (zone.isLowerZone() ? 1 : -1),
150 firstChannel (zone.getFirstMemberChannel()),
151 lastChannel (zone.getLastMemberChannel())
154 jassert (zone.numMemberChannels > 0);
160 auto channel = message.getChannel();
162 if (! zone.isUsingChannelAsMemberChannel (channel))
165 if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff()))
171 auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel));
173 if (messageIsNoteData (message))
178 if (applyRemapIfExisting (channel, sourceAndChannelID, message))
182 for (
int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
183 if (applyRemapIfExisting (chan, sourceAndChannelID, message))
187 if (sourceAndChannel[channel] ==
notMPE)
189 lastUsed[channel] = counter;
190 sourceAndChannel[channel] = sourceAndChannelID;
195 auto chan = getBestChanToReuse();
197 sourceAndChannel[chan] = sourceAndChannelID;
198 lastUsed[chan] = counter;
199 message.setChannel (chan);
205 for (
auto& s : sourceAndChannel)
211 sourceAndChannel[channel] =
notMPE;
216 for (
auto& s : sourceAndChannel)
218 if (uint32 (s >> 5) == mpeSourceID)
226 bool MPEChannelRemapper::applyRemapIfExisting (
int channel, uint32 sourceAndChannelID,
MidiMessage& m) noexcept
228 if (sourceAndChannel[channel] == sourceAndChannelID)
231 sourceAndChannel[channel] =
notMPE;
233 lastUsed[channel] = counter;
242 int MPEChannelRemapper::getBestChanToReuse()
const noexcept
244 for (
int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
245 if (sourceAndChannel[chan] ==
notMPE)
248 auto bestChan = firstChannel;
249 auto bestLastUse = counter;
251 for (
int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
253 if (lastUsed[chan] < bestLastUse)
255 bestLastUse = lastUsed[chan];
263 void MPEChannelRemapper::zeroArrays()
265 for (
int i = 0; i < 17; ++i)
267 sourceAndChannel[i] = 0;
277 struct MPEUtilsUnitTests :
public UnitTest 280 :
UnitTest (
"MPE Utilities", UnitTestCategories::midi)
283 void runTest()
override 285 beginTest (
"MPEChannelAssigner");
298 for (
int ch = 2; ch <= 16; ++ch)
299 expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
303 expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
305 channelAssigner.noteOff (61);
306 expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
309 channelAssigner.noteOff (65);
310 channelAssigner.noteOff (66);
311 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
312 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
315 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
316 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
319 channelAssigner.allNotesOff();
322 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
323 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
324 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
325 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
328 expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
329 expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
341 for (
int ch = 15; ch >= 1; --ch)
342 expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
346 expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
348 channelAssigner.noteOff (61);
349 expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
352 channelAssigner.noteOff (65);
353 channelAssigner.noteOff (66);
354 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
355 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
358 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
359 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
362 channelAssigner.allNotesOff();
365 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
366 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
367 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
368 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
371 expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
372 expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
381 for (
int ch = 1; ch <= 16; ++ch)
416 beginTest (
"MPEChannelRemapper");
419 const int sourceID1 = 0;
420 const int sourceID2 = 1;
421 const int sourceID3 = 2;
432 for (
int ch = 2; ch <= 16; ++ch)
436 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
437 expectEquals (noteOn.getChannel(), ch);
443 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
444 expectEquals (noteOn.getChannel(), 2);
447 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
448 expectEquals (noteOn.getChannel(), 3);
452 channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
453 expectEquals (noteOff.getChannel(), 3);
463 for (
int ch = 15; ch >= 1; --ch)
467 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
468 expectEquals (noteOn.getChannel(), ch);
474 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
475 expectEquals (noteOn.getChannel(), 15);
478 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
479 expectEquals (noteOn.getChannel(), 14);
483 channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
484 expectEquals (noteOff.getChannel(), 14);
490 static MPEUtilsUnitTests MPEUtilsUnitTests;
void remapMidiChannelIfNeeded(MidiMessage &message, uint32 mpeSourceID) noexcept
void noteOff(int noteNumber, int midiChannel=-1)
MPEChannelAssigner(MPEZoneLayout::Zone zoneToUse)
MPEChannelRemapper(MPEZoneLayout::Zone zoneToRemap)
void setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
void clearChannel(int channel) noexcept
static const uint32 notMPE
const Zone getUpperZone() const noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
void setChannel(int newChannelNumber) noexcept
const Zone getLowerZone() const noexcept
void clearSource(uint32 mpeSourceID)
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
JUCE_CONSTEXPR bool isEmpty() const noexcept
int findMidiChannelForNewNote(int noteNumber) noexcept
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
void setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept