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 reset() noexcept
Resets all the source & channel combinations.
This class represents the current MPE zone layout of a device capable of handling MPE...
void remapMidiChannelIfNeeded(MidiMessage &message, uint32 mpeSourceID) noexcept
Remaps the MIDI channel of the specified MIDI message (if necessary).
Encapsulates a MIDI message.
void noteOff(int noteNumber, int midiChannel=-1)
You must call this method for all note-offs that you receive so that this class can keep track of the...
MPEChannelAssigner(MPEZoneLayout::Zone zoneToUse)
Constructor.
This struct represents an MPE zone.
This class handles the logic for remapping MIDI note messages from multiple MPE sources onto a specif...
MPEChannelRemapper(MPEZoneLayout::Zone zoneToRemap)
Constructor.
void setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
Sets the upper zone of this layout.
void clearChannel(int channel) noexcept
Clears a specified channel of this MPE zone.
static const uint32 notMPE
Used to indicate that a particular source & channel combination is not currently using MPE...
const Zone getUpperZone() const noexcept
Returns a struct representing the upper MPE zone.
This is a base class for classes that perform a unit test.
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
Creates a key-up message.
void setChannel(int newChannelNumber) noexcept
Changes the message's midi channel.
const Zone getLowerZone() const noexcept
Returns a struct representing the lower MPE zone.
void clearSource(uint32 mpeSourceID)
Clears all channels in use by a specified source.
This class handles the assignment of new MIDI notes to member channels of an active MPE zone...
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
Returns true if this message is a 'key-up' event.
JUCE_CONSTEXPR bool isEmpty() const noexcept
Returns true if the range has a length of zero.
void allNotesOff()
Call this to clear all currently playing notes.
int findMidiChannelForNewNote(int noteNumber) noexcept
This method will use a set of rules recommended in the MPE specification to determine which member ch...
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
Creates a key-down message (using a floating-point velocity).
void setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
Sets the lower zone of this layout.