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());
57 if (midiChannels[
ch].isFree() && midiChannels[
ch].lastNotePlayed ==
noteNumber)
59 midiChannelLastAssigned =
ch;
65 for (
auto ch = midiChannelLastAssigned + channelIncrement; ;
ch += channelIncrement)
67 if (
ch == lastChannel + channelIncrement)
70 if (midiChannels[
ch].isFree())
72 midiChannelLastAssigned =
ch;
77 if (
ch == midiChannelLastAssigned)
81 midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (
noteNumber);
82 midiChannels[midiChannelLastAssigned].notes.add (
noteNumber);
84 return midiChannelLastAssigned;
89 for (
auto&
ch : midiChannels)
101 for (
auto&
ch : midiChannels)
110int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (
int noteNumber)
noexcept
117 for (
auto note : midiChannels[
ch].notes)
129 return channelWithClosestNote;
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()))
159 if (messageIsNoteData (message))
173 if (sourceAndChannel[channel] == notMPE)
175 lastUsed[channel] = counter;
181 auto chan = getBestChanToReuse();
184 lastUsed[
chan] = counter;
185 message.setChannel (
chan);
191 for (
auto&
s : sourceAndChannel)
197 sourceAndChannel[channel] = notMPE;
202 for (
auto&
s : sourceAndChannel)
217 sourceAndChannel[channel] = notMPE;
219 lastUsed[channel] = counter;
221 m.setChannel (channel);
228int 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];
249void MPEChannelRemapper::zeroArrays()
251 for (
int i = 0; i < 17; ++i)
253 sourceAndChannel[i] = 0;
262struct MPEUtilsUnitTests :
public UnitTest
265 : UnitTest (
"MPE Utilities",
"MIDI/MPE")
268 void runTest()
override
270 beginTest (
"MPEChannelAssigner");
283 for (
int ch = 2;
ch <= 16; ++
ch)
326 for (
int ch = 15;
ch >= 1; --
ch)
366 for (
int ch = 1;
ch <= 16; ++
ch)
401 beginTest (
"MPEChannelRemapper");
417 for (
int ch = 2;
ch <= 16; ++
ch)
422 expectEquals (noteOn.getChannel(),
ch);
429 expectEquals (noteOn.getChannel(), 2);
433 expectEquals (noteOn.getChannel(), 3);
438 expectEquals (noteOff.getChannel(), 3);
448 for (
int ch = 15;
ch >= 1; --
ch)
453 expectEquals (noteOn.getChannel(),
ch);
460 expectEquals (noteOn.getChannel(), 15);
464 expectEquals (noteOn.getChannel(), 14);
469 expectEquals (noteOff.getChannel(), 14);
475static MPEUtilsUnitTests MPEUtilsUnitTests;
Holds a resizable array of primitive or copy-by-value objects.
bool isEmpty() const noexcept
Returns true if the array is empty, false otherwise.
int removeAllInstancesOf(ParameterType valueToRemove)
Removes items from the array.
int size() const noexcept
Returns the current number of elements in the array.
Array()=default
Creates an empty array.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
void clear()
Removes all elements from the array.
ElementType getLast() const noexcept
Returns the last element in the array, or a default value if the array is empty.
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...
MPEChannelAssigner(MPEZoneLayout::Zone zoneToUse)
Constructor.
int findMidiChannelForNewNote(int noteNumber) noexcept
This method will use a set of rules recommended in the MPE specification to determine which member ch...
void allNotesOff()
Call this to clear all currently playing notes.
void reset() noexcept
Resets all the source & channel combinations.
void remapMidiChannelIfNeeded(MidiMessage &message, uint32 mpeSourceID) noexcept
Remaps the MIDI channel of the specified MIDI message (if necessary).
static const uint32 notMPE
Used to indicate that a particular source & channel combination is not currently using MPE.
void clearChannel(int channel) noexcept
Clears a specified channel of this MPE zone.
void clearSource(uint32 mpeSourceID)
Clears all channels in use by a specified source.
MPEChannelRemapper(MPEZoneLayout::Zone zoneToRemap)
Constructor.
This class represents the current MPE zone layout of a device capable of handling MPE.
Encapsulates a MIDI message.
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
Creates a key-down message (using a floating-point velocity).
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
Creates a key-up message.
This struct represents an MPE zone.