mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
MPE: replaced omniMode (all MIDI channels) by legacyMode which allows to define a custom MIDI channel range. Fixed a few minor MPE bugs.
This commit is contained in:
parent
1fe45f3d41
commit
e0bd51f26c
4 changed files with 236 additions and 75 deletions
|
|
@ -39,8 +39,9 @@ MPEInstrument::MPEInstrument() noexcept
|
||||||
pressureDimension.value = &MPENote::pressure;
|
pressureDimension.value = &MPENote::pressure;
|
||||||
timbreDimension.value = &MPENote::timbre;
|
timbreDimension.value = &MPENote::timbre;
|
||||||
|
|
||||||
omniMode.isEnabled = false;
|
legacyMode.isEnabled = false;
|
||||||
omniMode.pitchbendRange = 2;
|
legacyMode.pitchbendRange = 2;
|
||||||
|
legacyMode.channelRange = Range<int> (1, 17);
|
||||||
}
|
}
|
||||||
|
|
||||||
MPEInstrument::~MPEInstrument()
|
MPEInstrument::~MPEInstrument()
|
||||||
|
|
@ -58,24 +59,53 @@ void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout)
|
||||||
releaseAllNotes();
|
releaseAllNotes();
|
||||||
|
|
||||||
const ScopedLock sl (lock);
|
const ScopedLock sl (lock);
|
||||||
omniMode.isEnabled = false;
|
legacyMode.isEnabled = false;
|
||||||
zoneLayout = newLayout;
|
zoneLayout = newLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void MPEInstrument::enableOmniMode (int pitchbendRange)
|
void MPEInstrument::enableLegacyMode (int pitchbendRange, Range<int> channelRange)
|
||||||
{
|
{
|
||||||
releaseAllNotes();
|
releaseAllNotes();
|
||||||
|
|
||||||
const ScopedLock sl (lock);
|
const ScopedLock sl (lock);
|
||||||
omniMode.isEnabled = true;
|
legacyMode.isEnabled = true;
|
||||||
omniMode.pitchbendRange = pitchbendRange;
|
legacyMode.pitchbendRange = pitchbendRange;
|
||||||
|
legacyMode.channelRange = channelRange;
|
||||||
zoneLayout.clearAllZones();
|
zoneLayout.clearAllZones();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MPEInstrument::isOmniModeEnabled() const noexcept
|
bool MPEInstrument::isLegacyModeEnabled() const noexcept
|
||||||
{
|
{
|
||||||
return omniMode.isEnabled;
|
return legacyMode.isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
Range<int> MPEInstrument::getLegacyModeChannelRange() const noexcept
|
||||||
|
{
|
||||||
|
return legacyMode.channelRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEInstrument::setLegacyModeChannelRange (Range<int> channelRange)
|
||||||
|
{
|
||||||
|
jassert (Range<int>(1, 17).contains (channelRange));
|
||||||
|
|
||||||
|
releaseAllNotes();
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
legacyMode.channelRange = channelRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MPEInstrument::getLegacyModePitchbendRange() const noexcept
|
||||||
|
{
|
||||||
|
return legacyMode.pitchbendRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPEInstrument::setLegacyModePitchbendRange (int pitchbendRange)
|
||||||
|
{
|
||||||
|
jassert (pitchbendRange >= 0 && pitchbendRange <= 96);
|
||||||
|
|
||||||
|
releaseAllNotes();
|
||||||
|
const ScopedLock sl (lock);
|
||||||
|
legacyMode.pitchbendRange = pitchbendRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -187,7 +217,10 @@ void MPEInstrument::processMidiControllerMessage (const MidiMessage& message)
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void MPEInstrument::processMidiAllNotesOffMessage (const MidiMessage& message)
|
void MPEInstrument::processMidiAllNotesOffMessage (const MidiMessage& message)
|
||||||
{
|
{
|
||||||
if (omniMode.isEnabled)
|
// in MPE mode, "all notes off" is per-zone and expected on the master channel;
|
||||||
|
// in legacy mode, "all notes off" is per MIDI channel (within the channel range used).
|
||||||
|
|
||||||
|
if (legacyMode.isEnabled && legacyMode.channelRange.contains (message.getChannel()))
|
||||||
{
|
{
|
||||||
for (int i = notes.size(); --i >= 0;)
|
for (int i = notes.size(); --i >= 0;)
|
||||||
{
|
{
|
||||||
|
|
@ -267,7 +300,7 @@ void MPEInstrument::noteOn (int midiChannel,
|
||||||
int midiNoteNumber,
|
int midiNoteNumber,
|
||||||
MPEValue midiNoteOnVelocity)
|
MPEValue midiNoteOnVelocity)
|
||||||
{
|
{
|
||||||
if (! isNoteChannel (midiChannel) && ! omniMode.isEnabled)
|
if (! isNoteChannel (midiChannel))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MPENote newNote (midiChannel,
|
MPENote newNote (midiChannel,
|
||||||
|
|
@ -299,7 +332,7 @@ void MPEInstrument::noteOff (int midiChannel,
|
||||||
int midiNoteNumber,
|
int midiNoteNumber,
|
||||||
MPEValue midiNoteOffVelocity)
|
MPEValue midiNoteOffVelocity)
|
||||||
{
|
{
|
||||||
if (notes.empty() || (! isNoteChannel (midiChannel) && ! omniMode.isEnabled))
|
if (notes.empty() || ! isNoteChannel (midiChannel))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ScopedLock sl (lock);
|
const ScopedLock sl (lock);
|
||||||
|
|
@ -357,7 +390,7 @@ void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, M
|
||||||
{
|
{
|
||||||
updateDimensionMaster (*zone, dimension, value);
|
updateDimensionMaster (*zone, dimension, value);
|
||||||
}
|
}
|
||||||
else if (isNoteChannel (midiChannel) || omniMode.isEnabled)
|
else if (isNoteChannel (midiChannel))
|
||||||
{
|
{
|
||||||
if (dimension.trackingMode == allNotesOnChannel)
|
if (dimension.trackingMode == allNotesOnChannel)
|
||||||
{
|
{
|
||||||
|
|
@ -429,9 +462,9 @@ void MPEInstrument::callListenersDimensionChanged (MPENote& note, MPEDimension&
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void MPEInstrument::updateNoteTotalPitchbend (MPENote& note)
|
void MPEInstrument::updateNoteTotalPitchbend (MPENote& note)
|
||||||
{
|
{
|
||||||
if (omniMode.isEnabled)
|
if (legacyMode.isEnabled)
|
||||||
{
|
{
|
||||||
note.totalPitchbendInSemitones = note.pitchbend.asSignedFloat() * omniMode.pitchbendRange;
|
note.totalPitchbendInSemitones = note.pitchbend.asSignedFloat() * legacyMode.pitchbendRange;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -465,18 +498,19 @@ void MPEInstrument::sostenutoPedal (int midiChannel, bool isDown)
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto)
|
void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto)
|
||||||
{
|
{
|
||||||
|
// in MPE mode, sustain/sostenuto is per-zone and expected on the master channel;
|
||||||
|
// in legacy mode, sustain/sostenuto is per MIDI channel (within the channel range used).
|
||||||
|
|
||||||
MPEZone* affectedZone = zoneLayout.getZoneByMasterChannel (midiChannel);
|
MPEZone* affectedZone = zoneLayout.getZoneByMasterChannel (midiChannel);
|
||||||
|
|
||||||
if (affectedZone == nullptr && ! omniMode.isEnabled)
|
if (legacyMode.isEnabled ? (! legacyMode.channelRange.contains (midiChannel)) : (affectedZone == nullptr))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (int i = notes.size(); --i >= 0;)
|
for (int i = notes.size(); --i >= 0;)
|
||||||
{
|
{
|
||||||
MPENote& note = notes.getReference (i);
|
MPENote& note = notes.getReference (i);
|
||||||
|
|
||||||
if ((omniMode.isEnabled
|
if (legacyMode.isEnabled ? (note.midiChannel == midiChannel) : affectedZone->isUsingChannel (note.midiChannel))
|
||||||
|| note.midiChannel == midiChannel)
|
|
||||||
|| affectedZone->isUsingChannel (note.midiChannel))
|
|
||||||
{
|
{
|
||||||
if (note.keyState == MPENote::keyDown && isDown)
|
if (note.keyState == MPENote::keyDown && isDown)
|
||||||
note.keyState = MPENote::keyDownAndSustained;
|
note.keyState = MPENote::keyDownAndSustained;
|
||||||
|
|
@ -499,7 +533,7 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool
|
||||||
|
|
||||||
if (! isSostenuto)
|
if (! isSostenuto)
|
||||||
{
|
{
|
||||||
if (omniMode.isEnabled)
|
if (legacyMode.isEnabled)
|
||||||
isNoteChannelSustained[midiChannel - 1] = isDown;
|
isNoteChannelSustained[midiChannel - 1] = isDown;
|
||||||
else
|
else
|
||||||
for (int i = affectedZone->getFirstNoteChannel(); i <= affectedZone->getLastNoteChannel(); ++i)
|
for (int i = affectedZone->getFirstNoteChannel(); i <= affectedZone->getLastNoteChannel(); ++i)
|
||||||
|
|
@ -510,11 +544,17 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
bool MPEInstrument::isNoteChannel (int midiChannel) const noexcept
|
bool MPEInstrument::isNoteChannel (int midiChannel) const noexcept
|
||||||
{
|
{
|
||||||
|
if (legacyMode.isEnabled)
|
||||||
|
return legacyMode.channelRange.contains (midiChannel);
|
||||||
|
|
||||||
return zoneLayout.getZoneByNoteChannel (midiChannel) != nullptr;
|
return zoneLayout.getZoneByNoteChannel (midiChannel) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MPEInstrument::isMasterChannel (int midiChannel) const noexcept
|
bool MPEInstrument::isMasterChannel (int midiChannel) const noexcept
|
||||||
{
|
{
|
||||||
|
if (legacyMode.isEnabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
return zoneLayout.getZoneByMasterChannel (midiChannel) != nullptr;
|
return zoneLayout.getZoneByMasterChannel (midiChannel) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1672,10 +1712,10 @@ public:
|
||||||
expectEquals (test.getNumPlayingNotes(), 0);
|
expectEquals (test.getNumPlayingNotes(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
beginTest ("MIDI all notes off (omni mode)");
|
beginTest ("MIDI all notes off (legacy mode)");
|
||||||
{
|
{
|
||||||
UnitTestInstrument test;
|
UnitTestInstrument test;
|
||||||
test.enableOmniMode();
|
test.enableLegacyMode();
|
||||||
test.noteOn (3, 60, MPEValue::from7BitInt (100));
|
test.noteOn (3, 60, MPEValue::from7BitInt (100));
|
||||||
test.noteOn (4, 61, MPEValue::from7BitInt (100));
|
test.noteOn (4, 61, MPEValue::from7BitInt (100));
|
||||||
test.noteOn (15, 62, MPEValue::from7BitInt (100));
|
test.noteOn (15, 62, MPEValue::from7BitInt (100));
|
||||||
|
|
@ -1719,27 +1759,48 @@ public:
|
||||||
expectNote (test.getMostRecentNote (3), 100, 33, 4444, 55, MPENote::keyDown);
|
expectNote (test.getMostRecentNote (3), 100, 33, 4444, 55, MPENote::keyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
beginTest ("Omni mode");
|
beginTest ("Legacy mode");
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
// basic check
|
// basic check
|
||||||
MPEInstrument test;
|
MPEInstrument test;
|
||||||
expect (! test.isOmniModeEnabled());
|
expect (! test.isLegacyModeEnabled());
|
||||||
|
|
||||||
test.setZoneLayout (testLayout);
|
test.setZoneLayout (testLayout);
|
||||||
expect (! test.isOmniModeEnabled());
|
expect (! test.isLegacyModeEnabled());
|
||||||
|
|
||||||
test.enableOmniMode();
|
test.enableLegacyMode();
|
||||||
expect (test.isOmniModeEnabled());
|
expect (test.isLegacyModeEnabled());
|
||||||
|
|
||||||
test.setZoneLayout (testLayout);
|
test.setZoneLayout (testLayout);
|
||||||
expect (! test.isOmniModeEnabled());
|
expect (! test.isLegacyModeEnabled());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// constructor w/o default arguments
|
||||||
|
MPEInstrument test;
|
||||||
|
test.enableLegacyMode (0, Range<int> (1, 11));
|
||||||
|
expectEquals (test.getLegacyModePitchbendRange(), 0);
|
||||||
|
expect (test.getLegacyModeChannelRange() == Range<int> (1, 11));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// getters and setters
|
||||||
|
MPEInstrument test;
|
||||||
|
test.enableLegacyMode();
|
||||||
|
|
||||||
|
expectEquals (test.getLegacyModePitchbendRange(), 2);
|
||||||
|
expect (test.getLegacyModeChannelRange() == Range<int> (1, 17));
|
||||||
|
|
||||||
|
test.setLegacyModePitchbendRange (96);
|
||||||
|
expectEquals (test.getLegacyModePitchbendRange(), 96);
|
||||||
|
|
||||||
|
test.setLegacyModeChannelRange (Range<int> (10, 12));
|
||||||
|
expect (test.getLegacyModeChannelRange() == Range<int> (10, 12));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// note on should trigger notes on all 16 channels
|
// note on should trigger notes on all 16 channels
|
||||||
|
|
||||||
UnitTestInstrument test;
|
UnitTestInstrument test;
|
||||||
test.enableOmniMode();
|
test.enableLegacyMode();
|
||||||
|
|
||||||
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
||||||
test.noteOn (2, 60, MPEValue::from7BitInt (100));
|
test.noteOn (2, 60, MPEValue::from7BitInt (100));
|
||||||
|
|
@ -1758,7 +1819,7 @@ public:
|
||||||
expectNote (test.getNote (15, 60), 100, 100, 8192, 77, MPENote::keyDown);
|
expectNote (test.getNote (15, 60), 100, 100, 8192, 77, MPENote::keyDown);
|
||||||
expectNote (test.getNote (16, 60), 100, 100, 8192, 64, MPENote::keyDown);
|
expectNote (test.getNote (16, 60), 100, 100, 8192, 64, MPENote::keyDown);
|
||||||
|
|
||||||
// note off should work in omni mode
|
// note off should work in legacy mode
|
||||||
|
|
||||||
test.noteOff (15, 60, MPEValue::from7BitInt (0));
|
test.noteOff (15, 60, MPEValue::from7BitInt (0));
|
||||||
test.noteOff (1, 60, MPEValue::from7BitInt (0));
|
test.noteOff (1, 60, MPEValue::from7BitInt (0));
|
||||||
|
|
@ -1767,10 +1828,31 @@ public:
|
||||||
expectEquals (test.getNumPlayingNotes(), 0);
|
expectEquals (test.getNumPlayingNotes(), 0);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// tracking mode in omni mode
|
// legacy mode w/ custom channel range: note on should trigger notes only within range
|
||||||
|
|
||||||
|
UnitTestInstrument test;
|
||||||
|
test.enableLegacyMode (2, Range<int> (3, 8)); // channels 3-7
|
||||||
|
|
||||||
|
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
||||||
|
test.noteOn (2, 60, MPEValue::from7BitInt (100));
|
||||||
|
test.noteOn (3, 60, MPEValue::from7BitInt (100)); // should trigger
|
||||||
|
test.noteOn (4, 60, MPEValue::from7BitInt (100)); // should trigger
|
||||||
|
test.noteOn (6, 60, MPEValue::from7BitInt (100)); // should trigger
|
||||||
|
test.noteOn (7, 60, MPEValue::from7BitInt (100)); // should trigger
|
||||||
|
test.noteOn (8, 60, MPEValue::from7BitInt (100));
|
||||||
|
test.noteOn (16, 60, MPEValue::from7BitInt (100));
|
||||||
|
|
||||||
|
expectEquals (test.getNumPlayingNotes(), 4);
|
||||||
|
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown);
|
||||||
|
expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown);
|
||||||
|
expectNote (test.getNote (6, 60), 100, 100, 8192, 64, MPENote::keyDown);
|
||||||
|
expectNote (test.getNote (7, 60), 100, 100, 8192, 64, MPENote::keyDown);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// tracking mode in legacy mode
|
||||||
{
|
{
|
||||||
UnitTestInstrument test;
|
UnitTestInstrument test;
|
||||||
test.enableOmniMode();
|
test.enableLegacyMode();
|
||||||
|
|
||||||
test.setPitchbendTrackingMode (MPEInstrument::lastNotePlayedOnChannel);
|
test.setPitchbendTrackingMode (MPEInstrument::lastNotePlayedOnChannel);
|
||||||
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
||||||
|
|
@ -1783,7 +1865,7 @@ public:
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
UnitTestInstrument test;
|
UnitTestInstrument test;
|
||||||
test.enableOmniMode();
|
test.enableLegacyMode();
|
||||||
|
|
||||||
test.setPitchbendTrackingMode (MPEInstrument::lowestNoteOnChannel);
|
test.setPitchbendTrackingMode (MPEInstrument::lowestNoteOnChannel);
|
||||||
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
||||||
|
|
@ -1796,7 +1878,7 @@ public:
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
UnitTestInstrument test;
|
UnitTestInstrument test;
|
||||||
test.enableOmniMode();
|
test.enableLegacyMode();
|
||||||
|
|
||||||
test.setPitchbendTrackingMode (MPEInstrument::highestNoteOnChannel);
|
test.setPitchbendTrackingMode (MPEInstrument::highestNoteOnChannel);
|
||||||
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
||||||
|
|
@ -1809,7 +1891,7 @@ public:
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
UnitTestInstrument test;
|
UnitTestInstrument test;
|
||||||
test.enableOmniMode();
|
test.enableLegacyMode();
|
||||||
|
|
||||||
test.setPitchbendTrackingMode (MPEInstrument::allNotesOnChannel);
|
test.setPitchbendTrackingMode (MPEInstrument::allNotesOnChannel);
|
||||||
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
||||||
|
|
@ -1822,18 +1904,18 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// custom pitchbend range in omni mode.
|
// custom pitchbend range in legacy mode.
|
||||||
UnitTestInstrument test;
|
UnitTestInstrument test;
|
||||||
test.enableOmniMode (11);
|
test.enableLegacyMode (11);
|
||||||
|
|
||||||
test.pitchbend (1, MPEValue::from14BitInt (4096));
|
test.pitchbend (1, MPEValue::from14BitInt (4096));
|
||||||
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
||||||
expectDoubleWithinRelativeError (test.getMostRecentNote (1).totalPitchbendInSemitones, -5.5, 0.01);
|
expectDoubleWithinRelativeError (test.getMostRecentNote (1).totalPitchbendInSemitones, -5.5, 0.01);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// sustain pedal should be per channel in omni mode.
|
// sustain pedal should be per channel in legacy mode.
|
||||||
UnitTestInstrument test;
|
UnitTestInstrument test;
|
||||||
test.enableOmniMode();
|
test.enableLegacyMode();
|
||||||
|
|
||||||
test.sustainPedal (1, true);
|
test.sustainPedal (1, true);
|
||||||
test.noteOn (2, 61, MPEValue::from7BitInt (100));
|
test.noteOn (2, 61, MPEValue::from7BitInt (100));
|
||||||
|
|
@ -1846,11 +1928,17 @@ public:
|
||||||
|
|
||||||
test.sustainPedal (1, false);
|
test.sustainPedal (1, false);
|
||||||
expectEquals (test.getNumPlayingNotes(), 0);
|
expectEquals (test.getNumPlayingNotes(), 0);
|
||||||
|
|
||||||
|
test.noteOn (2, 61, MPEValue::from7BitInt (100));
|
||||||
|
test.sustainPedal (1, true);
|
||||||
|
test.noteOff (2, 61, MPEValue::from7BitInt (100));
|
||||||
|
expectEquals (test.getNumPlayingNotes(), 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// sostenuto pedal should be per channel in omni mode.
|
// sostenuto pedal should be per channel in legacy mode.
|
||||||
UnitTestInstrument test;
|
UnitTestInstrument test;
|
||||||
test.enableOmniMode();
|
test.enableLegacyMode();
|
||||||
|
|
||||||
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
||||||
test.sostenutoPedal (1, true);
|
test.sostenutoPedal (1, true);
|
||||||
|
|
@ -1863,6 +1951,11 @@ public:
|
||||||
|
|
||||||
test.sostenutoPedal (1, false);
|
test.sostenutoPedal (1, false);
|
||||||
expectEquals (test.getNumPlayingNotes(), 0);
|
expectEquals (test.getNumPlayingNotes(), 0);
|
||||||
|
|
||||||
|
test.noteOn (2, 61, MPEValue::from7BitInt (100));
|
||||||
|
test.sostenutoPedal (1, true);
|
||||||
|
test.noteOff (2, 61, MPEValue::from7BitInt (100));
|
||||||
|
expectEquals (test.getNumPlayingNotes(), 0);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// all notes released when switching layout
|
// all notes released when switching layout
|
||||||
|
|
@ -1871,7 +1964,7 @@ public:
|
||||||
test.noteOn (3, 60, MPEValue::from7BitInt (100));
|
test.noteOn (3, 60, MPEValue::from7BitInt (100));
|
||||||
expectEquals (test.getNumPlayingNotes(), 1);
|
expectEquals (test.getNumPlayingNotes(), 1);
|
||||||
|
|
||||||
test.enableOmniMode();
|
test.enableLegacyMode();
|
||||||
expectEquals (test.getNumPlayingNotes(), 0);
|
expectEquals (test.getNumPlayingNotes(), 0);
|
||||||
test.noteOn (3, 60, MPEValue::from7BitInt (100));
|
test.noteOn (3, 60, MPEValue::from7BitInt (100));
|
||||||
expectEquals (test.getNumPlayingNotes(), 1);
|
expectEquals (test.getNumPlayingNotes(), 1);
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ public:
|
||||||
This will construct an MPE instrument with initially no MPE zones.
|
This will construct an MPE instrument with initially no MPE zones.
|
||||||
|
|
||||||
In order to process incoming MIDI, call setZoneLayout, define the layout
|
In order to process incoming MIDI, call setZoneLayout, define the layout
|
||||||
via MIDI RPN messages, or set the instrument to omni mode.
|
via MIDI RPN messages, or set the instrument to legacy mode.
|
||||||
*/
|
*/
|
||||||
MPEInstrument() noexcept;
|
MPEInstrument() noexcept;
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ public:
|
||||||
/** Returns the current zone layout of the instrument.
|
/** Returns the current zone layout of the instrument.
|
||||||
This happens by value, to enforce thread-safety and class invariants.
|
This happens by value, to enforce thread-safety and class invariants.
|
||||||
|
|
||||||
Note: If the instrument is in Omni mode, the return value of this
|
Note: If the instrument is in legacy mode, the return value of this
|
||||||
method is unspecified.
|
method is unspecified.
|
||||||
*/
|
*/
|
||||||
MPEZoneLayout getZoneLayout() const noexcept;
|
MPEZoneLayout getZoneLayout() const noexcept;
|
||||||
|
|
@ -81,38 +81,23 @@ public:
|
||||||
As a side effect, this will discard all currently playing notes,
|
As a side effect, this will discard all currently playing notes,
|
||||||
and call noteReleased for all of them.
|
and call noteReleased for all of them.
|
||||||
|
|
||||||
This will also disable Omni Mode in case it was enabled previously.
|
This will also disable legacy mode in case it was enabled previously.
|
||||||
*/
|
*/
|
||||||
void setZoneLayout (MPEZoneLayout newLayout);
|
void setZoneLayout (MPEZoneLayout newLayout);
|
||||||
|
|
||||||
/** Returns true if the given MIDI channel (1-16) is a note channel in any
|
/** Returns true if the given MIDI channel (1-16) is a note channel in any
|
||||||
of the MPEInstrument's MPE zones; false otherwise.
|
of the MPEInstrument's MPE zones; false otherwise.
|
||||||
|
When in legacy mode, this will return true if the given channel is
|
||||||
|
contained in the current legacy mode channel range; false otherwise.
|
||||||
*/
|
*/
|
||||||
bool isNoteChannel (int midiChannel) const noexcept;
|
bool isNoteChannel (int midiChannel) const noexcept;
|
||||||
|
|
||||||
/** Returns true if the given MIDI channel (1-16) is a master channel in any
|
/** Returns true if the given MIDI channel (1-16) is a master channel in any
|
||||||
of the MPEInstrument's MPE zones; false otherwise.
|
of the MPEInstrument's MPE zones; false otherwise.
|
||||||
|
When in legacy mode, this will always return false.
|
||||||
*/
|
*/
|
||||||
bool isMasterChannel (int midiChannel) const noexcept;
|
bool isMasterChannel (int midiChannel) const noexcept;
|
||||||
|
|
||||||
/** Sets the instrument to Omni Mode.
|
|
||||||
As a side effect, this will discard all currently playing notes,
|
|
||||||
and call noteReleased for all of them.
|
|
||||||
|
|
||||||
This special zone layout mode is for backwards compatibility with
|
|
||||||
non-MPE MIDI devices. In this mode, the instrument will ignore the
|
|
||||||
current zone layout. It will instead treat all 16 MIDI channels as note
|
|
||||||
channels, with no master channel.
|
|
||||||
|
|
||||||
@param pitchbendRange The pitchbend range in semitones that should be
|
|
||||||
used while the instrument is in Omni mode. Must
|
|
||||||
be between 0 and 96, otherwise behaviour is undefined.
|
|
||||||
*/
|
|
||||||
void enableOmniMode (int pitchbendRange = 2);
|
|
||||||
|
|
||||||
/** Returns true if the instrument is in Omni mode, false otherwise. */
|
|
||||||
bool isOmniModeEnabled() const noexcept;
|
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
/** The MPE note tracking mode. In case there is more than one note playing
|
/** The MPE note tracking mode. In case there is more than one note playing
|
||||||
simultaneously on the same MIDI channel, this determines which of these
|
simultaneously on the same MIDI channel, this determines which of these
|
||||||
|
|
@ -300,6 +285,44 @@ public:
|
||||||
/** Removes a listener. */
|
/** Removes a listener. */
|
||||||
void removeListener (Listener* const listenerToRemove) noexcept;
|
void removeListener (Listener* const listenerToRemove) noexcept;
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
/** Puts the instrument into legacy mode.
|
||||||
|
As a side effect, this will discard all currently playing notes,
|
||||||
|
and call noteReleased for all of them.
|
||||||
|
|
||||||
|
This special zone layout mode is for backwards compatibility with
|
||||||
|
non-MPE MIDI devices. In this mode, the instrument will ignore the
|
||||||
|
current MPE zone layout. It will instead take a range of MIDI channels
|
||||||
|
(default: all channels 1-16) and treat them as note channels, with no
|
||||||
|
master channel. MIDI channels outside of this range will be ignored.
|
||||||
|
|
||||||
|
@param pitchbendRange The note pitchbend range in semitones to use when in legacy mode.
|
||||||
|
Must be between 0 and 96, otherwise behaviour is undefined.
|
||||||
|
The default pitchbend range in legacy mode is +/- 2 semitones.
|
||||||
|
|
||||||
|
@param channelRange The range of MIDI channels to use for notes when in legacy mode.
|
||||||
|
The default is to use all MIDI channels (1-16).
|
||||||
|
|
||||||
|
To get out of legacy mode, set a new MPE zone layout using setZoneLayout.
|
||||||
|
*/
|
||||||
|
void enableLegacyMode (int pitchbendRange = 2,
|
||||||
|
Range<int> channelRange = Range<int> (1, 17));
|
||||||
|
|
||||||
|
/** Returns true if the instrument is in legacy mode, false otherwise. */
|
||||||
|
bool isLegacyModeEnabled() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
|
||||||
|
Range<int> getLegacyModeChannelRange() const noexcept;
|
||||||
|
|
||||||
|
/** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
|
||||||
|
void setLegacyModeChannelRange (Range<int> channelRange);
|
||||||
|
|
||||||
|
/** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
|
||||||
|
int getLegacyModePitchbendRange() const noexcept;
|
||||||
|
|
||||||
|
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
|
||||||
|
void setLegacyModePitchbendRange (int pitchbendRange);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
/** This method defines what initial pitchbend value should be used for newly
|
/** This method defines what initial pitchbend value should be used for newly
|
||||||
|
|
@ -341,9 +364,10 @@ private:
|
||||||
uint8 lastTimbreLowerBitReceivedOnChannel[16];
|
uint8 lastTimbreLowerBitReceivedOnChannel[16];
|
||||||
bool isNoteChannelSustained[16];
|
bool isNoteChannelSustained[16];
|
||||||
|
|
||||||
struct OmniMode
|
struct LegacyMode
|
||||||
{
|
{
|
||||||
bool isEnabled;
|
bool isEnabled;
|
||||||
|
Range<int> channelRange;
|
||||||
int pitchbendRange;
|
int pitchbendRange;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -356,7 +380,7 @@ private:
|
||||||
MPEValue& getValue (MPENote& note) noexcept { return note.*(value); }
|
MPEValue& getValue (MPENote& note) noexcept { return note.*(value); }
|
||||||
};
|
};
|
||||||
|
|
||||||
OmniMode omniMode;
|
LegacyMode legacyMode;
|
||||||
MPEDimension pitchbendDimension, pressureDimension, timbreDimension;
|
MPEDimension pitchbendDimension, pressureDimension, timbreDimension;
|
||||||
|
|
||||||
void updateDimension (int midiChannel, MPEDimension&, MPEValue);
|
void updateDimension (int midiChannel, MPEDimension&, MPEValue);
|
||||||
|
|
|
||||||
|
|
@ -50,14 +50,35 @@ void MPESynthesiserBase::setZoneLayout (MPEZoneLayout newLayout)
|
||||||
instrument->setZoneLayout (newLayout);
|
instrument->setZoneLayout (newLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MPESynthesiserBase::enableOmniMode (int pitchbendRange)
|
//==============================================================================
|
||||||
|
void MPESynthesiserBase::enableLegacyMode (int pitchbendRange, Range<int> channelRange)
|
||||||
{
|
{
|
||||||
instrument->enableOmniMode (pitchbendRange);
|
instrument->enableLegacyMode (pitchbendRange, channelRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MPESynthesiserBase::isOmniModeEnabled() const noexcept
|
bool MPESynthesiserBase::isLegacyModeEnabled() const noexcept
|
||||||
{
|
{
|
||||||
return instrument->isOmniModeEnabled();
|
return instrument->isLegacyModeEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
Range<int> MPESynthesiserBase::getLegacyModeChannelRange() const noexcept
|
||||||
|
{
|
||||||
|
return instrument->getLegacyModeChannelRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiserBase::setLegacyModeChannelRange (Range<int> channelRange)
|
||||||
|
{
|
||||||
|
instrument->setLegacyModeChannelRange (channelRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MPESynthesiserBase::getLegacyModePitchbendRange() const noexcept
|
||||||
|
{
|
||||||
|
return instrument->getLegacyModePitchbendRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPESynthesiserBase::setLegacyModePitchbendRange (int pitchbendRange)
|
||||||
|
{
|
||||||
|
instrument->setLegacyModePitchbendRange (pitchbendRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
|
|
@ -69,16 +69,10 @@ public:
|
||||||
|
|
||||||
/** Re-sets the synthesiser's internal MPE zone layout to the one passed in.
|
/** Re-sets the synthesiser's internal MPE zone layout to the one passed in.
|
||||||
As a side effect, this will discard all currently playing notes,
|
As a side effect, this will discard all currently playing notes,
|
||||||
call noteReleased for all of them, and disable Omni mode (if previously enabled).
|
call noteReleased for all of them, and disable legacy mode (if previously enabled).
|
||||||
*/
|
*/
|
||||||
void setZoneLayout (MPEZoneLayout newLayout);
|
void setZoneLayout (MPEZoneLayout newLayout);
|
||||||
|
|
||||||
/** Sets the synthesiser to Omni mode. */
|
|
||||||
void enableOmniMode (int pitchbendRange = 2);
|
|
||||||
|
|
||||||
/** Returns true if the synthesiser is currently in Omni mode. */
|
|
||||||
bool isOmniModeEnabled() const noexcept;
|
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
/** Tells the synthesiser what the sample rate is for the audio it's being
|
/** Tells the synthesiser what the sample rate is for the audio it's being
|
||||||
used to render.
|
used to render.
|
||||||
|
|
@ -136,6 +130,35 @@ public:
|
||||||
*/
|
*/
|
||||||
void setMinimumRenderingSubdivisionSize (int numSamples) noexcept;
|
void setMinimumRenderingSubdivisionSize (int numSamples) noexcept;
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
/** Puts the synthesiser into legacy mode.
|
||||||
|
|
||||||
|
@param pitchbendRange The note pitchbend range in semitones to use when in legacy mode.
|
||||||
|
Must be between 0 and 96, otherwise behaviour is undefined.
|
||||||
|
The default pitchbend range in legacy mode is +/- 2 semitones.
|
||||||
|
@param channelRange The range of MIDI channels to use for notes when in legacy mode.
|
||||||
|
The default is to use all MIDI channels (1-16).
|
||||||
|
|
||||||
|
To get out of legacy mode, set a new MPE zone layout using setZoneLayout.
|
||||||
|
*/
|
||||||
|
void enableLegacyMode (int pitchbendRange = 2,
|
||||||
|
Range<int> channelRange = Range<int> (1, 17));
|
||||||
|
|
||||||
|
/** Returns true if the instrument is in legacy mode, false otherwise. */
|
||||||
|
bool isLegacyModeEnabled() const noexcept;
|
||||||
|
|
||||||
|
/** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
|
||||||
|
Range<int> getLegacyModeChannelRange() const noexcept;
|
||||||
|
|
||||||
|
/** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
|
||||||
|
void setLegacyModeChannelRange (Range<int> channelRange);
|
||||||
|
|
||||||
|
/** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
|
||||||
|
int getLegacyModePitchbendRange() const noexcept;
|
||||||
|
|
||||||
|
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
|
||||||
|
void setLegacyModePitchbendRange (int pitchbendRange);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
/** Implement this method to render your audio inside.
|
/** Implement this method to render your audio inside.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue