28 const uint8 noLSBValueReceived = 0xff;
29 const Range<int> allChannels { 1, 17 };
35 std::fill_n (lastPressureLowerBitReceivedOnChannel, 16, noLSBValueReceived);
36 std::fill_n (lastTimbreLowerBitReceivedOnChannel, 16, noLSBValueReceived);
37 std::fill_n (isMemberChannelSustained, 16,
false);
46 legacyMode.isEnabled =
false;
47 legacyMode.pitchbendRange = 2;
48 legacyMode.channelRange = allChannels;
66 legacyMode.isEnabled =
false;
67 zoneLayout = newLayout;
76 legacyMode.isEnabled =
true;
77 legacyMode.pitchbendRange = pitchbendRange;
78 legacyMode.channelRange = channelRange;
84 return legacyMode.isEnabled;
89 return legacyMode.channelRange;
94 jassert (allChannels.contains (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;
134 listeners.add (listenerToAdd);
139 listeners.remove (listenerToRemove);
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);
157 void MPEInstrument::processMidiNoteOnMessage (
const MidiMessage& message)
178 void MPEInstrument::processMidiNoteOffMessage (
const MidiMessage& message)
186 void MPEInstrument::processMidiPitchWheelMessage (
const MidiMessage& message)
193 void MPEInstrument::processMidiChannelPressureMessage (
const MidiMessage& message)
200 void MPEInstrument::processMidiControllerMessage (
const MidiMessage& message)
215 void MPEInstrument::processMidiResetAllControllersMessage (
const MidiMessage& message)
220 if (legacyMode.isEnabled && legacyMode.channelRange.contains (message.
getChannel()))
222 for (
auto i = notes.size(); --i >= 0;)
224 auto& note = notes.getReference (i);
240 for (
auto i = notes.size(); --i >= 0;)
242 auto& note = notes.getReference (i);
244 if (zone.isUsingChannelAsMemberChannel (note.midiChannel))
256 void MPEInstrument::handlePressureMSB (
int midiChannel,
int value) noexcept
258 auto lsb = lastPressureLowerBitReceivedOnChannel[midiChannel - 1];
264 void MPEInstrument::handlePressureLSB (
int midiChannel,
int value) noexcept
266 lastPressureLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value);
269 void MPEInstrument::handleTimbreMSB (
int midiChannel,
int value) noexcept
271 auto lsb = lastTimbreLowerBitReceivedOnChannel[midiChannel - 1];
277 void 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);
301 if (
auto* alreadyPlayingNote = getNotePtr (midiChannel, midiNoteNumber))
307 notes.remove (alreadyPlayingNote);
324 if (
auto* note = getNotePtr (midiChannel, midiNoteNumber))
327 note->noteOffVelocity = midiNoteOffVelocity;
351 updateDimension (midiChannel, pitchbendDimension, value);
357 updateDimension (midiChannel, pressureDimension, value);
363 updateDimension (midiChannel, timbreDimension, value);
366 MPEValue MPEInstrument::getInitialValueForNewNote (
int midiChannel, MPEDimension& dimension)
const 368 if (getLastNotePlayedPtr (midiChannel) !=
nullptr)
371 return dimension.lastValueReceivedOnChannel[midiChannel - 1];
375 void MPEInstrument::updateDimension (
int midiChannel, MPEDimension& dimension,
MPEValue value)
377 dimension.lastValueReceivedOnChannel[midiChannel - 1] = value;
386 for (
auto i = notes.size(); --i >= 0;)
388 auto& note = notes.getReference (i);
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);
407 void MPEInstrument::updateDimensionMaster (
bool isLowerZone, MPEDimension& dimension,
MPEValue value)
412 if (! zone.isActive())
415 for (
auto i = notes.size(); --i >= 0;)
417 auto& note = notes.getReference (i);
419 if (! zone.isUsingChannelAsMemberChannel (note.midiChannel))
422 if (&dimension == &pitchbendDimension)
426 updateNoteTotalPitchbend (note);
429 else if (dimension.getValue (note) != value)
431 dimension.getValue (note) = value;
432 callListenersDimensionChanged (note, dimension);
438 void 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);
452 void MPEInstrument::callListenersDimensionChanged (
const MPENote& note,
const MPEDimension& dimension)
460 void MPEInstrument::updateNoteTotalPitchbend (
MPENote& note)
462 if (legacyMode.isEnabled)
470 if (! zone.isUsingChannelAsMemberChannel (note.
midiChannel))
486 auto masterPitchbendInSemitones = pitchbendDimension.lastValueReceivedOnChannel[zone.getMasterChannel() - 1]
488 * zone.masterPitchbendRange;
498 handleSustainOrSostenuto (midiChannel, isDown,
false);
504 handleSustainOrSostenuto (midiChannel, isDown,
true);
508 void 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;)
521 auto& note = notes.getReference (i);
523 if (legacyMode.isEnabled ? (note.
midiChannel == midiChannel) : zone.isUsingChannelAsMemberChannel (note.
midiChannel))
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);
587 if (
auto* note = getNotePtr (midiChannel, midiNoteNumber))
601 if (
auto* note = getLastNotePlayedPtr (midiChannel))
609 for (
auto i = notes.size(); --i >= 0;)
611 auto& note = notes.getReference (i);
613 if (note != otherThanThisNote)
621 const MPENote* MPEInstrument::getNotePtr (
int midiChannel,
int midiNoteNumber)
const noexcept
623 for (
int i = 0; i < notes.size(); ++i)
625 auto& note = notes.getReference (i);
634 MPENote* MPEInstrument::getNotePtr (
int midiChannel,
int midiNoteNumber) noexcept
636 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getNotePtr (midiChannel, midiNoteNumber));
640 const MPENote* MPEInstrument::getNotePtr (
int midiChannel,
TrackingMode mode)
const noexcept
655 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getNotePtr (midiChannel, mode));
659 const MPENote* MPEInstrument::getLastNotePlayedPtr (
int midiChannel)
const noexcept
661 for (
auto i = notes.size(); --i >= 0;)
663 auto& note = notes.getReference (i);
673 MPENote* MPEInstrument::getLastNotePlayedPtr (
int midiChannel) noexcept
675 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getLastNotePlayedPtr (midiChannel));
679 const MPENote* MPEInstrument::getHighestNotePtr (
int midiChannel)
const noexcept
681 int initialNoteMax = -1;
684 for (
auto i = notes.size(); --i >= 0;)
686 auto& note = notes.getReference (i);
700 MPENote* MPEInstrument::getHighestNotePtr (
int midiChannel) noexcept
702 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getHighestNotePtr (midiChannel));
705 const MPENote* MPEInstrument::getLowestNotePtr (
int midiChannel)
const noexcept
707 int initialNoteMin = 128;
710 for (
auto i = notes.size(); --i >= 0;)
712 auto& note = notes.getReference (i);
726 MPENote* 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;)
738 auto& note = notes.getReference (i);
751 class MPEInstrumentTests :
public UnitTest 755 :
UnitTest (
"MPEInstrument class",
"MIDI/MPE")
762 testLayout.setLowerZone (5);
763 testLayout.setUpperZone (6);
766 void runTest()
override 768 beginTest (
"initial zone layout");
775 beginTest (
"get/setZoneLayout");
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");
798 UnitTestInstrument test;
799 test.setZoneLayout (testLayout);
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);
821 expectHasFinishedNote (test, 3, 60, 33);
824 UnitTestInstrument test;
825 test.setZoneLayout (testLayout);
830 expectEquals (test.getNumPlayingNotes(), 1);
832 expectEquals (test.noteReleasedCallCounter, 0);
836 expectEquals (test.getNumPlayingNotes(), 1);
838 expectEquals (test.noteReleasedCallCounter, 0);
842 UnitTestInstrument test;
843 test.setZoneLayout (testLayout);
847 expectEquals (test.getNumPlayingNotes(), 3);
854 UnitTestInstrument test;
855 test.setZoneLayout (testLayout);
858 expectEquals (test.getNumPlayingNotes(), 1);
863 beginTest (
"noteReleased after setZoneLayout");
865 UnitTestInstrument test;
866 test.setZoneLayout (testLayout);
871 expectEquals (test.getNumPlayingNotes(), 3);
872 expectEquals (test.noteReleasedCallCounter, 0);
874 test.setZoneLayout (testLayout);
875 expectEquals (test.getNumPlayingNotes(), 0);
876 expectEquals (test.noteReleasedCallCounter, 3);
879 beginTest (
"releaseAllNotes");
881 UnitTestInstrument test;
882 test.setZoneLayout (testLayout);
886 expectEquals (test.getNumPlayingNotes(), 3);
888 test.releaseAllNotes();
889 expectEquals (test.getNumPlayingNotes(), 0);
892 beginTest (
"sustainPedal");
894 UnitTestInstrument test;
895 test.setZoneLayout (testLayout);
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");
957 UnitTestInstrument test;
958 test.setZoneLayout (testLayout);
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");
1050 beginTest (
"getMostRecentNoteOtherThan");
1090 beginTest (
"pressure");
1093 UnitTestInstrument test;
1094 test.setZoneLayout (testLayout);
1105 expectEquals (test.notePressureChangedCallCounter, 1);
1112 expectEquals (test.notePressureChangedCallCounter, 3);
1119 expectEquals (test.notePressureChangedCallCounter, 3);
1122 UnitTestInstrument test;
1123 test.setZoneLayout (testLayout);
1131 expectEquals (test.notePressureChangedCallCounter, 1);
1134 UnitTestInstrument test;
1135 test.setZoneLayout (testLayout);
1143 expectEquals (test.getNumPlayingNotes(), 1);
1145 expectEquals (test.notePressureChangedCallCounter, 1);
1148 UnitTestInstrument test;
1149 test.setZoneLayout (testLayout);
1156 UnitTestInstrument test;
1157 test.setZoneLayout (testLayout);
1165 UnitTestInstrument test;
1166 test.setZoneLayout (testLayout);
1177 UnitTestInstrument test;
1178 test.setZoneLayout (testLayout);
1192 beginTest (
"pitchbend");
1195 UnitTestInstrument test;
1196 test.setZoneLayout (testLayout);
1207 expectEquals (test.notePitchbendChangedCallCounter, 1);
1217 expectEquals (test.notePitchbendChangedCallCounter, 3);
1224 expectEquals (test.notePitchbendChangedCallCounter, 3);
1227 UnitTestInstrument test;
1228 test.setZoneLayout (testLayout);
1236 expectEquals (test.notePitchbendChangedCallCounter, 1);
1239 UnitTestInstrument test;
1240 test.setZoneLayout (testLayout);
1248 expectEquals (test.getNumPlayingNotes(), 1);
1250 expectEquals (test.notePitchbendChangedCallCounter, 1);
1253 UnitTestInstrument test;
1254 test.setZoneLayout (testLayout);
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);
1279 UnitTestInstrument test;
1280 test.setZoneLayout (testLayout);
1301 UnitTestInstrument test;
1304 test.setZoneLayout (layout);
1307 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -24.0, 0.01);
1310 test.setZoneLayout (layout);
1313 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -96.0, 0.01);
1316 test.setZoneLayout (layout);
1319 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 1.0, 0.01);
1322 test.setZoneLayout (layout);
1325 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 0.0, 0.01);
1330 UnitTestInstrument test;
1333 test.setZoneLayout (layout);
1336 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -1.0, 0.01);
1339 test.setZoneLayout (layout);
1342 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -96.0, 0.01);
1345 test.setZoneLayout (layout);
1348 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 1.0, 0.01);
1351 test.setZoneLayout (layout);
1354 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 0.0, 0.01);
1360 UnitTestInstrument test;
1364 test.setZoneLayout (layout);
1371 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -12.5, 0.01);
1375 beginTest (
"timbre");
1378 UnitTestInstrument test;
1379 test.setZoneLayout (testLayout);
1390 expectEquals (test.noteTimbreChangedCallCounter, 1);
1397 expectEquals (test.noteTimbreChangedCallCounter, 3);
1404 expectEquals (test.noteTimbreChangedCallCounter, 3);
1407 UnitTestInstrument test;
1408 test.setZoneLayout (testLayout);
1416 expectEquals (test.noteTimbreChangedCallCounter, 1);
1419 UnitTestInstrument test;
1420 test.setZoneLayout (testLayout);
1428 expectEquals (test.getNumPlayingNotes(), 1);
1430 expectEquals (test.noteTimbreChangedCallCounter, 1);
1433 UnitTestInstrument test;
1434 test.setZoneLayout (testLayout);
1447 beginTest (
"setPressureTrackingMode");
1451 UnitTestInstrument test;
1452 test.setZoneLayout (testLayout);
1462 expectEquals (test.notePressureChangedCallCounter, 1);
1466 UnitTestInstrument test;
1467 test.setZoneLayout (testLayout);
1477 expectEquals (test.notePressureChangedCallCounter, 1);
1481 UnitTestInstrument test;
1482 test.setZoneLayout (testLayout);
1492 expectEquals (test.notePressureChangedCallCounter, 1);
1496 UnitTestInstrument test;
1497 test.setZoneLayout (testLayout);
1507 expectEquals (test.notePressureChangedCallCounter, 3);
1511 beginTest (
"setPitchbendTrackingMode");
1515 UnitTestInstrument test;
1516 test.setZoneLayout (testLayout);
1526 expectEquals (test.notePitchbendChangedCallCounter, 1);
1530 UnitTestInstrument test;
1531 test.setZoneLayout (testLayout);
1541 expectEquals (test.notePitchbendChangedCallCounter, 1);
1545 UnitTestInstrument test;
1546 test.setZoneLayout (testLayout);
1556 expectEquals (test.notePitchbendChangedCallCounter, 1);
1560 UnitTestInstrument test;
1561 test.setZoneLayout (testLayout);
1571 expectEquals (test.notePitchbendChangedCallCounter, 3);
1575 beginTest (
"setTimbreTrackingMode");
1579 UnitTestInstrument test;
1580 test.setZoneLayout (testLayout);
1590 expectEquals (test.noteTimbreChangedCallCounter, 1);
1594 UnitTestInstrument test;
1595 test.setZoneLayout (testLayout);
1605 expectEquals (test.noteTimbreChangedCallCounter, 1);
1609 UnitTestInstrument test;
1610 test.setZoneLayout (testLayout);
1620 expectEquals (test.noteTimbreChangedCallCounter, 1);
1624 UnitTestInstrument test;
1625 test.setZoneLayout (testLayout);
1635 expectEquals (test.noteTimbreChangedCallCounter, 3);
1639 beginTest (
"processNextMidiEvent");
1641 UnitTestInstrument test;
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);
1778 beginTest (
"MIDI all notes off");
1780 UnitTestInstrument test;
1781 test.setZoneLayout (testLayout);
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)");
1805 UnitTestInstrument test;
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");
1881 UnitTestInstrument test;
1882 test.enableLegacyMode();
1888 expectEquals (test.getNumPlayingNotes(), 4);
1907 expectEquals (test.getNumPlayingNotes(), 0);
1912 UnitTestInstrument test;
1913 test.enableLegacyMode (2,
Range<int> (3, 8));
1924 expectEquals (test.getNumPlayingNotes(), 4);
1933 UnitTestInstrument test;
1934 test.enableLegacyMode();
1946 UnitTestInstrument test;
1947 test.enableLegacyMode();
1959 UnitTestInstrument test;
1960 test.enableLegacyMode();
1972 UnitTestInstrument test;
1973 test.enableLegacyMode();
1987 UnitTestInstrument test;
1988 test.enableLegacyMode (11);
1992 expectDoubleWithinRelativeError (test.getMostRecentNote (1).totalPitchbendInSemitones, -5.5, 0.01);
1996 UnitTestInstrument test;
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);
2019 UnitTestInstrument test;
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);
2041 UnitTestInstrument test;
2042 test.setZoneLayout (testLayout);
2044 expectEquals (test.getNumPlayingNotes(), 1);
2046 test.enableLegacyMode();
2047 expectEquals (test.getNumPlayingNotes(), 0);
2049 expectEquals (test.getNumPlayingNotes(), 1);
2051 test.setZoneLayout (testLayout);
2052 expectEquals (test.getNumPlayingNotes(), 0);
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;
2102 Base::pitchbend (midiChannel, value);
2104 pitchbendCallCounter++;
2105 lastMidiChannelReceived = midiChannel;
2106 lastMPEValueReceived = value;
2111 Base::pressure (midiChannel, value);
2113 pressureCallCounter++;
2114 lastMidiChannelReceived = midiChannel;
2115 lastMPEValueReceived = value;
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;
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;
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));
2173 void expectNote (
MPENote noteToTest,
2174 int noteOnVelocity7Bit,
2180 expect (noteToTest.
isValid());
2185 expect (noteToTest.
keyState == keyState);
2188 void expectHasFinishedNote (
const UnitTestInstrument& test,
2189 int channel,
int noteNumber,
int noteOffVelocity7Bit)
2191 expect (test.lastNoteFinished !=
nullptr);
2192 expectEquals (
int (test.lastNoteFinished->midiChannel), channel);
2193 expectEquals (
int (test.lastNoteFinished->initialNote), noteNumber);
2194 expectEquals (test.lastNoteFinished->noteOffVelocity.as7BitInt(), noteOffVelocity7Bit);
2195 expect (test.lastNoteFinished->keyState ==
MPENote::off);
2198 void expectDoubleWithinRelativeError (
double actual,
double expected,
double maxRelativeError)
2200 const double maxAbsoluteError = jmax (1.0, std::abs (expected)) * maxRelativeError;
2201 expect (std::abs (expected - actual) < maxAbsoluteError);
2208 static MPEInstrumentTests MPEInstrumentUnitTests;
2210 #endif // JUCE_UNIT_TESTS bool isResetAllControllers() const noexcept
Checks whether this message is a reset all controllers message.
virtual void notePressureChanged(MPENote changedNote)=0
Implement this callback to be informed whenever a currently playing MPE note's pressure value changes...
virtual void sostenutoPedal(int midiChannel, bool isDown)
Request a sostenuto pedal press or release.
static MidiMessage channelPressureChange(int channel, int pressure) noexcept
Creates a channel-pressure change event.
bool isPitchWheel() const noexcept
Returns true if the message is a pitch-wheel move.
This class represents the current MPE zone layout of a device capable of handling MPE...
virtual void pitchbend(int midiChannel, MPEValue pitchbend)
Request a pitchbend on the given channel with the given value (in units of MIDI pitchwheel position)...
void enableLegacyMode(int pitchbendRange=2, Range< int > channelRange=Range< int >(1, 17))
Puts the instrument into legacy mode.
Encapsulates a MIDI message.
virtual void noteReleased(MPENote finishedNote)=0
Implement this callback to be informed whenever an MPE note is released (either by a note-off message...
KeyState keyState
Current key state.
static MPEValue from7BitInt(int value) noexcept
Constructs an MPEValue from an integer between 0 and 127 (using 7-bit precision). ...
bool isLegacyModeEnabled() const noexcept
Returns true if the instrument is in legacy mode, false otherwise.
MPEInstrument() noexcept
Constructor.
bool getNextEvent(MidiMessage &result, int &samplePosition) noexcept
Retrieves a copy of the next event from the buffer.
bool isAllNotesOff() const noexcept
Checks whether this message is an all-notes-off message.
void processNextMidiEvent(const MidiMessage &message)
Pass incoming MIDI messages to an object of this class if you want the zone layout to properly react ...
MPENote getNote(int index) const noexcept
Returns the note at the given index.
void removeListener(Listener *listenerToRemove)
Removes a listener.
static MidiMessage pitchWheel(int channel, int position) noexcept
Creates a pitch-wheel move message.
The highest note (by initialNote) on the channel with the note key still down.
MPEZoneLayout getZoneLayout() const noexcept
Returns the current zone layout of the instrument.
virtual void processNextMidiEvent(const MidiMessage &message)
Process a MIDI message and trigger the appropriate method calls (noteOn, noteOff etc.)
The most recent note on the channel that is still played (key down and/or sustained).
bool isValid() const noexcept
Checks whether the MPE note is valid.
float asSignedFloat() const noexcept
Retrieves the current value mapped to a float between -1.0f and 1.0f.
virtual void sustainPedal(int midiChannel, bool isDown)
Request a sustain pedal press or release.
KeyState
Possible values for the note key state.
int as14BitInt() const noexcept
Retrieves the current value as an integer between 0 and 16383.
Derive from this class to be informed about any changes in the expressive MIDI notes played by this i...
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 ...
virtual void notePitchbendChanged(MPENote changedNote)=0
Implement this callback to be informed whenever a currently playing MPE note's pitchbend value change...
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 ...
const Zone getUpperZone() const noexcept
Returns a struct representing the upper MPE zone.
void setTimbreTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the timbre dimension.
This is a base class for classes that perform a unit test.
int as7BitInt() const noexcept
Retrieves the current value as an integer between 0 and 127.
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
Creates a key-up message.
bool isSostenutoPedalOn() const noexcept
Returns true if this message is a 'sostenuto pedal down' controller message.
MPEValue noteOffVelocity
The release velocity ("lift") of the note after a note-off has been received.
TrackingMode
The MPE note tracking mode.
void setLegacyModeChannelRange(Range< int > channelRange)
Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode.
int getControllerNumber() const noexcept
Returns the controller number of a controller message.
uint8 initialNote
The MIDI note number that was sent when the note was triggered.
int getChannel() const noexcept
Returns the midi channel associated with the message.
MPEValue timbre
Current value of the note's third expressive dimension, typically encoding some kind of timbre parame...
void setPressureTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the pressure dimension.
bool isController() const noexcept
Returns true if this is a midi controller message.
MPEValue pressure
Current pressure with which the note is held down.
const Zone getLowerZone() const noexcept
Returns a struct representing the lower MPE zone.
int getNumPlayingNotes() const noexcept
Returns the number of MPE notes currently played by the instrument.
virtual void noteKeyStateChanged(MPENote changedNote)=0
Implement this callback to be informed whether a currently playing MPE note's key state (whether the ...
uint8 getVelocity() const noexcept
Returns the velocity of a note-on or note-off message.
virtual void timbre(int midiChannel, MPEValue value)
Request a third dimension (timbre) change on the given channel with the given value.
The lowest note (by initialNote) on the channel with the note key still down.
static MidiMessage allControllersOff(int channel) noexcept
Creates an all-controllers-off message.
The note key is down and sustained (by a sustain or sostenuto pedal).
void releaseAllNotes()
Discard all currently playing notes.
virtual void noteAdded(MPENote newNote)=0
Implement this callback to be informed whenever a new expressive MIDI note is triggered.
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...
The note key is currently down (pressed).
static MPEValue centreValue() noexcept
Constructs an MPEValue corresponding to the centre value.
void setLegacyModePitchbendRange(int pitchbendRange)
Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode...
virtual ~MPEInstrument()
Destructor.
bool isChannelPressure() const noexcept
Returns true if the message is a channel-pressure change event.
static MidiMessage controllerEvent(int channel, int controllerType, int value) noexcept
Creates a controller message.
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.
double totalPitchbendInSemitones
Current effective pitchbend of the note in units of semitones, relative to initialNote.
int getPitchWheelValue() const noexcept
Returns the pitch wheel position from a pitch-wheel move message.
int getLegacyModePitchbendRange() const noexcept
Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode...
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...
int getChannelPressureValue() const noexcept
Returns the pressure from a channel pressure change message.
virtual void noteOff(int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity)
Request a note-off.
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
Returns true if this message is a 'key-up' event.
Holds a sequence of time-stamped midi events.
bool isSustainPedalOn() const noexcept
Returns true if this message is a 'sustain pedal down' controller message.
void setZoneLayout(MPEZoneLayout newLayout)
Re-sets the zone layout of the instrument to the one passed in.
bool isNoteOn(bool returnTrueForVelocity0=false) const noexcept
Returns true if this message is a 'key-down' event.
void addListener(Listener *listenerToAdd)
Adds a listener.
uint8 midiChannel
The MIDI channel which this note uses.
This struct represents a playing MPE note.
virtual void pressure(int midiChannel, MPEValue value)
Request a pressure change on the given channel with the given value.
void clearAllZones()
Clears the lower and upper zones of this layout, making them both inactive and disabling MPE mode...
MPEValue pitchbend
Current per-note pitchbend of the note (in units of MIDI pitchwheel position).
The note is sustained (by a sustain or sostenuto pedal).
int getControllerValue() const noexcept
Returns the controller value from a controller message.
Used to iterate through the events in a MidiBuffer.
void addEvents(const MidiBuffer &otherBuffer, int startSample, int numSamples, int sampleDeltaToAdd)
Adds some events from another buffer to this one.
static MPEValue from14BitInt(int value) noexcept
Constructs an MPEValue from an integer between 0 and 16383 (using 14-bit precision).
MPENote getMostRecentNoteOtherThan(MPENote otherThanThisNote) const noexcept
Returns the most recent note that is not the note passed in.
This class represents an instrument handling MPE.
int getNoteNumber() const noexcept
Returns the midi note number for note-on and note-off messages.
void setPitchbendTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the pitchbend dimension.
Automatically locks and unlocks a mutex object.
This class represents a single value for any of the MPE dimensions of control.
static MPEValue minValue() noexcept
Constructs an MPEValue corresponding to the minimum value.
virtual void noteTimbreChanged(MPENote changedNote)=0
Implement this callback to be informed whenever a currently playing MPE note's timbre value changes...
All notes on the channel (key down and/or sustained).
Range< int > getLegacyModeChannelRange() const noexcept
Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode.
bool isMasterChannel(int midiChannel) const noexcept
Returns true if the given MIDI channel (1-16) is a master channel (channel 1 or 16).
MPEValue noteOnVelocity
The velocity ("strike") of the note-on.
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.