mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Allow notes and aftertouch messages on the master channel of MPEInstrument (see sections 3.2 and 2.5 of the MPE Spec)
This commit is contained in:
parent
1df891ccd7
commit
a2a04a726a
3 changed files with 102 additions and 21 deletions
|
|
@ -151,6 +151,7 @@ void MPEInstrument::processNextMidiEvent (const MidiMessage& message)
|
|||
else if (message.isPitchWheel()) processMidiPitchWheelMessage (message);
|
||||
else if (message.isChannelPressure()) processMidiChannelPressureMessage (message);
|
||||
else if (message.isController()) processMidiControllerMessage (message);
|
||||
else if (message.isAftertouch()) processMidiAfterTouchMessage (message);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -241,7 +242,7 @@ void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& me
|
|||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
||||
if (zone.isUsingChannelAsMemberChannel (note.midiChannel))
|
||||
if (zone.isUsing (note.midiChannel))
|
||||
{
|
||||
note.keyState = MPENote::off;
|
||||
note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number
|
||||
|
|
@ -252,6 +253,25 @@ void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& me
|
|||
}
|
||||
}
|
||||
|
||||
void MPEInstrument::processMidiAfterTouchMessage (const MidiMessage& message)
|
||||
{
|
||||
if (! isMasterChannel (message.getChannel()))
|
||||
return;
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
for (auto i = notes.size(); --i >= 0;)
|
||||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
||||
if (note.midiChannel == message.getChannel() && note.initialNote == message.getNoteNumber())
|
||||
{
|
||||
pressureDimension.getValue (note) = MPEValue::from7BitInt (message.getAfterTouchValue());
|
||||
listeners.call ([&] (Listener& l) { l.notePressureChanged (note); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MPEInstrument::handlePressureMSB (int midiChannel, int value) noexcept
|
||||
{
|
||||
|
|
@ -284,7 +304,7 @@ void MPEInstrument::noteOn (int midiChannel,
|
|||
int midiNoteNumber,
|
||||
MPEValue midiNoteOnVelocity)
|
||||
{
|
||||
if (! isMemberChannel (midiChannel))
|
||||
if (! isUsingChannel (midiChannel))
|
||||
return;
|
||||
|
||||
MPENote newNote (midiChannel,
|
||||
|
|
@ -316,7 +336,7 @@ void MPEInstrument::noteOff (int midiChannel,
|
|||
int midiNoteNumber,
|
||||
MPEValue midiNoteOffVelocity)
|
||||
{
|
||||
if (notes.isEmpty() || ! isMemberChannel (midiChannel))
|
||||
if (notes.isEmpty() || ! isUsingChannel (midiChannel))
|
||||
return;
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
|
@ -326,11 +346,13 @@ void MPEInstrument::noteOff (int midiChannel,
|
|||
note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off;
|
||||
note->noteOffVelocity = midiNoteOffVelocity;
|
||||
|
||||
// last dimension values received for this note should not be re-used for
|
||||
// any new notes, so reset them:
|
||||
pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue();
|
||||
pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue();
|
||||
timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue();
|
||||
// If no more notes are playing on this channel, reset the dimension values
|
||||
if (getLastNotePlayedPtr (midiChannel) == nullptr)
|
||||
{
|
||||
pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue();
|
||||
pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue();
|
||||
timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue();
|
||||
}
|
||||
|
||||
if (note->keyState == MPENote::off)
|
||||
{
|
||||
|
|
@ -416,7 +438,7 @@ void MPEInstrument::updateDimensionMaster (bool isLowerZone, MPEDimension& dimen
|
|||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
||||
if (! zone.isUsingChannelAsMemberChannel (note.midiChannel))
|
||||
if (! zone.isUsing (note.midiChannel))
|
||||
continue;
|
||||
|
||||
if (&dimension == &pitchbendDimension)
|
||||
|
|
@ -467,9 +489,9 @@ void MPEInstrument::updateNoteTotalPitchbend (MPENote& note)
|
|||
{
|
||||
auto zone = zoneLayout.getLowerZone();
|
||||
|
||||
if (! zone.isUsingChannelAsMemberChannel (note.midiChannel))
|
||||
if (! zone.isUsing (note.midiChannel))
|
||||
{
|
||||
if (zoneLayout.getUpperZone().isUsingChannelAsMemberChannel (note.midiChannel))
|
||||
if (zoneLayout.getUpperZone().isUsing (note.midiChannel))
|
||||
{
|
||||
zone = zoneLayout.getUpperZone();
|
||||
}
|
||||
|
|
@ -481,7 +503,10 @@ void MPEInstrument::updateNoteTotalPitchbend (MPENote& note)
|
|||
}
|
||||
}
|
||||
|
||||
auto notePitchbendInSemitones = note.pitchbend.asSignedFloat() * zone.perNotePitchbendRange;
|
||||
auto notePitchbendInSemitones = 0.0f;
|
||||
|
||||
if (zone.isUsingChannelAsMemberChannel (note.midiChannel))
|
||||
notePitchbendInSemitones = note.pitchbend.asSignedFloat() * zone.perNotePitchbendRange;
|
||||
|
||||
auto masterPitchbendInSemitones = pitchbendDimension.lastValueReceivedOnChannel[zone.getMasterChannel() - 1]
|
||||
.asSignedFloat()
|
||||
|
|
@ -520,7 +545,7 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool
|
|||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
||||
if (legacyMode.isEnabled ? (note.midiChannel == midiChannel) : zone.isUsingChannelAsMemberChannel (note.midiChannel))
|
||||
if (legacyMode.isEnabled ? (note.midiChannel == midiChannel) : zone.isUsing (note.midiChannel))
|
||||
{
|
||||
if (note.keyState == MPENote::keyDown && isDown)
|
||||
note.keyState = MPENote::keyDownAndSustained;
|
||||
|
|
@ -560,7 +585,7 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
bool MPEInstrument::isMemberChannel (int midiChannel) noexcept
|
||||
bool MPEInstrument::isMemberChannel (int midiChannel) const noexcept
|
||||
{
|
||||
if (legacyMode.isEnabled)
|
||||
return legacyMode.channelRange.contains (midiChannel);
|
||||
|
|
@ -576,6 +601,16 @@ bool MPEInstrument::isMasterChannel (int midiChannel) const noexcept
|
|||
|
||||
return (midiChannel == 1 || midiChannel == 16);
|
||||
}
|
||||
|
||||
bool MPEInstrument::isUsingChannel (int midiChannel) const noexcept
|
||||
{
|
||||
if (legacyMode.isEnabled)
|
||||
return legacyMode.channelRange.contains (midiChannel);
|
||||
|
||||
return zoneLayout.getLowerZone().isUsing (midiChannel)
|
||||
|| zoneLayout.getUpperZone().isUsing (midiChannel);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int MPEInstrument::getNumPlayingNotes() const noexcept
|
||||
{
|
||||
|
|
@ -798,12 +833,7 @@ public:
|
|||
UnitTestInstrument test;
|
||||
test.setZoneLayout (testLayout);
|
||||
|
||||
// note-on on master channel - ignore
|
||||
test.noteOn (1, 60, MPEValue::from7BitInt (100));
|
||||
expectEquals (test.getNumPlayingNotes(), 0);
|
||||
expectEquals (test.noteAddedCallCounter, 0);
|
||||
|
||||
// note-on on any other channel - ignore
|
||||
// note-on on unused channel - ignore
|
||||
test.noteOn (7, 60, MPEValue::from7BitInt (100));
|
||||
expectEquals (test.getNumPlayingNotes(), 0);
|
||||
expectEquals (test.noteAddedCallCounter, 0);
|
||||
|
|
@ -819,7 +849,21 @@ public:
|
|||
expectEquals (test.getNumPlayingNotes(), 0);
|
||||
expectEquals (test.noteReleasedCallCounter, 1);
|
||||
expectHasFinishedNote (test, 3, 60, 33);
|
||||
|
||||
|
||||
// note-on on master channel - create new note
|
||||
test.noteOn (1, 62, MPEValue::from7BitInt (100));
|
||||
expectEquals (test.getNumPlayingNotes(), 1);
|
||||
expectEquals (test.noteAddedCallCounter, 2);
|
||||
expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown);
|
||||
|
||||
// note-off
|
||||
test.noteOff (1, 62, MPEValue::from7BitInt (33));
|
||||
expectEquals (test.getNumPlayingNotes(), 0);
|
||||
expectEquals (test.noteReleasedCallCounter, 2);
|
||||
expectHasFinishedNote (test, 1, 62, 33);
|
||||
}
|
||||
|
||||
{
|
||||
UnitTestInstrument test;
|
||||
test.setZoneLayout (testLayout);
|
||||
|
|
@ -837,6 +881,7 @@ public:
|
|||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
|
||||
expectEquals (test.noteReleasedCallCounter, 0);
|
||||
}
|
||||
|
||||
{
|
||||
// can have multiple notes on the same channel
|
||||
UnitTestInstrument test;
|
||||
|
|
@ -1187,6 +1232,22 @@ public:
|
|||
expectNote (test.getNote (3, 60), 100, 78, 8192, 64, MPENote::keyDown);
|
||||
expectNote (test.getNote (3, 61), 100, 77, 8192, 64, MPENote::keyDown);
|
||||
}
|
||||
|
||||
{
|
||||
UnitTestInstrument test;
|
||||
test.setZoneLayout (testLayout);
|
||||
|
||||
// master channel will use poly-aftertouch for pressure
|
||||
test.noteOn (16, 60, MPEValue::from7BitInt (100));
|
||||
expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown);
|
||||
test.aftertouch (16, 60, MPEValue::from7BitInt (27));
|
||||
expectNote (test.getNote (16, 60), 100, 27, 8192, 64, MPENote::keyDown);
|
||||
|
||||
// member channels will not respond to poly-aftertouch
|
||||
test.noteOn (3, 60, MPEValue::from7BitInt (100));
|
||||
test.aftertouch (3, 60, MPEValue::from7BitInt (50));
|
||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("pitchbend");
|
||||
|
|
@ -2142,6 +2203,12 @@ private:
|
|||
lastSostenutoPedalValueReceived = value;
|
||||
}
|
||||
|
||||
void aftertouch (int midiChannel, int midiNoteNumber, MPEValue value)
|
||||
{
|
||||
const auto message = juce::MidiMessage::aftertouchChange (midiChannel, midiNoteNumber, value.as7BitInt());
|
||||
processNextMidiEvent (message);
|
||||
}
|
||||
|
||||
int noteOnCallCounter, noteOffCallCounter, pitchbendCallCounter,
|
||||
pressureCallCounter, timbreCallCounter, sustainPedalCallCounter,
|
||||
sostenutoPedalCallCounter, noteAddedCallCounter,
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public:
|
|||
When in legacy mode, this will return true if the given channel is
|
||||
contained in the current legacy mode channel range; false otherwise.
|
||||
*/
|
||||
bool isMemberChannel (int midiChannel) noexcept;
|
||||
bool isMemberChannel (int midiChannel) const noexcept;
|
||||
|
||||
/** Returns true if the given MIDI channel (1-16) is a master channel (channel
|
||||
1 or 16).
|
||||
|
|
@ -99,6 +99,14 @@ public:
|
|||
*/
|
||||
bool isMasterChannel (int midiChannel) const noexcept;
|
||||
|
||||
/** Returns true if the given MIDI channel (1-16) is used by any 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 isUsingChannel (int midiChannel) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** 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
|
||||
|
|
@ -373,6 +381,7 @@ private:
|
|||
void processMidiChannelPressureMessage (const MidiMessage&);
|
||||
void processMidiControllerMessage (const MidiMessage&);
|
||||
void processMidiResetAllControllersMessage (const MidiMessage&);
|
||||
void processMidiAfterTouchMessage (const MidiMessage&);
|
||||
void handlePressureMSB (int midiChannel, int value) noexcept;
|
||||
void handlePressureLSB (int midiChannel, int value) noexcept;
|
||||
void handleTimbreMSB (int midiChannel, int value) noexcept;
|
||||
|
|
|
|||
|
|
@ -98,6 +98,11 @@ public:
|
|||
: (channel < 16 && channel >= 16 - numMemberChannels);
|
||||
}
|
||||
|
||||
bool isUsing (int channel) const noexcept
|
||||
{
|
||||
return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel();
|
||||
}
|
||||
|
||||
bool operator== (const Zone& other) const noexcept { return lowerZone == other.lowerZone
|
||||
&& numMemberChannels == other.numMemberChannels
|
||||
&& perNotePitchbendRange == other.perNotePitchbendRange
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue