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);
154 else if (message.
isAftertouch()) processMidiAfterTouchMessage (message);
158 void MPEInstrument::processMidiNoteOnMessage (
const MidiMessage& message)
179 void MPEInstrument::processMidiNoteOffMessage (
const MidiMessage& message)
187 void MPEInstrument::processMidiPitchWheelMessage (
const MidiMessage& message)
194 void MPEInstrument::processMidiChannelPressureMessage (
const MidiMessage& message)
201 void MPEInstrument::processMidiControllerMessage (
const MidiMessage& message)
216 void MPEInstrument::processMidiResetAllControllersMessage (
const MidiMessage& message)
221 if (legacyMode.isEnabled && legacyMode.channelRange.contains (message.
getChannel()))
223 for (
auto i = notes.size(); --i >= 0;)
225 auto& note = notes.getReference (i);
241 for (
auto i = notes.size(); --i >= 0;)
243 auto& note = notes.getReference (i);
245 if (zone.isUsing (note.midiChannel))
256 void MPEInstrument::processMidiAfterTouchMessage (
const MidiMessage& message)
266 void MPEInstrument::handlePressureMSB (
int midiChannel,
int value) noexcept
268 auto lsb = lastPressureLowerBitReceivedOnChannel[midiChannel - 1];
274 void MPEInstrument::handlePressureLSB (
int midiChannel,
int value) noexcept
276 lastPressureLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value);
279 void MPEInstrument::handleTimbreMSB (
int midiChannel,
int value) noexcept
281 auto lsb = lastTimbreLowerBitReceivedOnChannel[midiChannel - 1];
287 void MPEInstrument::handleTimbreLSB (
int midiChannel,
int value) noexcept
289 lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value);
303 getInitialValueForNewNote (midiChannel, pitchbendDimension),
304 getInitialValueForNewNote (midiChannel, pressureDimension),
305 getInitialValueForNewNote (midiChannel, timbreDimension),
309 updateNoteTotalPitchbend (newNote);
311 if (
auto* alreadyPlayingNote = getNotePtr (midiChannel, midiNoteNumber))
317 notes.remove (alreadyPlayingNote);
334 if (
auto* note = getNotePtr (midiChannel, midiNoteNumber))
337 note->noteOffVelocity = midiNoteOffVelocity;
340 if (getLastNotePlayedPtr (midiChannel) ==
nullptr)
363 updateDimension (midiChannel, pitchbendDimension, value);
369 updateDimension (midiChannel, pressureDimension, value);
375 updateDimension (midiChannel, timbreDimension, value);
382 for (
auto i = notes.size(); --i >= 0;)
384 auto& note = notes.getReference (i);
386 if (note.midiChannel == midiChannel
387 && note.initialNote == midiNoteNumber
388 && pressureDimension.getValue (note) != value)
390 pressureDimension.getValue (note) = value;
391 callListenersDimensionChanged (note, pressureDimension);
396 MPEValue MPEInstrument::getInitialValueForNewNote (
int midiChannel, MPEDimension& dimension)
const 398 if (getLastNotePlayedPtr (midiChannel) !=
nullptr)
401 return dimension.lastValueReceivedOnChannel[midiChannel - 1];
405 void MPEInstrument::updateDimension (
int midiChannel, MPEDimension& dimension,
MPEValue value)
407 dimension.lastValueReceivedOnChannel[midiChannel - 1] = value;
416 for (
auto i = notes.size(); --i >= 0;)
418 auto& note = notes.getReference (i);
420 if (note.midiChannel == midiChannel)
421 updateDimensionForNote (note, dimension, value);
426 if (
auto* note = getNotePtr (midiChannel, dimension.trackingMode))
427 updateDimensionForNote (*note, dimension, value);
432 updateDimensionMaster (midiChannel == 1, dimension, value);
437 void MPEInstrument::updateDimensionMaster (
bool isLowerZone, MPEDimension& dimension,
MPEValue value)
442 if (! zone.isActive())
445 for (
auto i = notes.size(); --i >= 0;)
447 auto& note = notes.getReference (i);
449 if (! zone.isUsing (note.midiChannel))
452 if (&dimension == &pitchbendDimension)
456 updateNoteTotalPitchbend (note);
459 else if (dimension.getValue (note) != value)
461 dimension.getValue (note) = value;
462 callListenersDimensionChanged (note, dimension);
468 void MPEInstrument::updateDimensionForNote (
MPENote& note, MPEDimension& dimension,
MPEValue value)
470 if (dimension.getValue (note) != value)
472 dimension.getValue (note) = value;
474 if (&dimension == &pitchbendDimension)
475 updateNoteTotalPitchbend (note);
477 callListenersDimensionChanged (note, dimension);
482 void MPEInstrument::callListenersDimensionChanged (
const MPENote& note,
const MPEDimension& dimension)
490 void MPEInstrument::updateNoteTotalPitchbend (
MPENote& note)
492 if (legacyMode.isEnabled)
514 auto notePitchbendInSemitones = 0.0f;
516 if (zone.isUsingChannelAsMemberChannel (note.
midiChannel))
519 auto masterPitchbendInSemitones = pitchbendDimension.lastValueReceivedOnChannel[zone.getMasterChannel() - 1]
521 * zone.masterPitchbendRange;
531 handleSustainOrSostenuto (midiChannel, isDown,
false);
537 handleSustainOrSostenuto (midiChannel, isDown,
true);
541 void MPEInstrument::handleSustainOrSostenuto (
int midiChannel,
bool isDown,
bool isSostenuto)
546 if (legacyMode.isEnabled ? (! legacyMode.channelRange.contains (midiChannel)) : (!
isMasterChannel (midiChannel)))
549 auto zone = (midiChannel == 1 ? zoneLayout.
getLowerZone()
552 for (
auto i = notes.size(); --i >= 0;)
554 auto& note = notes.getReference (i);
579 if (legacyMode.isEnabled)
581 isMemberChannelSustained[midiChannel - 1] = isDown;
585 if (zone.isLowerZone())
586 for (
auto i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i)
587 isMemberChannelSustained[i - 1] = isDown;
589 for (
auto i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i)
590 isMemberChannelSustained[i - 1] = isDown;
598 if (legacyMode.isEnabled)
599 return legacyMode.channelRange.contains (midiChannel);
601 return zoneLayout.
getLowerZone().isUsingChannelAsMemberChannel (midiChannel)
602 || zoneLayout.
getUpperZone().isUsingChannelAsMemberChannel (midiChannel);
607 if (legacyMode.isEnabled)
613 return (lowerZone.isActive() && midiChannel == lowerZone.getMasterChannel())
614 || (upperZone.isActive() && midiChannel == upperZone.getMasterChannel());
619 if (legacyMode.isEnabled)
620 return legacyMode.channelRange.contains (midiChannel);
634 if (
auto* note = getNotePtr (midiChannel, midiNoteNumber))
648 if (
auto* note = getLastNotePlayedPtr (midiChannel))
656 for (
auto i = notes.size(); --i >= 0;)
658 auto& note = notes.getReference (i);
660 if (note != otherThanThisNote)
668 const MPENote* MPEInstrument::getNotePtr (
int midiChannel,
int midiNoteNumber)
const noexcept
670 for (
int i = 0; i < notes.size(); ++i)
672 auto& note = notes.getReference (i);
681 MPENote* MPEInstrument::getNotePtr (
int midiChannel,
int midiNoteNumber) noexcept
683 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getNotePtr (midiChannel, midiNoteNumber));
687 const MPENote* MPEInstrument::getNotePtr (
int midiChannel,
TrackingMode mode)
const noexcept
702 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getNotePtr (midiChannel, mode));
706 const MPENote* MPEInstrument::getLastNotePlayedPtr (
int midiChannel)
const noexcept
708 for (
auto i = notes.size(); --i >= 0;)
710 auto& note = notes.getReference (i);
720 MPENote* MPEInstrument::getLastNotePlayedPtr (
int midiChannel) noexcept
722 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getLastNotePlayedPtr (midiChannel));
726 const MPENote* MPEInstrument::getHighestNotePtr (
int midiChannel)
const noexcept
728 int initialNoteMax = -1;
729 const MPENote* result =
nullptr;
731 for (
auto i = notes.size(); --i >= 0;)
733 auto& note = notes.getReference (i);
747 MPENote* MPEInstrument::getHighestNotePtr (
int midiChannel) noexcept
749 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getHighestNotePtr (midiChannel));
752 const MPENote* MPEInstrument::getLowestNotePtr (
int midiChannel)
const noexcept
754 int initialNoteMin = 128;
755 const MPENote* result =
nullptr;
757 for (
auto i = notes.size(); --i >= 0;)
759 auto& note = notes.getReference (i);
773 MPENote* MPEInstrument::getLowestNotePtr (
int midiChannel) noexcept
775 return const_cast<MPENote*
> (
static_cast<const MPEInstrument&
> (*this).getLowestNotePtr (midiChannel));
783 for (
auto i = notes.size(); --i >= 0;)
785 auto& note = notes.getReference (i);
799 class MPEInstrumentTests :
public UnitTest 803 :
UnitTest (
"MPEInstrument class", UnitTestCategories::midi)
810 testLayout.setLowerZone (5);
811 testLayout.setUpperZone (6);
814 void runTest()
override 816 beginTest (
"initial zone layout");
823 beginTest (
"get/setZoneLayout");
832 expectEquals (newLayout.getLowerZone().getMasterChannel(), 1);
833 expectEquals (newLayout.getLowerZone().numMemberChannels, 5);
834 expectEquals (newLayout.getUpperZone().getMasterChannel(), 16);
835 expectEquals (newLayout.getUpperZone().numMemberChannels, 6);
838 beginTest (
"noteOn / noteOff");
846 UnitTestInstrument test;
847 test.setZoneLayout (testLayout);
851 expectEquals (test.getNumPlayingNotes(), 0);
852 expectEquals (test.noteAddedCallCounter, 0);
856 expectEquals (test.getNumPlayingNotes(), 1);
857 expectEquals (test.noteAddedCallCounter, 1);
862 expectEquals (test.getNumPlayingNotes(), 0);
863 expectEquals (test.noteReleasedCallCounter, 1);
864 expectHasFinishedNote (test, 3, 60, 33);
869 expectEquals (test.getNumPlayingNotes(), 1);
870 expectEquals (test.noteAddedCallCounter, 2);
875 expectEquals (test.getNumPlayingNotes(), 0);
876 expectEquals (test.noteReleasedCallCounter, 2);
877 expectHasFinishedNote (test, 1, 62, 33);
881 UnitTestInstrument test;
882 test.setZoneLayout (testLayout);
887 expectEquals (test.getNumPlayingNotes(), 1);
889 expectEquals (test.noteReleasedCallCounter, 0);
893 expectEquals (test.getNumPlayingNotes(), 1);
895 expectEquals (test.noteReleasedCallCounter, 0);
900 UnitTestInstrument test;
901 test.setZoneLayout (testLayout);
905 expectEquals (test.getNumPlayingNotes(), 3);
912 UnitTestInstrument test;
913 test.setZoneLayout (testLayout);
916 expectEquals (test.getNumPlayingNotes(), 1);
921 beginTest (
"noteReleased after setZoneLayout");
923 UnitTestInstrument test;
924 test.setZoneLayout (testLayout);
929 expectEquals (test.getNumPlayingNotes(), 3);
930 expectEquals (test.noteReleasedCallCounter, 0);
932 test.setZoneLayout (testLayout);
933 expectEquals (test.getNumPlayingNotes(), 0);
934 expectEquals (test.noteReleasedCallCounter, 3);
937 beginTest (
"releaseAllNotes");
939 UnitTestInstrument test;
940 test.setZoneLayout (testLayout);
944 expectEquals (test.getNumPlayingNotes(), 3);
946 test.releaseAllNotes();
947 expectEquals (test.getNumPlayingNotes(), 0);
950 beginTest (
"sustainPedal");
952 UnitTestInstrument test;
953 test.setZoneLayout (testLayout);
958 test.sustainPedal (3,
true);
963 expectEquals (test.noteKeyStateChangedCallCounter, 0);
966 test.sustainPedal (7,
true);
969 expectEquals (test.noteKeyStateChangedCallCounter, 0);
972 test.sustainPedal (1,
true);
975 expectEquals (test.noteKeyStateChangedCallCounter, 1);
978 test.sustainPedal (1,
false);
981 expectEquals (test.noteKeyStateChangedCallCounter, 2);
984 test.sustainPedal (1,
true);
985 expectEquals (test.noteKeyStateChangedCallCounter, 3);
988 expectEquals (test.noteKeyStateChangedCallCounter, 3);
991 test.sustainPedal (11,
true);
995 expectEquals (test.noteReleasedCallCounter, 1);
1001 expectEquals (test.getNumPlayingNotes(), 2);
1002 expectEquals (test.noteReleasedCallCounter, 2);
1003 expectEquals (test.noteKeyStateChangedCallCounter, 5);
1008 test.sustainPedal (1,
false);
1009 expectEquals (test.getNumPlayingNotes(), 0);
1010 expectEquals (test.noteReleasedCallCounter, 4);
1013 beginTest (
"sostenutoPedal");
1015 UnitTestInstrument test;
1016 test.setZoneLayout (testLayout);
1021 test.sostenutoPedal (3,
true);
1024 expectEquals (test.noteKeyStateChangedCallCounter, 0);
1027 test.sostenutoPedal (9,
true);
1030 expectEquals (test.noteKeyStateChangedCallCounter, 0);
1033 test.sostenutoPedal (1,
true);
1036 expectEquals (test.noteKeyStateChangedCallCounter, 1);
1039 test.sostenutoPedal (1,
false);
1042 expectEquals (test.noteKeyStateChangedCallCounter, 2);
1045 test.sostenutoPedal (1,
true);
1046 expectEquals (test.noteKeyStateChangedCallCounter, 3);
1048 expectEquals (test.getNumPlayingNotes(), 3);
1052 expectEquals (test.noteKeyStateChangedCallCounter, 3);
1059 expectEquals (test.getNumPlayingNotes(), 1);
1061 expectEquals (test.noteReleasedCallCounter, 2);
1062 expectEquals (test.noteKeyStateChangedCallCounter, 4);
1065 test.sustainPedal (1,
false);
1066 expectEquals (test.getNumPlayingNotes(), 0);
1067 expectEquals (test.noteReleasedCallCounter, 3);
1070 beginTest (
"getMostRecentNote");
1108 beginTest (
"getMostRecentNoteOtherThan");
1148 beginTest (
"pressure");
1151 UnitTestInstrument test;
1152 test.setZoneLayout (testLayout);
1163 expectEquals (test.notePressureChangedCallCounter, 1);
1170 expectEquals (test.notePressureChangedCallCounter, 3);
1177 expectEquals (test.notePressureChangedCallCounter, 3);
1180 UnitTestInstrument test;
1181 test.setZoneLayout (testLayout);
1189 expectEquals (test.notePressureChangedCallCounter, 1);
1192 UnitTestInstrument test;
1193 test.setZoneLayout (testLayout);
1201 expectEquals (test.getNumPlayingNotes(), 1);
1203 expectEquals (test.notePressureChangedCallCounter, 1);
1206 UnitTestInstrument test;
1207 test.setZoneLayout (testLayout);
1214 UnitTestInstrument test;
1215 test.setZoneLayout (testLayout);
1223 UnitTestInstrument test;
1224 test.setZoneLayout (testLayout);
1235 UnitTestInstrument test;
1236 test.setZoneLayout (testLayout);
1250 UnitTestInstrument test;
1251 test.setZoneLayout (testLayout);
1266 beginTest (
"pitchbend");
1269 UnitTestInstrument test;
1270 test.setZoneLayout (testLayout);
1281 expectEquals (test.notePitchbendChangedCallCounter, 1);
1291 expectEquals (test.notePitchbendChangedCallCounter, 3);
1298 expectEquals (test.notePitchbendChangedCallCounter, 3);
1301 UnitTestInstrument test;
1302 test.setZoneLayout (testLayout);
1310 expectEquals (test.notePitchbendChangedCallCounter, 1);
1313 UnitTestInstrument test;
1314 test.setZoneLayout (testLayout);
1322 expectEquals (test.getNumPlayingNotes(), 1);
1324 expectEquals (test.notePitchbendChangedCallCounter, 1);
1327 UnitTestInstrument test;
1328 test.setZoneLayout (testLayout);
1339 test.sustainPedal (1,
true);
1341 expectEquals (test.getNumPlayingNotes(), 1);
1343 expectEquals (test.noteKeyStateChangedCallCounter, 2);
1347 expectEquals (test.getNumPlayingNotes(), 2);
1350 expectEquals (test.notePitchbendChangedCallCounter, 1);
1353 UnitTestInstrument test;
1354 test.setZoneLayout (testLayout);
1375 UnitTestInstrument test;
1378 test.setZoneLayout (layout);
1381 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -24.0, 0.01);
1384 test.setZoneLayout (layout);
1387 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -96.0, 0.01);
1390 test.setZoneLayout (layout);
1393 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 1.0, 0.01);
1396 test.setZoneLayout (layout);
1399 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 0.0, 0.01);
1404 UnitTestInstrument test;
1407 test.setZoneLayout (layout);
1410 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -1.0, 0.01);
1413 test.setZoneLayout (layout);
1416 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -96.0, 0.01);
1419 test.setZoneLayout (layout);
1422 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 1.0, 0.01);
1425 test.setZoneLayout (layout);
1428 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 0.0, 0.01);
1434 UnitTestInstrument test;
1438 test.setZoneLayout (layout);
1445 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -12.5, 0.01);
1449 beginTest (
"timbre");
1452 UnitTestInstrument test;
1453 test.setZoneLayout (testLayout);
1464 expectEquals (test.noteTimbreChangedCallCounter, 1);
1471 expectEquals (test.noteTimbreChangedCallCounter, 3);
1478 expectEquals (test.noteTimbreChangedCallCounter, 3);
1481 UnitTestInstrument test;
1482 test.setZoneLayout (testLayout);
1490 expectEquals (test.noteTimbreChangedCallCounter, 1);
1493 UnitTestInstrument test;
1494 test.setZoneLayout (testLayout);
1502 expectEquals (test.getNumPlayingNotes(), 1);
1504 expectEquals (test.noteTimbreChangedCallCounter, 1);
1507 UnitTestInstrument test;
1508 test.setZoneLayout (testLayout);
1521 beginTest (
"setPressureTrackingMode");
1525 UnitTestInstrument test;
1526 test.setZoneLayout (testLayout);
1536 expectEquals (test.notePressureChangedCallCounter, 1);
1540 UnitTestInstrument test;
1541 test.setZoneLayout (testLayout);
1551 expectEquals (test.notePressureChangedCallCounter, 1);
1555 UnitTestInstrument test;
1556 test.setZoneLayout (testLayout);
1566 expectEquals (test.notePressureChangedCallCounter, 1);
1570 UnitTestInstrument test;
1571 test.setZoneLayout (testLayout);
1581 expectEquals (test.notePressureChangedCallCounter, 3);
1585 beginTest (
"setPitchbendTrackingMode");
1589 UnitTestInstrument test;
1590 test.setZoneLayout (testLayout);
1600 expectEquals (test.notePitchbendChangedCallCounter, 1);
1604 UnitTestInstrument test;
1605 test.setZoneLayout (testLayout);
1615 expectEquals (test.notePitchbendChangedCallCounter, 1);
1619 UnitTestInstrument test;
1620 test.setZoneLayout (testLayout);
1630 expectEquals (test.notePitchbendChangedCallCounter, 1);
1634 UnitTestInstrument test;
1635 test.setZoneLayout (testLayout);
1645 expectEquals (test.notePitchbendChangedCallCounter, 3);
1649 beginTest (
"setTimbreTrackingMode");
1653 UnitTestInstrument test;
1654 test.setZoneLayout (testLayout);
1664 expectEquals (test.noteTimbreChangedCallCounter, 1);
1668 UnitTestInstrument test;
1669 test.setZoneLayout (testLayout);
1679 expectEquals (test.noteTimbreChangedCallCounter, 1);
1683 UnitTestInstrument test;
1684 test.setZoneLayout (testLayout);
1694 expectEquals (test.noteTimbreChangedCallCounter, 1);
1698 UnitTestInstrument test;
1699 test.setZoneLayout (testLayout);
1709 expectEquals (test.noteTimbreChangedCallCounter, 3);
1713 beginTest (
"processNextMidiEvent");
1715 UnitTestInstrument test;
1720 expectEquals (test.noteOnCallCounter, 1);
1721 expectEquals (test.lastMidiChannelReceived, 3);
1722 expectEquals (test.lastMidiNoteNumberReceived, 42);
1723 expectEquals (test.lastMPEValueReceived.as7BitInt(), 92);
1728 expectEquals (test.noteOffCallCounter, 1);
1729 expectEquals (test.lastMidiChannelReceived, 4);
1730 expectEquals (test.lastMidiNoteNumberReceived, 12);
1731 expectEquals (test.lastMPEValueReceived.as7BitInt(), 33);
1737 expectEquals (test.noteOffCallCounter, 2);
1738 expectEquals (test.lastMidiChannelReceived, 5);
1739 expectEquals (test.lastMidiNoteNumberReceived, 11);
1740 expectEquals (test.lastMPEValueReceived.as7BitInt(), 64);
1745 expectEquals (test.pitchbendCallCounter, 1);
1746 expectEquals (test.lastMidiChannelReceived, 1);
1747 expectEquals (test.lastMPEValueReceived.as14BitInt(), 3333);
1753 expectEquals (test.pressureCallCounter, 1);
1754 expectEquals (test.lastMidiChannelReceived, 10);
1755 expectEquals (test.lastMPEValueReceived.as7BitInt(), 35);
1762 expectEquals (test.pressureCallCounter, 2);
1763 expectEquals (test.lastMidiChannelReceived, 3);
1764 expectEquals (test.lastMPEValueReceived.as7BitInt(), 120);
1768 expectEquals (test.pressureCallCounter, 2);
1770 expectEquals (test.pressureCallCounter, 2);
1772 expectEquals (test.pressureCallCounter, 3);
1773 expectEquals (test.lastMidiChannelReceived, 4);
1774 expectEquals (test.lastMPEValueReceived.as14BitInt(), 121 + (123 << 7));
1776 expectEquals (test.pressureCallCounter, 4);
1777 expectEquals (test.lastMidiChannelReceived, 5);
1778 expectEquals (test.lastMPEValueReceived.as14BitInt(), 122 + (124 << 7));
1780 expectEquals (test.pressureCallCounter, 5);
1781 expectEquals (test.lastMidiChannelReceived, 5);
1782 expectEquals (test.lastMPEValueReceived.as7BitInt(), 64);
1786 expectEquals (test.timbreCallCounter, 1);
1787 expectEquals (test.lastMidiChannelReceived, 3);
1788 expectEquals (test.lastMPEValueReceived.as7BitInt(), 120);
1790 expectEquals (test.timbreCallCounter, 1);
1792 expectEquals (test.timbreCallCounter, 1);
1794 expectEquals (test.timbreCallCounter, 2);
1795 expectEquals (test.lastMidiChannelReceived, 4);
1796 expectEquals (test.lastMPEValueReceived.as14BitInt(), 121 + (123 << 7));
1798 expectEquals (test.timbreCallCounter, 3);
1799 expectEquals (test.lastMidiChannelReceived, 5);
1800 expectEquals (test.lastMPEValueReceived.as14BitInt(), 122 + (124 << 7));
1802 expectEquals (test.timbreCallCounter, 4);
1803 expectEquals (test.lastMidiChannelReceived, 5);
1804 expectEquals (test.lastMPEValueReceived.as7BitInt(), 64);
1808 expectEquals (test.sustainPedalCallCounter, 1);
1809 expectEquals (test.lastMidiChannelReceived, 1);
1810 expect (test.lastSustainPedalValueReceived);
1812 expectEquals (test.sustainPedalCallCounter, 2);
1813 expectEquals (test.lastMidiChannelReceived, 16);
1814 expect (! test.lastSustainPedalValueReceived);
1818 expectEquals (test.sostenutoPedalCallCounter, 1);
1819 expectEquals (test.lastMidiChannelReceived, 1);
1820 expect (test.lastSostenutoPedalValueReceived);
1822 expectEquals (test.sostenutoPedalCallCounter, 2);
1823 expectEquals (test.lastMidiChannelReceived, 16);
1824 expect (! test.lastSostenutoPedalValueReceived);
1852 beginTest (
"MIDI all notes off");
1854 UnitTestInstrument test;
1855 test.setZoneLayout (testLayout);
1860 expectEquals (test.getNumPlayingNotes(), 4);
1864 expectEquals (test.getNumPlayingNotes(), 4);
1868 expectEquals (test.getNumPlayingNotes(), 4);
1872 expectEquals (test.getNumPlayingNotes(), 2);
1874 expectEquals (test.getNumPlayingNotes(), 0);
1877 beginTest (
"MIDI all notes off (legacy mode)");
1879 UnitTestInstrument test;
1880 test.enableLegacyMode();
1885 expectEquals (test.getNumPlayingNotes(), 4);
1888 expectEquals (test.getNumPlayingNotes(), 3);
1891 expectEquals (test.getNumPlayingNotes(), 1);
1894 expectEquals (test.getNumPlayingNotes(), 0);
1897 beginTest (
"default initial values for pitchbend and timbre");
1915 beginTest (
"Legacy mode");
1955 UnitTestInstrument test;
1956 test.enableLegacyMode();
1962 expectEquals (test.getNumPlayingNotes(), 4);
1981 expectEquals (test.getNumPlayingNotes(), 0);
1986 UnitTestInstrument test;
1987 test.enableLegacyMode (2,
Range<int> (3, 8));
1998 expectEquals (test.getNumPlayingNotes(), 4);
2007 UnitTestInstrument test;
2008 test.enableLegacyMode();
2020 UnitTestInstrument test;
2021 test.enableLegacyMode();
2033 UnitTestInstrument test;
2034 test.enableLegacyMode();
2046 UnitTestInstrument test;
2047 test.enableLegacyMode();
2061 UnitTestInstrument test;
2062 test.enableLegacyMode (11);
2066 expectDoubleWithinRelativeError (test.getMostRecentNote (1).totalPitchbendInSemitones, -5.5, 0.01);
2070 UnitTestInstrument test;
2071 test.enableLegacyMode();
2073 test.sustainPedal (1,
true);
2079 expectEquals (test.getNumPlayingNotes(), 1);
2082 test.sustainPedal (1,
false);
2083 expectEquals (test.getNumPlayingNotes(), 0);
2086 test.sustainPedal (1,
true);
2088 expectEquals (test.getNumPlayingNotes(), 0);
2093 UnitTestInstrument test;
2094 test.enableLegacyMode();
2097 test.sostenutoPedal (1,
true);
2102 expectEquals (test.getNumPlayingNotes(), 1);
2105 test.sostenutoPedal (1,
false);
2106 expectEquals (test.getNumPlayingNotes(), 0);
2109 test.sostenutoPedal (1,
true);
2111 expectEquals (test.getNumPlayingNotes(), 0);
2115 UnitTestInstrument test;
2116 test.setZoneLayout (testLayout);
2118 expectEquals (test.getNumPlayingNotes(), 1);
2120 test.enableLegacyMode();
2121 expectEquals (test.getNumPlayingNotes(), 0);
2123 expectEquals (test.getNumPlayingNotes(), 1);
2125 test.setZoneLayout (testLayout);
2126 expectEquals (test.getNumPlayingNotes(), 0);
2142 UnitTestInstrument()
2143 : noteOnCallCounter (0), noteOffCallCounter (0), pitchbendCallCounter (0),
2144 pressureCallCounter (0), timbreCallCounter (0), sustainPedalCallCounter (0),
2145 sostenutoPedalCallCounter (0), noteAddedCallCounter (0), notePressureChangedCallCounter (0),
2146 notePitchbendChangedCallCounter (0), noteTimbreChangedCallCounter (0),
2147 noteKeyStateChangedCallCounter (0), noteReleasedCallCounter (0),
2148 lastMidiChannelReceived (-1), lastMidiNoteNumberReceived (-1),
2149 lastSustainPedalValueReceived (
false), lastSostenutoPedalValueReceived (
false)
2154 void noteOn (
int midiChannel,
int midiNoteNumber,
MPEValue midiNoteOnVelocity)
override 2156 Base::noteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity);
2158 noteOnCallCounter++;
2159 lastMidiChannelReceived = midiChannel;
2160 lastMidiNoteNumberReceived = midiNoteNumber;
2161 lastMPEValueReceived = midiNoteOnVelocity;
2164 void noteOff (
int midiChannel,
int midiNoteNumber,
MPEValue midiNoteOffVelocity)
override 2166 Base::noteOff (midiChannel, midiNoteNumber, midiNoteOffVelocity);
2168 noteOffCallCounter++;
2169 lastMidiChannelReceived = midiChannel;
2170 lastMidiNoteNumberReceived = midiNoteNumber;
2171 lastMPEValueReceived = midiNoteOffVelocity;
2176 Base::pitchbend (midiChannel, value);
2178 pitchbendCallCounter++;
2179 lastMidiChannelReceived = midiChannel;
2180 lastMPEValueReceived = value;
2185 Base::pressure (midiChannel, value);
2187 pressureCallCounter++;
2188 lastMidiChannelReceived = midiChannel;
2189 lastMPEValueReceived = value;
2194 Base::timbre (midiChannel, value);
2196 timbreCallCounter++;
2197 lastMidiChannelReceived = midiChannel;
2198 lastMPEValueReceived = value;
2201 void sustainPedal (
int midiChannel,
bool value)
override 2203 Base::sustainPedal (midiChannel, value);
2205 sustainPedalCallCounter++;
2206 lastMidiChannelReceived = midiChannel;
2207 lastSustainPedalValueReceived = value;
2212 Base::sostenutoPedal (midiChannel, value);
2214 sostenutoPedalCallCounter++;
2215 lastMidiChannelReceived = midiChannel;
2216 lastSostenutoPedalValueReceived = value;
2219 void aftertouch (
int midiChannel,
int midiNoteNumber,
MPEValue value)
2225 int noteOnCallCounter, noteOffCallCounter, pitchbendCallCounter,
2226 pressureCallCounter, timbreCallCounter, sustainPedalCallCounter,
2227 sostenutoPedalCallCounter, noteAddedCallCounter,
2228 notePressureChangedCallCounter, notePitchbendChangedCallCounter,
2229 noteTimbreChangedCallCounter, noteKeyStateChangedCallCounter,
2230 noteReleasedCallCounter, lastMidiChannelReceived, lastMidiNoteNumberReceived;
2232 bool lastSustainPedalValueReceived, lastSostenutoPedalValueReceived;
2234 std::unique_ptr<MPENote> lastNoteFinished;
2238 void noteAdded (
MPENote)
override { noteAddedCallCounter++; }
2240 void notePressureChanged (
MPENote)
override { notePressureChangedCallCounter++; }
2241 void notePitchbendChanged (
MPENote)
override { notePitchbendChangedCallCounter++; }
2242 void noteTimbreChanged (
MPENote)
override { noteTimbreChangedCallCounter++; }
2243 void noteKeyStateChanged (
MPENote)
override { noteKeyStateChangedCallCounter++; }
2245 void noteReleased (
MPENote finishedNote)
override 2247 noteReleasedCallCounter++;
2248 lastNoteFinished.reset (
new MPENote (finishedNote));
2253 void expectNote (
MPENote noteToTest,
2254 int noteOnVelocity7Bit,
2260 expect (noteToTest.
isValid());
2265 expect (noteToTest.
keyState == keyState);
2268 void expectHasFinishedNote (
const UnitTestInstrument& test,
2269 int channel,
int noteNumber,
int noteOffVelocity7Bit)
2271 expect (test.lastNoteFinished !=
nullptr);
2272 expectEquals (
int (test.lastNoteFinished->midiChannel), channel);
2273 expectEquals (
int (test.lastNoteFinished->initialNote), noteNumber);
2274 expectEquals (test.lastNoteFinished->noteOffVelocity.as7BitInt(), noteOffVelocity7Bit);
2275 expect (test.lastNoteFinished->keyState ==
MPENote::off);
2278 void expectDoubleWithinRelativeError (
double actual,
double expected,
double maxRelativeError)
2280 const double maxAbsoluteError = jmax (1.0, std::abs (expected)) * maxRelativeError;
2281 expect (std::abs (expected - actual) < maxAbsoluteError);
2288 static MPEInstrumentTests MPEInstrumentUnitTests;
bool isResetAllControllers() const noexcept
Checks whether this message is a reset all controllers message.
bool isAftertouch() const noexcept
Returns true if the message is an aftertouch event.
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 isUsingChannel(int midiChannel) const noexcept
Returns true if the given MIDI channel (1-16) is used by any of the MPEInstrument's MPE zones; false ...
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...
int getAfterTouchValue() const noexcept
Returns the amount of aftertouch from an aftertouch messages.
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.
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 ...
virtual void noteKeyStateChanged(MPENote changedNote)
Implement this callback to be informed whether a currently playing MPE note's key state (whether the ...
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...
virtual void polyAftertouch(int midiChannel, int midiNoteNumber, MPEValue value)
Request a poly-aftertouch change for a given note number.
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 ...
const Zone getUpperZone() const noexcept
Returns a struct representing the upper MPE zone.
virtual void noteAdded(MPENote newNote)
Implement this callback to be informed whenever a new expressive MIDI note is triggered.
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.
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.
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 void noteTimbreChanged(MPENote changedNote)
Implement this callback to be informed whenever a currently playing MPE note's timbre value changes...
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.
virtual void noteReleased(MPENote finishedNote)
Implement this callback to be informed whenever an MPE note is released (either by a note-off message...
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...
virtual void notePitchbendChanged(MPENote changedNote)
Implement this callback to be informed whenever a currently playing MPE note's pitchbend value change...
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.
virtual void notePressureChanged(MPENote changedNote)
Implement this callback to be informed whenever a currently playing MPE note's pressure value changes...
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...
bool isMemberChannel(int midiChannel) const noexcept
Returns true if the given MIDI channel (1-16) is a note channel in any of the MPEInstrument's MPE zon...
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.
static MidiMessage aftertouchChange(int channel, int noteNumber, int aftertouchAmount) noexcept
Creates an aftertouch message.
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.