28 const uint8 noLSBValueReceived = 0xff;
29 const Range<int> allChannels { 1, 17 };
37 std::fill_n (isMemberChannelSustained, 16,
false);
46 legacyMode.isEnabled =
false;
47 legacyMode.pitchbendRange = 2;
66 legacyMode.isEnabled =
false;
76 legacyMode.isEnabled =
true;
77 legacyMode.pitchbendRange = pitchbendRange;
78 legacyMode.channelRange = channelRange;
84 return legacyMode.isEnabled;
89 return legacyMode.channelRange;
98 legacyMode.channelRange = channelRange;
103 return legacyMode.pitchbendRange;
108 jassert (pitchbendRange >= 0 && pitchbendRange <= 96);
112 legacyMode.pitchbendRange = pitchbendRange;
118 pressureDimension.trackingMode =
modeToUse;
123 pitchbendDimension.trackingMode =
modeToUse;
128 timbreDimension.trackingMode =
modeToUse;
147 if (message.
isNoteOn (
true)) processMidiNoteOnMessage (message);
148 else if (message.
isNoteOff (
false)) processMidiNoteOffMessage (message);
150 || message.
isAllNotesOff()) processMidiResetAllControllersMessage (message);
151 else if (message.
isPitchWheel()) processMidiPitchWheelMessage (message);
152 else if (message.
isChannelPressure()) processMidiChannelPressureMessage (message);
153 else if (message.
isController()) processMidiControllerMessage (message);
157void MPEInstrument::processMidiNoteOnMessage (
const MidiMessage& message)
178void MPEInstrument::processMidiNoteOffMessage (
const MidiMessage& message)
181 message.getNoteNumber(),
186void MPEInstrument::processMidiPitchWheelMessage (
const MidiMessage& message)
193void MPEInstrument::processMidiChannelPressureMessage (
const MidiMessage& message)
200void MPEInstrument::processMidiControllerMessage (
const MidiMessage& message)
202 switch (message.getControllerNumber())
204 case 64:
sustainPedal (message.getChannel(), message.isSustainPedalOn());
break;
205 case 66:
sostenutoPedal (message.getChannel(), message.isSostenutoPedalOn());
break;
206 case 70: handlePressureMSB (message.getChannel(), message.getControllerValue());
break;
207 case 74: handleTimbreMSB (message.getChannel(), message.getControllerValue());
break;
208 case 102: handlePressureLSB (message.getChannel(), message.getControllerValue());
break;
209 case 106: handleTimbreLSB (message.getChannel(), message.getControllerValue());
break;
215void MPEInstrument::processMidiResetAllControllersMessage (
const MidiMessage& message)
220 if (legacyMode.isEnabled && legacyMode.channelRange.contains (message.getChannel()))
222 for (
auto i = notes.
size(); --i >= 0;)
226 if (note.midiChannel == message.getChannel())
230 listeners.call ([&] (Listener& l) { l.noteReleased (note); });
237 auto zone = (message.getChannel() == 1 ? zoneLayout.
getLowerZone()
240 for (
auto i = notes.
size(); --i >= 0;)
244 if (zone.isUsingChannelAsMemberChannel (note.midiChannel))
248 listeners.call ([&] (Listener& l) { l.noteReleased (note); });
256void MPEInstrument::handlePressureMSB (
int midiChannel,
int value)
noexcept
258 auto lsb = lastPressureLowerBitReceivedOnChannel[midiChannel - 1];
261 : MPEValue::from14BitInt (lsb + (value << 7)));
264void MPEInstrument::handlePressureLSB (
int midiChannel,
int value)
noexcept
266 lastPressureLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value);
269void MPEInstrument::handleTimbreMSB (
int midiChannel,
int value)
noexcept
271 auto lsb = lastTimbreLowerBitReceivedOnChannel[midiChannel - 1];
274 : MPEValue::from14BitInt (lsb + (value << 7)));
277void MPEInstrument::handleTimbreLSB (
int midiChannel,
int value)
noexcept
279 lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value);
293 getInitialValueForNewNote (midiChannel, pitchbendDimension),
294 getInitialValueForNewNote (midiChannel, pressureDimension),
295 getInitialValueForNewNote (midiChannel, timbreDimension),
299 updateNoteTotalPitchbend (
newNote);
342 listeners.call ([=] (
Listener&
l) {
l.noteKeyStateChanged (*
note); });
351 updateDimension (midiChannel, pitchbendDimension, value);
357 updateDimension (midiChannel, pressureDimension, value);
363 updateDimension (midiChannel, timbreDimension, value);
366MPEValue MPEInstrument::getInitialValueForNewNote (
int midiChannel, MPEDimension&
dimension)
const
368 if (getLastNotePlayedPtr (midiChannel) !=
nullptr)
371 return dimension.lastValueReceivedOnChannel[midiChannel - 1];
375void MPEInstrument::updateDimension (
int midiChannel, MPEDimension& dimension, MPEValue value)
377 dimension.lastValueReceivedOnChannel[midiChannel - 1] = value;
386 for (
auto i = notes.
size(); --i >= 0;)
390 if (note.midiChannel == midiChannel)
391 updateDimensionForNote (note, dimension, value);
396 if (
auto* note = getNotePtr (midiChannel, dimension.trackingMode))
397 updateDimensionForNote (*note, dimension, value);
402 updateDimensionMaster (midiChannel == 1, dimension, value);
407void MPEInstrument::updateDimensionMaster (
bool isLowerZone, MPEDimension& dimension, MPEValue value)
412 if (! zone.isActive())
415 for (
auto i = notes.
size(); --i >= 0;)
419 if (! zone.isUsingChannelAsMemberChannel (note.midiChannel))
422 if (&dimension == &pitchbendDimension)
426 updateNoteTotalPitchbend (note);
427 listeners.call ([&] (Listener& l) { l.notePitchbendChanged (note); });
429 else if (dimension.getValue (note) != value)
431 dimension.getValue (note) = value;
432 callListenersDimensionChanged (note, dimension);
438void MPEInstrument::updateDimensionForNote (MPENote& note, MPEDimension& dimension, MPEValue value)
440 if (dimension.getValue (note) != value)
442 dimension.getValue (note) = value;
444 if (&dimension == &pitchbendDimension)
445 updateNoteTotalPitchbend (note);
447 callListenersDimensionChanged (note, dimension);
452void MPEInstrument::callListenersDimensionChanged (
const MPENote& note,
const MPEDimension& dimension)
454 if (&dimension == &pressureDimension) { listeners.call ([&] (Listener& l) { l.notePressureChanged (note); });
return; }
455 if (&dimension == &timbreDimension) { listeners.call ([&] (Listener& l) { l.noteTimbreChanged (note); });
return; }
456 if (&dimension == &pitchbendDimension) { listeners.call ([&] (Listener& l) { l.notePitchbendChanged (note); });
return; }
460void MPEInstrument::updateNoteTotalPitchbend (MPENote& note)
462 if (legacyMode.isEnabled)
464 note.totalPitchbendInSemitones = note.pitchbend.asSignedFloat() * legacyMode.pitchbendRange;
470 if (! zone.isUsingChannelAsMemberChannel (note.midiChannel))
472 if (zoneLayout.
getUpperZone().isUsingChannelAsMemberChannel (note.midiChannel))
484 auto notePitchbendInSemitones = note.pitchbend.asSignedFloat() * zone.perNotePitchbendRange;
486 auto masterPitchbendInSemitones = pitchbendDimension.lastValueReceivedOnChannel[zone.getMasterChannel() - 1]
488 * zone.masterPitchbendRange;
490 note.totalPitchbendInSemitones = notePitchbendInSemitones + masterPitchbendInSemitones;
498 handleSustainOrSostenuto (midiChannel,
isDown,
false);
504 handleSustainOrSostenuto (midiChannel,
isDown,
true);
508void MPEInstrument::handleSustainOrSostenuto (
int midiChannel,
bool isDown,
bool isSostenuto)
513 if (legacyMode.isEnabled ? (! legacyMode.channelRange.contains (midiChannel)) : (!
isMasterChannel (midiChannel)))
516 auto zone = (midiChannel == 1 ? zoneLayout.
getLowerZone()
519 for (
auto i = notes.
size(); --i >= 0;)
523 if (legacyMode.isEnabled ? (
note.midiChannel == midiChannel) : zone.isUsingChannelAsMemberChannel (
note.midiChannel))
534 listeners.call ([&] (Listener&
l) {
l.noteReleased (
note); });
539 listeners.call ([&] (Listener& l) { l.noteKeyStateChanged (note); });
546 if (legacyMode.isEnabled)
548 isMemberChannelSustained[midiChannel - 1] = isDown;
552 if (zone.isLowerZone())
553 for (
auto i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i)
554 isMemberChannelSustained[i - 1] = isDown;
556 for (
auto i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i)
557 isMemberChannelSustained[i - 1] = isDown;
565 if (legacyMode.isEnabled)
566 return legacyMode.channelRange.
contains (midiChannel);
568 return zoneLayout.getLowerZone().isUsingChannelAsMemberChannel (midiChannel)
569 || zoneLayout.getUpperZone().isUsingChannelAsMemberChannel (midiChannel);
574 if (legacyMode.isEnabled)
577 return (midiChannel == 1 || midiChannel == 16);
601 if (
auto*
note = getLastNotePlayedPtr (midiChannel))
609 for (
auto i = notes.size(); --i >= 0;)
623 for (
int i = 0; i < notes.size(); ++i)
634MPENote* MPEInstrument::getNotePtr (
int midiChannel,
int midiNoteNumber)
noexcept
636 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getNotePtr (midiChannel, midiNoteNumber));
640const MPENote* MPEInstrument::getNotePtr (
int midiChannel, TrackingMode mode)
const noexcept
644 jassert (mode != allNotesOnChannel);
646 if (mode == lastNotePlayedOnChannel)
return getLastNotePlayedPtr (midiChannel);
647 if (mode == lowestNoteOnChannel)
return getLowestNotePtr (midiChannel);
648 if (mode == highestNoteOnChannel)
return getHighestNotePtr (midiChannel);
653MPENote* MPEInstrument::getNotePtr (
int midiChannel, TrackingMode mode)
noexcept
655 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getNotePtr (midiChannel, mode));
659const MPENote* MPEInstrument::getLastNotePlayedPtr (
int midiChannel)
const noexcept
661 for (
auto i = notes.size(); --i >= 0;)
663 auto& note = notes.getReference (i);
665 if (note.midiChannel == midiChannel
673MPENote* MPEInstrument::getLastNotePlayedPtr (
int midiChannel)
noexcept
675 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getLastNotePlayedPtr (midiChannel));
679const MPENote* MPEInstrument::getHighestNotePtr (
int midiChannel)
const noexcept
681 int initialNoteMax = -1;
682 MPENote* result =
nullptr;
684 for (
auto i = notes.size(); --i >= 0;)
686 auto& note = notes.getReference (i);
688 if (note.midiChannel == midiChannel
690 && note.initialNote > initialNoteMax)
693 initialNoteMax = note.initialNote;
700MPENote* MPEInstrument::getHighestNotePtr (
int midiChannel)
noexcept
702 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getHighestNotePtr (midiChannel));
705const MPENote* MPEInstrument::getLowestNotePtr (
int midiChannel)
const noexcept
707 int initialNoteMin = 128;
708 MPENote* result =
nullptr;
710 for (
auto i = notes.size(); --i >= 0;)
712 auto& note = notes.getReference (i);
714 if (note.midiChannel == midiChannel
716 && note.initialNote < initialNoteMin)
719 initialNoteMin = note.initialNote;
726MPENote* MPEInstrument::getLowestNotePtr (
int midiChannel)
noexcept
728 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getLowestNotePtr (midiChannel));
736 for (
auto i = notes.
size(); --i >= 0;)
755 :
UnitTest (
"MPEInstrument class",
"MIDI/MPE")
766 void runTest()
override
768 beginTest (
"initial zone layout");
771 expect (!
test.getZoneLayout().getLowerZone().isActive());
772 expect (!
test.getZoneLayout().getUpperZone().isActive());
775 beginTest (
"get/setZoneLayout");
782 expect (
test.getZoneLayout().getLowerZone().isActive());
783 expect (
test.getZoneLayout().getUpperZone().isActive());
784 expectEquals (
newLayout.getLowerZone().getMasterChannel(), 1);
785 expectEquals (
newLayout.getLowerZone().numMemberChannels, 5);
786 expectEquals (
newLayout.getUpperZone().getMasterChannel(), 16);
787 expectEquals (
newLayout.getUpperZone().numMemberChannels, 6);
790 beginTest (
"noteOn / noteOff");
795 expectEquals (
test.getNumPlayingNotes(), 0);
803 expectEquals (
test.getNumPlayingNotes(), 0);
804 expectEquals (
test.noteAddedCallCounter, 0);
808 expectEquals (
test.getNumPlayingNotes(), 0);
809 expectEquals (
test.noteAddedCallCounter, 0);
813 expectEquals (
test.getNumPlayingNotes(), 1);
814 expectEquals (
test.noteAddedCallCounter, 1);
819 expectEquals (
test.getNumPlayingNotes(), 0);
820 expectEquals (
test.noteReleasedCallCounter, 1);
830 expectEquals (
test.getNumPlayingNotes(), 1);
832 expectEquals (
test.noteReleasedCallCounter, 0);
836 expectEquals (
test.getNumPlayingNotes(), 1);
838 expectEquals (
test.noteReleasedCallCounter, 0);
847 expectEquals (
test.getNumPlayingNotes(), 3);
858 expectEquals (
test.getNumPlayingNotes(), 1);
863 beginTest (
"noteReleased after setZoneLayout");
871 expectEquals (
test.getNumPlayingNotes(), 3);
872 expectEquals (
test.noteReleasedCallCounter, 0);
875 expectEquals (
test.getNumPlayingNotes(), 0);
876 expectEquals (
test.noteReleasedCallCounter, 3);
879 beginTest (
"releaseAllNotes");
886 expectEquals (
test.getNumPlayingNotes(), 3);
888 test.releaseAllNotes();
889 expectEquals (
test.getNumPlayingNotes(), 0);
892 beginTest (
"sustainPedal");
900 test.sustainPedal (3,
true);
905 expectEquals (
test.noteKeyStateChangedCallCounter, 0);
908 test.sustainPedal (7,
true);
911 expectEquals (
test.noteKeyStateChangedCallCounter, 0);
914 test.sustainPedal (1,
true);
917 expectEquals (
test.noteKeyStateChangedCallCounter, 1);
920 test.sustainPedal (1,
false);
923 expectEquals (
test.noteKeyStateChangedCallCounter, 2);
926 test.sustainPedal (1,
true);
927 expectEquals (
test.noteKeyStateChangedCallCounter, 3);
930 expectEquals (
test.noteKeyStateChangedCallCounter, 3);
933 test.sustainPedal (11,
true);
937 expectEquals (
test.noteReleasedCallCounter, 1);
943 expectEquals (
test.getNumPlayingNotes(), 2);
944 expectEquals (
test.noteReleasedCallCounter, 2);
945 expectEquals (
test.noteKeyStateChangedCallCounter, 5);
950 test.sustainPedal (1,
false);
951 expectEquals (
test.getNumPlayingNotes(), 0);
952 expectEquals (
test.noteReleasedCallCounter, 4);
955 beginTest (
"sostenutoPedal");
963 test.sostenutoPedal (3,
true);
966 expectEquals (
test.noteKeyStateChangedCallCounter, 0);
969 test.sostenutoPedal (9,
true);
972 expectEquals (
test.noteKeyStateChangedCallCounter, 0);
975 test.sostenutoPedal (1,
true);
978 expectEquals (
test.noteKeyStateChangedCallCounter, 1);
981 test.sostenutoPedal (1,
false);
984 expectEquals (
test.noteKeyStateChangedCallCounter, 2);
987 test.sostenutoPedal (1,
true);
988 expectEquals (
test.noteKeyStateChangedCallCounter, 3);
990 expectEquals (
test.getNumPlayingNotes(), 3);
994 expectEquals (
test.noteKeyStateChangedCallCounter, 3);
1001 expectEquals (
test.getNumPlayingNotes(), 1);
1003 expectEquals (
test.noteReleasedCallCounter, 2);
1004 expectEquals (
test.noteKeyStateChangedCallCounter, 4);
1007 test.sustainPedal (1,
false);
1008 expectEquals (
test.getNumPlayingNotes(), 0);
1009 expectEquals (
test.noteReleasedCallCounter, 3);
1012 beginTest (
"getMostRecentNote");
1021 auto note =
test.getMostRecentNote (2);
1022 expect (!
note.isValid());
1025 auto note =
test.getMostRecentNote (3);
1026 expect (
note.isValid());
1027 expectEquals (
int (
note.midiChannel), 3);
1028 expectEquals (
int (
note.initialNote), 61);
1031 test.sustainPedal (1,
true);
1035 auto note =
test.getMostRecentNote (3);
1036 expect (
note.isValid());
1037 expectEquals (
int (
note.midiChannel), 3);
1038 expectEquals (
int (
note.initialNote), 60);
1041 test.sustainPedal (1,
false);
1045 auto note =
test.getMostRecentNote (3);
1046 expect (!
note.isValid());
1050 beginTest (
"getMostRecentNoteOtherThan");
1061 expect (!
test.getMostRecentNoteOtherThan (
testNote).isValid());
1064 expect (!
test.getMostRecentNoteOtherThan (
testNote).isValid());
1067 expect (
test.getMostRecentNoteOtherThan (
testNote).isValid());
1068 expect (
test.getMostRecentNoteOtherThan (
testNote).midiChannel == 4);
1069 expect (
test.getMostRecentNoteOtherThan (
testNote).initialNote == 61);
1076 expect (!
test.getMostRecentNoteOtherThan (
testNote).isValid());
1079 expect (
test.getMostRecentNoteOtherThan (
testNote).isValid());
1080 expect (
test.getMostRecentNoteOtherThan (
testNote).midiChannel == 4);
1081 expect (
test.getMostRecentNoteOtherThan (
testNote).initialNote == 61);
1084 expect (
test.getMostRecentNoteOtherThan (
testNote).isValid());
1085 expect (
test.getMostRecentNoteOtherThan (
testNote).midiChannel == 4);
1086 expect (
test.getMostRecentNoteOtherThan (
testNote).initialNote == 61);
1090 beginTest (
"pressure");
1105 expectEquals (
test.notePressureChangedCallCounter, 1);
1112 expectEquals (
test.notePressureChangedCallCounter, 3);
1119 expectEquals (
test.notePressureChangedCallCounter, 3);
1131 expectEquals (
test.notePressureChangedCallCounter, 1);
1143 expectEquals (
test.getNumPlayingNotes(), 1);
1145 expectEquals (
test.notePressureChangedCallCounter, 1);
1192 beginTest (
"pitchbend");
1207 expectEquals (
test.notePitchbendChangedCallCounter, 1);
1217 expectEquals (
test.notePitchbendChangedCallCounter, 3);
1224 expectEquals (
test.notePitchbendChangedCallCounter, 3);
1236 expectEquals (
test.notePitchbendChangedCallCounter, 1);
1248 expectEquals (
test.getNumPlayingNotes(), 1);
1250 expectEquals (
test.notePitchbendChangedCallCounter, 1);
1265 test.sustainPedal (1,
true);
1267 expectEquals (
test.getNumPlayingNotes(), 1);
1269 expectEquals (
test.noteKeyStateChangedCallCounter, 2);
1273 expectEquals (
test.getNumPlayingNotes(), 2);
1276 expectEquals (
test.notePitchbendChangedCallCounter, 1);
1309 layout.setLowerZone (5, 96);
1315 layout.setLowerZone (5, 1);
1321 layout.setLowerZone (5, 0);
1338 layout.setLowerZone (5, 48, 96);
1344 layout.setLowerZone (5, 48, 1);
1350 layout.setLowerZone (5, 48, 0);
1363 layout.setLowerZone (5, 12, 1);
1375 beginTest (
"timbre");
1390 expectEquals (
test.noteTimbreChangedCallCounter, 1);
1397 expectEquals (
test.noteTimbreChangedCallCounter, 3);
1404 expectEquals (
test.noteTimbreChangedCallCounter, 3);
1416 expectEquals (
test.noteTimbreChangedCallCounter, 1);
1428 expectEquals (
test.getNumPlayingNotes(), 1);
1430 expectEquals (
test.noteTimbreChangedCallCounter, 1);
1447 beginTest (
"setPressureTrackingMode");
1462 expectEquals (
test.notePressureChangedCallCounter, 1);
1477 expectEquals (
test.notePressureChangedCallCounter, 1);
1492 expectEquals (
test.notePressureChangedCallCounter, 1);
1507 expectEquals (
test.notePressureChangedCallCounter, 3);
1511 beginTest (
"setPitchbendTrackingMode");
1526 expectEquals (
test.notePitchbendChangedCallCounter, 1);
1541 expectEquals (
test.notePitchbendChangedCallCounter, 1);
1556 expectEquals (
test.notePitchbendChangedCallCounter, 1);
1571 expectEquals (
test.notePitchbendChangedCallCounter, 3);
1575 beginTest (
"setTimbreTrackingMode");
1590 expectEquals (
test.noteTimbreChangedCallCounter, 1);
1605 expectEquals (
test.noteTimbreChangedCallCounter, 1);
1620 expectEquals (
test.noteTimbreChangedCallCounter, 1);
1635 expectEquals (
test.noteTimbreChangedCallCounter, 3);
1639 beginTest (
"processNextMidiEvent");
1646 expectEquals (
test.noteOnCallCounter, 1);
1647 expectEquals (
test.lastMidiChannelReceived, 3);
1648 expectEquals (
test.lastMidiNoteNumberReceived, 42);
1649 expectEquals (
test.lastMPEValueReceived.as7BitInt(), 92);
1654 expectEquals (
test.noteOffCallCounter, 1);
1655 expectEquals (
test.lastMidiChannelReceived, 4);
1656 expectEquals (
test.lastMidiNoteNumberReceived, 12);
1657 expectEquals (
test.lastMPEValueReceived.as7BitInt(), 33);
1663 expectEquals (
test.noteOffCallCounter, 2);
1664 expectEquals (
test.lastMidiChannelReceived, 5);
1665 expectEquals (
test.lastMidiNoteNumberReceived, 11);
1666 expectEquals (
test.lastMPEValueReceived.as7BitInt(), 64);
1671 expectEquals (
test.pitchbendCallCounter, 1);
1672 expectEquals (
test.lastMidiChannelReceived, 1);
1673 expectEquals (
test.lastMPEValueReceived.as14BitInt(), 3333);
1679 expectEquals (
test.pressureCallCounter, 1);
1680 expectEquals (
test.lastMidiChannelReceived, 10);
1681 expectEquals (
test.lastMPEValueReceived.as7BitInt(), 35);
1688 expectEquals (
test.pressureCallCounter, 2);
1689 expectEquals (
test.lastMidiChannelReceived, 3);
1690 expectEquals (
test.lastMPEValueReceived.as7BitInt(), 120);
1694 expectEquals (
test.pressureCallCounter, 2);
1696 expectEquals (
test.pressureCallCounter, 2);
1698 expectEquals (
test.pressureCallCounter, 3);
1699 expectEquals (
test.lastMidiChannelReceived, 4);
1700 expectEquals (
test.lastMPEValueReceived.as14BitInt(), 121 + (123 << 7));
1702 expectEquals (
test.pressureCallCounter, 4);
1703 expectEquals (
test.lastMidiChannelReceived, 5);
1704 expectEquals (
test.lastMPEValueReceived.as14BitInt(), 122 + (124 << 7));
1706 expectEquals (
test.pressureCallCounter, 5);
1707 expectEquals (
test.lastMidiChannelReceived, 5);
1708 expectEquals (
test.lastMPEValueReceived.as7BitInt(), 64);
1712 expectEquals (
test.timbreCallCounter, 1);
1713 expectEquals (
test.lastMidiChannelReceived, 3);
1714 expectEquals (
test.lastMPEValueReceived.as7BitInt(), 120);
1716 expectEquals (
test.timbreCallCounter, 1);
1718 expectEquals (
test.timbreCallCounter, 1);
1720 expectEquals (
test.timbreCallCounter, 2);
1721 expectEquals (
test.lastMidiChannelReceived, 4);
1722 expectEquals (
test.lastMPEValueReceived.as14BitInt(), 121 + (123 << 7));
1724 expectEquals (
test.timbreCallCounter, 3);
1725 expectEquals (
test.lastMidiChannelReceived, 5);
1726 expectEquals (
test.lastMPEValueReceived.as14BitInt(), 122 + (124 << 7));
1728 expectEquals (
test.timbreCallCounter, 4);
1729 expectEquals (
test.lastMidiChannelReceived, 5);
1730 expectEquals (
test.lastMPEValueReceived.as7BitInt(), 64);
1734 expectEquals (
test.sustainPedalCallCounter, 1);
1735 expectEquals (
test.lastMidiChannelReceived, 1);
1736 expect (
test.lastSustainPedalValueReceived);
1738 expectEquals (
test.sustainPedalCallCounter, 2);
1739 expectEquals (
test.lastMidiChannelReceived, 16);
1740 expect (!
test.lastSustainPedalValueReceived);
1744 expectEquals (
test.sostenutoPedalCallCounter, 1);
1745 expectEquals (
test.lastMidiChannelReceived, 1);
1746 expect (
test.lastSostenutoPedalValueReceived);
1748 expectEquals (
test.sostenutoPedalCallCounter, 2);
1749 expectEquals (
test.lastMidiChannelReceived, 16);
1750 expect (!
test.lastSostenutoPedalValueReceived);
1763 MidiBuffer::Iterator
iter (buffer);
1764 MidiMessage message;
1768 test.processNextMidiEvent (message);
1770 expect (
test.getZoneLayout().getLowerZone().isActive());
1771 expect (
test.getZoneLayout().getUpperZone().isActive());
1772 expectEquals (
test.getZoneLayout().getLowerZone().getMasterChannel(), 1);
1773 expectEquals (
test.getZoneLayout().getLowerZone().numMemberChannels, 5);
1774 expectEquals (
test.getZoneLayout().getUpperZone().getMasterChannel(), 16);
1775 expectEquals (
test.getZoneLayout().getUpperZone().numMemberChannels, 6);
1778 beginTest (
"MIDI all notes off");
1786 expectEquals (
test.getNumPlayingNotes(), 4);
1790 expectEquals (
test.getNumPlayingNotes(), 4);
1794 expectEquals (
test.getNumPlayingNotes(), 4);
1798 expectEquals (
test.getNumPlayingNotes(), 2);
1800 expectEquals (
test.getNumPlayingNotes(), 0);
1803 beginTest (
"MIDI all notes off (legacy mode)");
1806 test.enableLegacyMode();
1811 expectEquals (
test.getNumPlayingNotes(), 4);
1814 expectEquals (
test.getNumPlayingNotes(), 3);
1817 expectEquals (
test.getNumPlayingNotes(), 1);
1820 expectEquals (
test.getNumPlayingNotes(), 0);
1823 beginTest (
"default initial values for pitchbend and timbre");
1841 beginTest (
"Legacy mode");
1846 expect (!
test.isLegacyModeEnabled());
1849 expect (!
test.isLegacyModeEnabled());
1851 test.enableLegacyMode();
1852 expect (
test.isLegacyModeEnabled());
1855 expect (!
test.isLegacyModeEnabled());
1860 test.enableLegacyMode (0, Range<int> (1, 11));
1861 expectEquals (
test.getLegacyModePitchbendRange(), 0);
1862 expect (
test.getLegacyModeChannelRange() == Range<int> (1, 11));
1867 test.enableLegacyMode();
1869 expectEquals (
test.getLegacyModePitchbendRange(), 2);
1870 expect (
test.getLegacyModeChannelRange() == Range<int> (1, 17));
1872 test.setLegacyModePitchbendRange (96);
1873 expectEquals (
test.getLegacyModePitchbendRange(), 96);
1875 test.setLegacyModeChannelRange (Range<int> (10, 12));
1876 expect (
test.getLegacyModeChannelRange() == Range<int> (10, 12));
1882 test.enableLegacyMode();
1888 expectEquals (
test.getNumPlayingNotes(), 4);
1907 expectEquals (
test.getNumPlayingNotes(), 0);
1913 test.enableLegacyMode (2, Range<int> (3, 8));
1924 expectEquals (
test.getNumPlayingNotes(), 4);
1934 test.enableLegacyMode();
1947 test.enableLegacyMode();
1960 test.enableLegacyMode();
1973 test.enableLegacyMode();
1988 test.enableLegacyMode (11);
1997 test.enableLegacyMode();
1999 test.sustainPedal (1,
true);
2005 expectEquals (
test.getNumPlayingNotes(), 1);
2008 test.sustainPedal (1,
false);
2009 expectEquals (
test.getNumPlayingNotes(), 0);
2012 test.sustainPedal (1,
true);
2014 expectEquals (
test.getNumPlayingNotes(), 0);
2020 test.enableLegacyMode();
2023 test.sostenutoPedal (1,
true);
2028 expectEquals (
test.getNumPlayingNotes(), 1);
2031 test.sostenutoPedal (1,
false);
2032 expectEquals (
test.getNumPlayingNotes(), 0);
2035 test.sostenutoPedal (1,
true);
2037 expectEquals (
test.getNumPlayingNotes(), 0);
2044 expectEquals (
test.getNumPlayingNotes(), 1);
2046 test.enableLegacyMode();
2047 expectEquals (
test.getNumPlayingNotes(), 0);
2049 expectEquals (
test.getNumPlayingNotes(), 1);
2052 expectEquals (
test.getNumPlayingNotes(), 0);
2063 private MPEInstrument::Listener
2065 using Base = MPEInstrument;
2068 UnitTestInstrument()
2069 : noteOnCallCounter (0), noteOffCallCounter (0), pitchbendCallCounter (0),
2070 pressureCallCounter (0), timbreCallCounter (0), sustainPedalCallCounter (0),
2071 sostenutoPedalCallCounter (0), noteAddedCallCounter (0), notePressureChangedCallCounter (0),
2072 notePitchbendChangedCallCounter (0), noteTimbreChangedCallCounter (0),
2073 noteKeyStateChangedCallCounter (0), noteReleasedCallCounter (0),
2074 lastMidiChannelReceived (-1), lastMidiNoteNumberReceived (-1),
2075 lastSustainPedalValueReceived (false), lastSostenutoPedalValueReceived (false)
2080 void noteOn (
int midiChannel,
int midiNoteNumber, MPEValue midiNoteOnVelocity)
override
2082 Base::noteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity);
2084 noteOnCallCounter++;
2085 lastMidiChannelReceived = midiChannel;
2086 lastMidiNoteNumberReceived = midiNoteNumber;
2087 lastMPEValueReceived = midiNoteOnVelocity;
2090 void noteOff (
int midiChannel,
int midiNoteNumber, MPEValue midiNoteOffVelocity)
override
2092 Base::noteOff (midiChannel, midiNoteNumber, midiNoteOffVelocity);
2094 noteOffCallCounter++;
2095 lastMidiChannelReceived = midiChannel;
2096 lastMidiNoteNumberReceived = midiNoteNumber;
2097 lastMPEValueReceived = midiNoteOffVelocity;
2100 void pitchbend (
int midiChannel, MPEValue value)
override
2102 Base::pitchbend (midiChannel, value);
2104 pitchbendCallCounter++;
2105 lastMidiChannelReceived = midiChannel;
2106 lastMPEValueReceived = value;
2109 void pressure (
int midiChannel, MPEValue value)
override
2111 Base::pressure (midiChannel, value);
2113 pressureCallCounter++;
2114 lastMidiChannelReceived = midiChannel;
2115 lastMPEValueReceived = value;
2118 void timbre (
int midiChannel, MPEValue value)
override
2120 Base::timbre (midiChannel, value);
2122 timbreCallCounter++;
2123 lastMidiChannelReceived = midiChannel;
2124 lastMPEValueReceived = value;
2127 void sustainPedal (
int midiChannel,
bool value)
override
2129 Base::sustainPedal (midiChannel, value);
2131 sustainPedalCallCounter++;
2132 lastMidiChannelReceived = midiChannel;
2133 lastSustainPedalValueReceived = value;
2136 void sostenutoPedal (
int midiChannel,
bool value)
override
2138 Base::sostenutoPedal (midiChannel, value);
2140 sostenutoPedalCallCounter++;
2141 lastMidiChannelReceived = midiChannel;
2142 lastSostenutoPedalValueReceived = value;
2145 int noteOnCallCounter, noteOffCallCounter, pitchbendCallCounter,
2146 pressureCallCounter, timbreCallCounter, sustainPedalCallCounter,
2147 sostenutoPedalCallCounter, noteAddedCallCounter,
2148 notePressureChangedCallCounter, notePitchbendChangedCallCounter,
2149 noteTimbreChangedCallCounter, noteKeyStateChangedCallCounter,
2150 noteReleasedCallCounter, lastMidiChannelReceived, lastMidiNoteNumberReceived;
2152 bool lastSustainPedalValueReceived, lastSostenutoPedalValueReceived;
2153 MPEValue lastMPEValueReceived;
2154 std::unique_ptr<MPENote> lastNoteFinished;
2158 void noteAdded (MPENote)
override { noteAddedCallCounter++; }
2160 void notePressureChanged (MPENote)
override { notePressureChangedCallCounter++; }
2161 void notePitchbendChanged (MPENote)
override { notePitchbendChangedCallCounter++; }
2162 void noteTimbreChanged (MPENote)
override { noteTimbreChangedCallCounter++; }
2163 void noteKeyStateChanged (MPENote)
override { noteKeyStateChangedCallCounter++; }
2165 void noteReleased (MPENote finishedNote)
override
2167 noteReleasedCallCounter++;
2168 lastNoteFinished.reset (
new MPENote (finishedNote));
2191 expect (
test.lastNoteFinished !=
nullptr);
2192 expectEquals (
int (
test.lastNoteFinished->midiChannel), channel);
2193 expectEquals (
int (
test.lastNoteFinished->initialNote),
noteNumber);
2208static MPEInstrumentTests MPEInstrumentUnitTests;
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 size() const noexcept
Returns the current number of elements in the array.
void remove(int indexToRemove)
Removes an element from the array.
Array()=default
Creates an empty array.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
ElementType & getReference(int index) const noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in.
bool contains(ParameterType elementToLookFor) const
Returns true if the array contains at least one occurrence of an object.
void clear()
Removes all elements from the array.
Derive from this class to be informed about any changes in the expressive MIDI notes played by this i...
void setPitchbendTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the pitchbend dimension.
void setLegacyModeChannelRange(Range< int > channelRange)
Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode.
MPENote getMostRecentNoteOtherThan(MPENote otherThanThisNote) const noexcept
Returns the most recent note that is not the note passed in.
MPEZoneLayout getZoneLayout() const noexcept
Returns the current zone layout of the instrument.
virtual void sostenutoPedal(int midiChannel, bool isDown)
Request a sostenuto pedal press or release.
virtual void pitchbend(int midiChannel, MPEValue pitchbend)
Request a pitchbend on the given channel with the given value (in units of MIDI pitchwheel position).
virtual ~MPEInstrument()
Destructor.
void enableLegacyMode(int pitchbendRange=2, Range< int > channelRange=Range< int >(1, 17))
Puts the instrument into legacy mode.
MPENote getNote(int index) const noexcept
Returns the note at the given index.
TrackingMode
The MPE note tracking mode.
@ highestNoteOnChannel
The highest note (by initialNote) on the channel with the note key still down.
@ lowestNoteOnChannel
The lowest note (by initialNote) on the channel with the note key still down.
@ lastNotePlayedOnChannel
The most recent note on the channel that is still played (key down and/or sustained).
@ allNotesOnChannel
All notes on the channel (key down and/or sustained).
MPEInstrument() noexcept
Constructor.
void setZoneLayout(MPEZoneLayout newLayout)
Re-sets the zone layout of the instrument to the one passed in.
virtual void processNextMidiEvent(const MidiMessage &message)
Process a MIDI message and trigger the appropriate method calls (noteOn, noteOff etc....
void setLegacyModePitchbendRange(int pitchbendRange)
Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode.
bool isLegacyModeEnabled() const noexcept
Returns true if the instrument is in legacy mode, false otherwise.
void setPressureTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the pressure dimension.
void addListener(Listener *listenerToAdd)
Adds a listener.
void removeListener(Listener *listenerToRemove)
Removes a listener.
virtual void sustainPedal(int midiChannel, bool isDown)
Request a sustain pedal press or release.
void setTimbreTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the timbre dimension.
int getNumPlayingNotes() const noexcept
Returns the number of MPE notes currently played by the instrument.
void releaseAllNotes()
Discard all currently playing notes.
virtual void timbre(int midiChannel, MPEValue value)
Request a third dimension (timbre) change on the given channel with the given value.
MPENote getMostRecentNote(int midiChannel) const noexcept
Returns the most recent note that is playing on the given midiChannel (this will be the note which ha...
Range< int > getLegacyModeChannelRange() const noexcept
Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode.
virtual void noteOn(int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity)
Request a note-on on the given channel, with the given initial note number and velocity.
int getLegacyModePitchbendRange() const noexcept
Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode.
virtual void noteOff(int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity)
Request a note-off.
virtual void pressure(int midiChannel, MPEValue value)
Request a pressure change on the given channel with the given value.
bool isMasterChannel(int midiChannel) const noexcept
Returns true if the given MIDI channel (1-16) is a master channel (channel 1 or 16).
bool isMemberChannel(int midiChannel) noexcept
Returns true if the given MIDI channel (1-16) is a note channel in any of the MPEInstrument's MPE zon...
static MidiBuffer setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2)
Returns the sequence of MIDI messages that, if sent to an Expressive MIDI device, will set the upper ...
static MidiBuffer setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2)
Returns the sequence of MIDI messages that, if sent to an Expressive MIDI device, will set the lower ...
This class represents a single value for any of the MPE dimensions of control.
static MPEValue centreValue() noexcept
Constructs an MPEValue corresponding to the centre value.
static MPEValue from14BitInt(int value) noexcept
Constructs an MPEValue from an integer between 0 and 16383 (using 14-bit precision).
static MPEValue minValue() noexcept
Constructs an MPEValue corresponding to the minimum value.
static MPEValue from7BitInt(int value) noexcept
Constructs an MPEValue from an integer between 0 and 127 (using 7-bit precision).
This class represents the current MPE zone layout of a device capable of handling MPE.
void clearAllZones()
Clears the lower and upper zones of this layout, making them both inactive and disabling MPE mode.
const Zone getUpperZone() const noexcept
Returns a struct representing the upper MPE zone.
const Zone getLowerZone() const noexcept
Returns a struct representing the lower MPE zone.
void processNextMidiEvent(const MidiMessage &message)
Pass incoming MIDI messages to an object of this class if you want the zone layout to properly react ...
Encapsulates a MIDI message.
bool isNoteOn(bool returnTrueForVelocity0=false) const noexcept
Returns true if this message is a 'key-down' event.
int getChannel() const noexcept
Returns the midi channel associated with the message.
bool isController() const noexcept
Returns true if this is a midi controller message.
static MidiMessage pitchWheel(int channel, int position) noexcept
Creates a pitch-wheel move message.
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
Returns true if this message is a 'key-up' event.
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
Creates a key-down message (using a floating-point velocity).
bool isPitchWheel() const noexcept
Returns true if the message is a pitch-wheel move.
int getNoteNumber() const noexcept
Returns the midi note number for note-on and note-off messages.
static MidiMessage controllerEvent(int channel, int controllerType, int value) noexcept
Creates a controller message.
bool isResetAllControllers() const noexcept
Checks whether this message is a reset all controllers message.
static MidiMessage channelPressureChange(int channel, int pressure) noexcept
Creates a channel-pressure change event.
bool isAllNotesOff() const noexcept
Checks whether this message is an all-notes-off message.
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
Creates a key-up message.
uint8 getVelocity() const noexcept
Returns the velocity of a note-on or note-off message.
bool isChannelPressure() const noexcept
Returns true if the message is a channel-pressure change event.
static MidiMessage allControllersOff(int channel) noexcept
Creates an all-controllers-off message.
This is a base class for classes that perform a unit test.
This struct represents a playing MPE note.
KeyState
Possible values for the note key state.
@ keyDown
The note key is currently down (pressed).
@ off
The key is up (off).
@ keyDownAndSustained
The note key is down and sustained (by a sustain or sostenuto pedal).
@ sustained
The note is sustained (by a sustain or sostenuto pedal).
MPEValue timbre
Current value of the note's third expressive dimension, typically encoding some kind of timbre parame...
MPEValue pitchbend
Current per-note pitchbend of the note (in units of MIDI pitchwheel position).
MPEValue pressure
Current pressure with which the note is held down.