Browse Source

Allow notes and aftertouch messages on the master channel of MPEInstrument (see sections 3.2 and 2.5 of the MPE Spec)

tags/2021-05-28
dimitri ed 6 years ago
parent
commit
a2a04a726a
3 changed files with 102 additions and 21 deletions
  1. +87
    -20
      modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp
  2. +10
    -1
      modules/juce_audio_basics/mpe/juce_MPEInstrument.h
  3. +5
    -0
      modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h

+ 87
- 20
modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp View File

@@ -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,


+ 10
- 1
modules/juce_audio_basics/mpe/juce_MPEInstrument.h View File

@@ -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;


+ 5
- 0
modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h View File

@@ -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…
Cancel
Save