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 for (
auto& ch : midiChannels)
91 if (ch.notes.removeAllInstancesOf (noteNumber) > 0)
93 ch.lastNotePlayed = noteNumber;
101 for (
auto& ch : midiChannels)
103 if (ch.notes.size() > 0)
104 ch.lastNotePlayed = ch.notes.getLast();
110 int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (
int noteNumber) noexcept
112 auto channelWithClosestNote = firstChannel;
113 int closestNoteDistance = 127;
115 for (
auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
117 for (
auto note : midiChannels[ch].notes)
119 auto noteDistance = std::abs (note - noteNumber);
121 if (noteDistance > 0 && noteDistance < closestNoteDistance)
123 closestNoteDistance = noteDistance;
124 channelWithClosestNote = ch;
129 return channelWithClosestNote;
134 : zone (zoneToRemap),
135 channelIncrement (zone.isLowerZone() ? 1 : -1),
136 firstChannel (zone.getFirstMemberChannel()),
137 lastChannel (zone.getLastMemberChannel())
140 jassert (zone.numMemberChannels > 0);
146 auto channel = message.getChannel();
148 if (! zone.isUsingChannelAsMemberChannel (channel))
151 if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff()))
157 auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel));
159 if (messageIsNoteData (message))
164 if (applyRemapIfExisting (channel, sourceAndChannelID, message))
168 for (
int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
169 if (applyRemapIfExisting (chan, sourceAndChannelID, message))
173 if (sourceAndChannel[channel] ==
notMPE)
175 lastUsed[channel] = counter;
176 sourceAndChannel[channel] = sourceAndChannelID;
181 auto chan = getBestChanToReuse();
183 sourceAndChannel[chan] = sourceAndChannelID;
184 lastUsed[chan] = counter;
185 message.setChannel (chan);
191 for (
auto& s : sourceAndChannel)
197 sourceAndChannel[channel] =
notMPE;
202 for (
auto& s : sourceAndChannel)
204 if (uint32 (s >> 5) == mpeSourceID)
212 bool MPEChannelRemapper::applyRemapIfExisting (
int channel, uint32 sourceAndChannelID,
MidiMessage& m) noexcept
214 if (sourceAndChannel[channel] == sourceAndChannelID)
217 sourceAndChannel[channel] =
notMPE;
219 lastUsed[channel] = counter;
228 int MPEChannelRemapper::getBestChanToReuse()
const noexcept
230 for (
int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
231 if (sourceAndChannel[chan] ==
notMPE)
234 auto bestChan = firstChannel;
235 auto bestLastUse = counter;
237 for (
int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement)
239 if (lastUsed[chan] < bestLastUse)
241 bestLastUse = lastUsed[chan];
249 void MPEChannelRemapper::zeroArrays()
251 for (
int i = 0; i < 17; ++i)
253 sourceAndChannel[i] = 0;
262 struct MPEUtilsUnitTests :
public UnitTest 265 :
UnitTest (
"MPE Utilities",
"MIDI/MPE")
268 void runTest()
override 270 beginTest (
"MPEChannelAssigner");
283 for (
int ch = 2; ch <= 16; ++ch)
284 expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
288 expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
290 channelAssigner.noteOff (61);
291 expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
294 channelAssigner.noteOff (65);
295 channelAssigner.noteOff (66);
296 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
297 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
300 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
301 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
304 channelAssigner.allNotesOff();
307 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
308 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
309 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
310 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
313 expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
314 expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
326 for (
int ch = 15; ch >= 1; --ch)
327 expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
331 expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
333 channelAssigner.noteOff (61);
334 expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
337 channelAssigner.noteOff (65);
338 channelAssigner.noteOff (66);
339 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
340 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
343 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
344 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
347 channelAssigner.allNotesOff();
350 expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
351 expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
352 expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
353 expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
356 expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
357 expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
366 for (
int ch = 1; ch <= 16; ++ch)
401 beginTest (
"MPEChannelRemapper");
404 const int sourceID1 = 0;
405 const int sourceID2 = 1;
406 const int sourceID3 = 2;
417 for (
int ch = 2; ch <= 16; ++ch)
421 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
422 expectEquals (noteOn.getChannel(), ch);
428 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
429 expectEquals (noteOn.getChannel(), 2);
432 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
433 expectEquals (noteOn.getChannel(), 3);
437 channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
438 expectEquals (noteOff.getChannel(), 3);
448 for (
int ch = 15; ch >= 1; --ch)
452 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1);
453 expectEquals (noteOn.getChannel(), ch);
459 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2);
460 expectEquals (noteOn.getChannel(), 15);
463 channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3);
464 expectEquals (noteOn.getChannel(), 14);
468 channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3);
469 expectEquals (noteOff.getChannel(), 14);
475 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.
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 noteOff(int noteNumber)
You must call this method for all note-offs that you receive so that this class can keep track of the...
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.