mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Add KeyboardComponentBase class for custom MIDI keyboard components and MPEKeyboardComponent class
This commit is contained in:
parent
461192b355
commit
e0e8e85d6b
28 changed files with 2219 additions and 1502 deletions
|
|
@ -43,16 +43,20 @@ MPEInstrument::MPEInstrument() noexcept
|
|||
mpeInstrumentFill (isMemberChannelSustained, false);
|
||||
|
||||
pitchbendDimension.value = &MPENote::pitchbend;
|
||||
pressureDimension.value = &MPENote::pressure;
|
||||
timbreDimension.value = &MPENote::timbre;
|
||||
pressureDimension.value = &MPENote::pressure;
|
||||
timbreDimension.value = &MPENote::timbre;
|
||||
|
||||
resetLastReceivedValues();
|
||||
|
||||
legacyMode.isEnabled = false;
|
||||
legacyMode.pitchbendRange = 2;
|
||||
legacyMode.channelRange = allChannels;
|
||||
}
|
||||
|
||||
MPEInstrument::MPEInstrument (MPEZoneLayout layout)
|
||||
: MPEInstrument()
|
||||
{
|
||||
setZoneLayout (layout);
|
||||
}
|
||||
|
||||
MPEInstrument::~MPEInstrument() = default;
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -84,21 +88,30 @@ void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout)
|
|||
|
||||
const ScopedLock sl (lock);
|
||||
legacyMode.isEnabled = false;
|
||||
zoneLayout = newLayout;
|
||||
|
||||
resetLastReceivedValues();
|
||||
if (zoneLayout != newLayout)
|
||||
{
|
||||
zoneLayout = newLayout;
|
||||
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); });
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MPEInstrument::enableLegacyMode (int pitchbendRange, Range<int> channelRange)
|
||||
{
|
||||
if (legacyMode.isEnabled)
|
||||
return;
|
||||
|
||||
releaseAllNotes();
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
legacyMode.isEnabled = true;
|
||||
legacyMode.pitchbendRange = pitchbendRange;
|
||||
legacyMode.channelRange = channelRange;
|
||||
|
||||
zoneLayout.clearAllZones();
|
||||
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); });
|
||||
}
|
||||
|
||||
bool MPEInstrument::isLegacyModeEnabled() const noexcept
|
||||
|
|
@ -117,7 +130,12 @@ void MPEInstrument::setLegacyModeChannelRange (Range<int> channelRange)
|
|||
|
||||
releaseAllNotes();
|
||||
const ScopedLock sl (lock);
|
||||
legacyMode.channelRange = channelRange;
|
||||
|
||||
if (legacyMode.channelRange != channelRange)
|
||||
{
|
||||
legacyMode.channelRange = channelRange;
|
||||
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); });
|
||||
}
|
||||
}
|
||||
|
||||
int MPEInstrument::getLegacyModePitchbendRange() const noexcept
|
||||
|
|
@ -131,7 +149,12 @@ void MPEInstrument::setLegacyModePitchbendRange (int pitchbendRange)
|
|||
|
||||
releaseAllNotes();
|
||||
const ScopedLock sl (lock);
|
||||
legacyMode.pitchbendRange = pitchbendRange;
|
||||
|
||||
if (legacyMode.pitchbendRange != pitchbendRange)
|
||||
{
|
||||
legacyMode.pitchbendRange = pitchbendRange;
|
||||
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); });
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -242,7 +265,7 @@ void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& me
|
|||
|
||||
if (legacyMode.isEnabled && legacyMode.channelRange.contains (message.getChannel()))
|
||||
{
|
||||
for (auto i = notes.size(); --i >= 0;)
|
||||
for (int i = notes.size(); --i >= 0;)
|
||||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
||||
|
|
@ -260,7 +283,7 @@ void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& me
|
|||
auto zone = (message.getChannel() == 1 ? zoneLayout.getLowerZone()
|
||||
: zoneLayout.getUpperZone());
|
||||
|
||||
for (auto i = notes.size(); --i >= 0;)
|
||||
for (int i = notes.size(); --i >= 0;)
|
||||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
||||
|
|
@ -348,11 +371,11 @@ void MPEInstrument::noteOff (int midiChannel,
|
|||
int midiNoteNumber,
|
||||
MPEValue midiNoteOffVelocity)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (notes.isEmpty() || ! isUsingChannel (midiChannel))
|
||||
return;
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (auto* note = getNotePtr (midiChannel, midiNoteNumber))
|
||||
{
|
||||
note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off;
|
||||
|
|
@ -401,7 +424,7 @@ void MPEInstrument::polyAftertouch (int midiChannel, int midiNoteNumber, MPEValu
|
|||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
for (auto i = notes.size(); --i >= 0;)
|
||||
for (int i = notes.size(); --i >= 0;)
|
||||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
||||
|
|
@ -435,7 +458,7 @@ void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, M
|
|||
{
|
||||
if (dimension.trackingMode == allNotesOnChannel)
|
||||
{
|
||||
for (auto i = notes.size(); --i >= 0;)
|
||||
for (int i = notes.size(); --i >= 0;)
|
||||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
||||
|
|
@ -464,7 +487,7 @@ void MPEInstrument::updateDimensionMaster (bool isLowerZone, MPEDimension& dimen
|
|||
if (! zone.isActive())
|
||||
return;
|
||||
|
||||
for (auto i = notes.size(); --i >= 0;)
|
||||
for (int i = notes.size(); --i >= 0;)
|
||||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
||||
|
|
@ -573,7 +596,7 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool
|
|||
auto zone = (midiChannel == 1 ? zoneLayout.getLowerZone()
|
||||
: zoneLayout.getUpperZone());
|
||||
|
||||
for (auto i = notes.size(); --i >= 0;)
|
||||
for (int i = notes.size(); --i >= 0;)
|
||||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
||||
|
|
@ -605,11 +628,15 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool
|
|||
if (! legacyMode.isEnabled)
|
||||
{
|
||||
if (zone.isLowerZone())
|
||||
for (auto i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i)
|
||||
{
|
||||
for (int i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i)
|
||||
isMemberChannelSustained[i - 1] = isDown;
|
||||
}
|
||||
else
|
||||
for (auto i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i)
|
||||
{
|
||||
for (int i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i)
|
||||
isMemberChannelSustained[i - 1] = isDown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -664,6 +691,17 @@ MPENote MPEInstrument::getNote (int index) const noexcept
|
|||
return notes[index];
|
||||
}
|
||||
|
||||
MPENote MPEInstrument::getNoteWithID (uint16 noteID) const noexcept
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
for (auto& note : notes)
|
||||
if (note.noteID == noteID)
|
||||
return note;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MPENote MPEInstrument::getMostRecentNote (int midiChannel) const noexcept
|
||||
{
|
||||
|
|
@ -727,6 +765,8 @@ MPENote* MPEInstrument::getNotePtr (int midiChannel, TrackingMode mode) noexcept
|
|||
//==============================================================================
|
||||
const MPENote* MPEInstrument::getLastNotePlayedPtr (int midiChannel) const noexcept
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
for (auto i = notes.size(); --i >= 0;)
|
||||
{
|
||||
auto& note = notes.getReference (i);
|
||||
|
|
|
|||
|
|
@ -38,10 +38,8 @@ namespace juce
|
|||
MPE. If you pass it a message, it will know what notes on what
|
||||
channels (if any) should be affected by that message.
|
||||
|
||||
The class has a Listener class with the three callbacks MPENoteAdded,
|
||||
MPENoteChanged, and MPENoteFinished. Implement such a
|
||||
Listener class to react to note changes and trigger some functionality for
|
||||
your application that depends on the MPE note state.
|
||||
The class has a Listener class that can be used to react to note and
|
||||
state changes and trigger some functionality for your application.
|
||||
For example, you can use this class to write an MPE visualiser.
|
||||
|
||||
If you want to write a real-time audio synth with MPE functionality,
|
||||
|
|
@ -59,11 +57,14 @@ public:
|
|||
|
||||
This will construct an MPE instrument with inactive lower and upper zones.
|
||||
|
||||
In order to process incoming MIDI, call setZoneLayout, define the layout
|
||||
via MIDI RPN messages, or set the instrument to legacy mode.
|
||||
In order to process incoming MIDI messages call setZoneLayout, use the MPEZoneLayout
|
||||
constructor, define the layout via MIDI RPN messages, or set the instrument to legacy mode.
|
||||
*/
|
||||
MPEInstrument() noexcept;
|
||||
|
||||
/** Constructs an MPE instrument with the specified zone layout. */
|
||||
MPEInstrument (MPEZoneLayout layout);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~MPEInstrument();
|
||||
|
||||
|
|
@ -229,6 +230,9 @@ public:
|
|||
*/
|
||||
MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept;
|
||||
|
||||
/** Returns the note with a given ID. */
|
||||
MPENote getNoteWithID (uint16 noteID) const noexcept;
|
||||
|
||||
/** Returns the most recent note that is playing on the given midiChannel
|
||||
(this will be the note which has received the most recent note-on without
|
||||
a corresponding note-off), if there is such a note. Otherwise, this returns an
|
||||
|
|
@ -244,8 +248,8 @@ public:
|
|||
MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Derive from this class to be informed about any changes in the expressive
|
||||
MIDI notes played by this instrument.
|
||||
/** Derive from this class to be informed about any changes in the MPE notes played
|
||||
by this instrument, and any changes to its zone layout.
|
||||
|
||||
Note: This listener type receives its callbacks immediately, and not
|
||||
via the message thread (so you might be for example in the MIDI thread).
|
||||
|
|
@ -297,6 +301,11 @@ public:
|
|||
and should therefore stop playing.
|
||||
*/
|
||||
virtual void noteReleased (MPENote finishedNote) { ignoreUnused (finishedNote); }
|
||||
|
||||
/** Implement this callback to be informed whenever the MPE zone layout
|
||||
or legacy mode settings of this instrument have been changed.
|
||||
*/
|
||||
virtual void zoneLayoutChanged() {}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -307,7 +316,9 @@ public:
|
|||
void removeListener (Listener* listenerToRemove);
|
||||
|
||||
//==============================================================================
|
||||
/** Puts the instrument into legacy mode.
|
||||
/** Puts the instrument into legacy mode. If legacy mode is already enabled this method
|
||||
does nothing.
|
||||
|
||||
As a side effect, this will discard all currently playing notes,
|
||||
and call noteReleased for all of them.
|
||||
|
||||
|
|
@ -360,9 +371,9 @@ private:
|
|||
|
||||
struct LegacyMode
|
||||
{
|
||||
bool isEnabled;
|
||||
bool isEnabled = false;
|
||||
Range<int> channelRange;
|
||||
int pitchbendRange;
|
||||
int pitchbendRange = 2;
|
||||
};
|
||||
|
||||
struct MPEDimension
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ struct JUCE_API MPENote
|
|||
*/
|
||||
MPEValue noteOnVelocity { MPEValue::minValue() };
|
||||
|
||||
/** Current per-note pitchbend of the note (in units of MIDI pitchwheel
|
||||
/** Current per-note pitchbend of the note (in units of MIDI pitchwheel
|
||||
position). This dimension can be modulated while the note sounds.
|
||||
|
||||
Note: This value is not aware of the currently used pitchbend range,
|
||||
|
|
|
|||
|
|
@ -25,12 +25,10 @@ namespace juce
|
|||
|
||||
MPESynthesiser::MPESynthesiser()
|
||||
{
|
||||
MPEZoneLayout zoneLayout;
|
||||
zoneLayout.setLowerZone (15);
|
||||
setZoneLayout (zoneLayout);
|
||||
}
|
||||
|
||||
MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument)
|
||||
MPESynthesiser::MPESynthesiser (MPEInstrument& mpeInstrument)
|
||||
: MPESynthesiserBase (mpeInstrument)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -314,7 +312,7 @@ void MPESynthesiser::turnOffAllVoices (bool allowTailOff)
|
|||
}
|
||||
|
||||
// finally make sure the MPE Instrument also doesn't have any notes anymore.
|
||||
instrument->releaseAllNotes();
|
||||
instrument.releaseAllNotes();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -65,11 +65,10 @@ public:
|
|||
/** Constructor to pass to the synthesiser a custom MPEInstrument object
|
||||
to handle the MPE note state, MIDI channel assignment etc.
|
||||
(in case you need custom logic for this that goes beyond MIDI and MPE).
|
||||
The synthesiser will take ownership of this object.
|
||||
|
||||
@see MPESynthesiserBase, MPEInstrument
|
||||
*/
|
||||
MPESynthesiser (MPEInstrument* instrumentToUse);
|
||||
MPESynthesiser (MPEInstrument& instrumentToUse);
|
||||
|
||||
/** Destructor. */
|
||||
~MPESynthesiser() override;
|
||||
|
|
@ -303,7 +302,7 @@ protected:
|
|||
|
||||
private:
|
||||
//==============================================================================
|
||||
bool shouldStealVoices = false;
|
||||
std::atomic<bool> shouldStealVoices { false };
|
||||
uint32 lastNoteOnCounter = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser)
|
||||
|
|
|
|||
|
|
@ -24,80 +24,79 @@ namespace juce
|
|||
{
|
||||
|
||||
MPESynthesiserBase::MPESynthesiserBase()
|
||||
: instrument (new MPEInstrument)
|
||||
: instrument (defaultInstrument)
|
||||
{
|
||||
instrument->addListener (this);
|
||||
instrument.addListener (this);
|
||||
}
|
||||
|
||||
MPESynthesiserBase::MPESynthesiserBase (MPEInstrument* inst)
|
||||
MPESynthesiserBase::MPESynthesiserBase (MPEInstrument& inst)
|
||||
: instrument (inst)
|
||||
{
|
||||
jassert (instrument != nullptr);
|
||||
instrument->addListener (this);
|
||||
instrument.addListener (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MPEZoneLayout MPESynthesiserBase::getZoneLayout() const noexcept
|
||||
{
|
||||
return instrument->getZoneLayout();
|
||||
return instrument.getZoneLayout();
|
||||
}
|
||||
|
||||
void MPESynthesiserBase::setZoneLayout (MPEZoneLayout newLayout)
|
||||
{
|
||||
instrument->setZoneLayout (newLayout);
|
||||
instrument.setZoneLayout (newLayout);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MPESynthesiserBase::enableLegacyMode (int pitchbendRange, Range<int> channelRange)
|
||||
{
|
||||
instrument->enableLegacyMode (pitchbendRange, channelRange);
|
||||
instrument.enableLegacyMode (pitchbendRange, channelRange);
|
||||
}
|
||||
|
||||
bool MPESynthesiserBase::isLegacyModeEnabled() const noexcept
|
||||
{
|
||||
return instrument->isLegacyModeEnabled();
|
||||
return instrument.isLegacyModeEnabled();
|
||||
}
|
||||
|
||||
Range<int> MPESynthesiserBase::getLegacyModeChannelRange() const noexcept
|
||||
{
|
||||
return instrument->getLegacyModeChannelRange();
|
||||
return instrument.getLegacyModeChannelRange();
|
||||
}
|
||||
|
||||
void MPESynthesiserBase::setLegacyModeChannelRange (Range<int> channelRange)
|
||||
{
|
||||
instrument->setLegacyModeChannelRange (channelRange);
|
||||
instrument.setLegacyModeChannelRange (channelRange);
|
||||
}
|
||||
|
||||
int MPESynthesiserBase::getLegacyModePitchbendRange() const noexcept
|
||||
{
|
||||
return instrument->getLegacyModePitchbendRange();
|
||||
return instrument.getLegacyModePitchbendRange();
|
||||
}
|
||||
|
||||
void MPESynthesiserBase::setLegacyModePitchbendRange (int pitchbendRange)
|
||||
{
|
||||
instrument->setLegacyModePitchbendRange (pitchbendRange);
|
||||
instrument.setLegacyModePitchbendRange (pitchbendRange);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MPESynthesiserBase::setPressureTrackingMode (TrackingMode modeToUse)
|
||||
{
|
||||
instrument->setPressureTrackingMode (modeToUse);
|
||||
instrument.setPressureTrackingMode (modeToUse);
|
||||
}
|
||||
|
||||
void MPESynthesiserBase::setPitchbendTrackingMode (TrackingMode modeToUse)
|
||||
{
|
||||
instrument->setPitchbendTrackingMode (modeToUse);
|
||||
instrument.setPitchbendTrackingMode (modeToUse);
|
||||
}
|
||||
|
||||
void MPESynthesiserBase::setTimbreTrackingMode (TrackingMode modeToUse)
|
||||
{
|
||||
instrument->setTimbreTrackingMode (modeToUse);
|
||||
instrument.setTimbreTrackingMode (modeToUse);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MPESynthesiserBase::handleMidiEvent (const MidiMessage& m)
|
||||
{
|
||||
instrument->processNextMidiEvent (m);
|
||||
instrument.processNextMidiEvent (m);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -148,7 +147,7 @@ void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate)
|
|||
if (sampleRate != newRate)
|
||||
{
|
||||
const ScopedLock sl (noteStateLock);
|
||||
instrument->releaseAllNotes();
|
||||
instrument.releaseAllNotes();
|
||||
sampleRate = newRate;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,13 +52,12 @@ public:
|
|||
|
||||
/** Constructor.
|
||||
|
||||
If you use this constructor, the synthesiser will take ownership of the
|
||||
provided instrument object, and will use it internally to handle the
|
||||
MPE note state logic.
|
||||
If you use this constructor, the synthesiser will use the provided instrument
|
||||
object to handle the MPE note state logic.
|
||||
This is useful if you want to use an instance of your own class derived
|
||||
from MPEInstrument for the MPE logic.
|
||||
*/
|
||||
MPESynthesiserBase (MPEInstrument* instrument);
|
||||
MPESynthesiserBase (MPEInstrument& instrument);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the synthesiser's internal MPE zone layout.
|
||||
|
|
@ -200,10 +199,12 @@ protected:
|
|||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
std::unique_ptr<MPEInstrument> instrument;
|
||||
MPEInstrument& instrument;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
MPEInstrument defaultInstrument { MPEZone (MPEZone::Type::lower, 15) };
|
||||
|
||||
CriticalSection noteStateLock;
|
||||
double sampleRate = 0.0;
|
||||
int minimumSubBlockSize = 32;
|
||||
|
|
|
|||
|
|
@ -52,25 +52,25 @@ int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept
|
|||
if (numChannels <= 1)
|
||||
return firstChannel;
|
||||
|
||||
for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
|
||||
for (int ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
|
||||
{
|
||||
if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber)
|
||||
if (midiChannels[(size_t) ch].isFree() && midiChannels[(size_t) ch].lastNotePlayed == noteNumber)
|
||||
{
|
||||
midiChannelLastAssigned = ch;
|
||||
midiChannels[ch].notes.add (noteNumber);
|
||||
midiChannels[(size_t) ch].notes.add (noteNumber);
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
|
||||
for (int ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
|
||||
{
|
||||
if (ch == lastChannel + channelIncrement) // loop wrap-around
|
||||
ch = firstChannel;
|
||||
|
||||
if (midiChannels[ch].isFree())
|
||||
if (midiChannels[(size_t) ch].isFree())
|
||||
{
|
||||
midiChannelLastAssigned = ch;
|
||||
midiChannels[ch].notes.add (noteNumber);
|
||||
midiChannels[(size_t) ch].notes.add (noteNumber);
|
||||
return ch;
|
||||
}
|
||||
|
||||
|
|
@ -79,11 +79,21 @@ int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept
|
|||
}
|
||||
|
||||
midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber);
|
||||
midiChannels[midiChannelLastAssigned].notes.add (noteNumber);
|
||||
midiChannels[(size_t) midiChannelLastAssigned].notes.add (noteNumber);
|
||||
|
||||
return midiChannelLastAssigned;
|
||||
}
|
||||
|
||||
int MPEChannelAssigner::findMidiChannelForExistingNote (int noteNumber) noexcept
|
||||
{
|
||||
const auto iter = std::find_if (midiChannels.cbegin(), midiChannels.cend(), [&] (auto& ch)
|
||||
{
|
||||
return std::find (ch.notes.begin(), ch.notes.end(), noteNumber) != ch.notes.end();
|
||||
});
|
||||
|
||||
return iter != midiChannels.cend() ? (int) std::distance (midiChannels.cbegin(), iter) : -1;
|
||||
}
|
||||
|
||||
void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel)
|
||||
{
|
||||
const auto removeNote = [] (MidiChannel& ch, int noteNum)
|
||||
|
|
@ -99,7 +109,7 @@ void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel)
|
|||
|
||||
if (midiChannel >= 0 && midiChannel <= 16)
|
||||
{
|
||||
removeNote (midiChannels[midiChannel], noteNumber);
|
||||
removeNote (midiChannels[(size_t) midiChannel], noteNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -126,9 +136,9 @@ int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumbe
|
|||
auto channelWithClosestNote = firstChannel;
|
||||
int closestNoteDistance = 127;
|
||||
|
||||
for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
|
||||
for (int ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
|
||||
{
|
||||
for (auto note : midiChannels[ch].notes)
|
||||
for (auto note : midiChannels[(size_t) ch].notes)
|
||||
{
|
||||
auto noteDistance = std::abs (note - noteNumber);
|
||||
|
||||
|
|
@ -296,24 +306,35 @@ struct MPEUtilsUnitTests : public UnitTest
|
|||
// check that channels are assigned in correct order
|
||||
int noteNum = 60;
|
||||
for (int ch = 2; ch <= 16; ++ch)
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
|
||||
{
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch);
|
||||
|
||||
++noteNum;
|
||||
}
|
||||
|
||||
// check that note-offs are processed
|
||||
channelAssigner.noteOff (60);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 2);
|
||||
|
||||
channelAssigner.noteOff (61);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 3);
|
||||
|
||||
// check that assigned channel was last to play note
|
||||
channelAssigner.noteOff (65);
|
||||
channelAssigner.noteOff (66);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 8);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 7);
|
||||
|
||||
// find closest channel playing nonequal note
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 2);
|
||||
|
||||
// all notes off
|
||||
channelAssigner.allNotesOff();
|
||||
|
|
@ -323,10 +344,16 @@ struct MPEUtilsUnitTests : public UnitTest
|
|||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 8);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 7);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 2);
|
||||
|
||||
// normal assignment
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 3);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 4);
|
||||
}
|
||||
|
||||
// upper
|
||||
|
|
@ -339,24 +366,35 @@ struct MPEUtilsUnitTests : public UnitTest
|
|||
// check that channels are assigned in correct order
|
||||
int noteNum = 60;
|
||||
for (int ch = 15; ch >= 1; --ch)
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
|
||||
{
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch);
|
||||
|
||||
++noteNum;
|
||||
}
|
||||
|
||||
// check that note-offs are processed
|
||||
channelAssigner.noteOff (60);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 15);
|
||||
|
||||
channelAssigner.noteOff (61);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 14);
|
||||
|
||||
// check that assigned channel was last to play note
|
||||
channelAssigner.noteOff (65);
|
||||
channelAssigner.noteOff (66);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 9);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 10);
|
||||
|
||||
// find closest channel playing nonequal note
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 1);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 15);
|
||||
|
||||
// all notes off
|
||||
channelAssigner.allNotesOff();
|
||||
|
|
@ -366,10 +404,16 @@ struct MPEUtilsUnitTests : public UnitTest
|
|||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 9);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 10);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 1);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 15);
|
||||
|
||||
// normal assignment
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 14);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 13);
|
||||
}
|
||||
|
||||
// legacy
|
||||
|
|
@ -379,24 +423,35 @@ struct MPEUtilsUnitTests : public UnitTest
|
|||
// check that channels are assigned in correct order
|
||||
int noteNum = 60;
|
||||
for (int ch = 1; ch <= 16; ++ch)
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
|
||||
{
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch);
|
||||
|
||||
++noteNum;
|
||||
}
|
||||
|
||||
// check that note-offs are processed
|
||||
channelAssigner.noteOff (60);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 1);
|
||||
|
||||
channelAssigner.noteOff (61);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 2);
|
||||
|
||||
// check that assigned channel was last to play note
|
||||
channelAssigner.noteOff (65);
|
||||
channelAssigner.noteOff (66);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 7);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 6);
|
||||
|
||||
// find closest channel playing nonequal note
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 1);
|
||||
|
||||
// all notes off
|
||||
channelAssigner.allNotesOff();
|
||||
|
|
@ -406,10 +461,16 @@ struct MPEUtilsUnitTests : public UnitTest
|
|||
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 7);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 6);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 1);
|
||||
|
||||
// normal assignment
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2);
|
||||
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 2);
|
||||
expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,11 @@ public:
|
|||
*/
|
||||
int findMidiChannelForNewNote (int noteNumber) noexcept;
|
||||
|
||||
/** If a note has been added using findMidiChannelForNewNote() this will return the channel
|
||||
to which it was assigned, otherwise it will return -1.
|
||||
*/
|
||||
int findMidiChannelForExistingNote (int initialNoteOnNumber) noexcept;
|
||||
|
||||
/** You must call this method for all note-offs that you receive so that this class
|
||||
can keep track of the currently playing notes internally.
|
||||
|
||||
|
|
@ -86,7 +91,7 @@ private:
|
|||
int lastNotePlayed = -1;
|
||||
bool isFree() const noexcept { return notes.isEmpty(); }
|
||||
};
|
||||
MidiChannel midiChannels[17];
|
||||
std::array<MidiChannel, 17> midiChannels;
|
||||
|
||||
//==============================================================================
|
||||
int findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,18 @@ MPEValue MPEValue::from14BitInt (int value) noexcept
|
|||
return { value };
|
||||
}
|
||||
|
||||
MPEValue MPEValue::fromUnsignedFloat (float value) noexcept
|
||||
{
|
||||
jassert (0.0f <= value && value <= 1.0f);
|
||||
return { roundToInt (value * 16383.0f) };
|
||||
}
|
||||
|
||||
MPEValue MPEValue::fromSignedFloat (float value) noexcept
|
||||
{
|
||||
jassert (-1.0f <= value && value <= 1.0f);
|
||||
return { roundToInt (((value + 1.0f) * 16383.0f) / 2.0f) };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MPEValue MPEValue::minValue() noexcept { return MPEValue::from7BitInt (0); }
|
||||
MPEValue MPEValue::centreValue() noexcept { return MPEValue::from7BitInt (64); }
|
||||
|
|
@ -121,26 +133,34 @@ public:
|
|||
|
||||
beginTest ("zero/minimum value");
|
||||
{
|
||||
expectValuesConsistent (MPEValue::from7BitInt (0), 0, 0, -1.0f, 0.0f);
|
||||
expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f);
|
||||
expectValuesConsistent (MPEValue::from7BitInt (0), 0, 0, -1.0f, 0.0f);
|
||||
expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f);
|
||||
expectValuesConsistent (MPEValue::fromUnsignedFloat (0.0f), 0, 0, -1.0f, 0.0f);
|
||||
expectValuesConsistent (MPEValue::fromSignedFloat (-1.0f), 0, 0, -1.0f, 0.0f);
|
||||
}
|
||||
|
||||
beginTest ("maximum value");
|
||||
{
|
||||
expectValuesConsistent (MPEValue::from7BitInt (127), 127, 16383, 1.0f, 1.0f);
|
||||
expectValuesConsistent (MPEValue::from14BitInt (16383), 127, 16383, 1.0f, 1.0f);
|
||||
expectValuesConsistent (MPEValue::from7BitInt (127), 127, 16383, 1.0f, 1.0f);
|
||||
expectValuesConsistent (MPEValue::from14BitInt (16383), 127, 16383, 1.0f, 1.0f);
|
||||
expectValuesConsistent (MPEValue::fromUnsignedFloat (1.0f), 127, 16383, 1.0f, 1.0f);
|
||||
expectValuesConsistent (MPEValue::fromSignedFloat (1.0f), 127, 16383, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
beginTest ("centre value");
|
||||
{
|
||||
expectValuesConsistent (MPEValue::from7BitInt (64), 64, 8192, 0.0f, 0.5f);
|
||||
expectValuesConsistent (MPEValue::from14BitInt (8192), 64, 8192, 0.0f, 0.5f);
|
||||
expectValuesConsistent (MPEValue::from7BitInt (64), 64, 8192, 0.0f, 0.5f);
|
||||
expectValuesConsistent (MPEValue::from14BitInt (8192), 64, 8192, 0.0f, 0.5f);
|
||||
expectValuesConsistent (MPEValue::fromUnsignedFloat (0.5f), 64, 8192, 0.0f, 0.5f);
|
||||
expectValuesConsistent (MPEValue::fromSignedFloat (0.0f), 64, 8192, 0.0f, 0.5f);
|
||||
}
|
||||
|
||||
beginTest ("value halfway between min and centre");
|
||||
{
|
||||
expectValuesConsistent (MPEValue::from7BitInt (32), 32, 4096, -0.5f, 0.25f);
|
||||
expectValuesConsistent (MPEValue::from14BitInt (4096), 32, 4096, -0.5f, 0.25f);
|
||||
expectValuesConsistent (MPEValue::from7BitInt (32), 32, 4096, -0.5f, 0.25f);
|
||||
expectValuesConsistent (MPEValue::from14BitInt (4096), 32, 4096, -0.5f, 0.25f);
|
||||
expectValuesConsistent (MPEValue::fromUnsignedFloat (0.25f), 32, 4096, -0.5f, 0.25f);
|
||||
expectValuesConsistent (MPEValue::fromSignedFloat (-0.5f), 32, 4096, -0.5f, 0.25f);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,12 @@ public:
|
|||
*/
|
||||
static MPEValue from14BitInt (int value) noexcept;
|
||||
|
||||
/** Constructs an MPEValue from a float between 0.0f and 1.0f. */
|
||||
static MPEValue fromUnsignedFloat (float value) noexcept;
|
||||
|
||||
/** Constructs an MPEValue from a float between -1.0f and 1.0f. */
|
||||
static MPEValue fromSignedFloat (float value) noexcept;
|
||||
|
||||
/** Constructs an MPEValue corresponding to the centre value. */
|
||||
static MPEValue centreValue() noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,17 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
MPEZoneLayout::MPEZoneLayout() noexcept {}
|
||||
MPEZoneLayout::MPEZoneLayout (MPEZone lower, MPEZone upper)
|
||||
: lowerZone (lower), upperZone (upper)
|
||||
{
|
||||
}
|
||||
|
||||
MPEZoneLayout::MPEZoneLayout (MPEZone zone)
|
||||
: lowerZone (zone.isLowerZone() ? zone : MPEZone()),
|
||||
upperZone (! zone.isLowerZone() ? zone : MPEZone())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other)
|
||||
: lowerZone (other.lowerZone),
|
||||
|
|
@ -54,9 +64,9 @@ void MPEZoneLayout::setZone (bool isLower, int numMemberChannels, int perNotePit
|
|||
checkAndLimitZoneParameters (0, 96, masterPitchbendRange);
|
||||
|
||||
if (isLower)
|
||||
lowerZone = { true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
|
||||
lowerZone = { MPEZone::Type::lower, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
|
||||
else
|
||||
upperZone = { false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
|
||||
upperZone = { MPEZone::Type::upper, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
|
||||
|
||||
if (numMemberChannels > 0)
|
||||
{
|
||||
|
|
@ -86,8 +96,8 @@ void MPEZoneLayout::setUpperZone (int numMemberChannels, int perNotePitchbendRan
|
|||
|
||||
void MPEZoneLayout::clearAllZones()
|
||||
{
|
||||
lowerZone = { true, 0 };
|
||||
upperZone = { false, 0 };
|
||||
lowerZone = { MPEZone::Type::lower, 0 };
|
||||
upperZone = { MPEZone::Type::upper, 0 };
|
||||
|
||||
sendLayoutChangeMessage();
|
||||
}
|
||||
|
|
@ -128,7 +138,7 @@ void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn)
|
|||
}
|
||||
}
|
||||
|
||||
void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value)
|
||||
void MPEZoneLayout::updateMasterPitchbend (MPEZone& zone, int value)
|
||||
{
|
||||
if (zone.masterPitchbendRange != value)
|
||||
{
|
||||
|
|
@ -138,7 +148,7 @@ void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value)
|
|||
}
|
||||
}
|
||||
|
||||
void MPEZoneLayout::updatePerNotePitchbendRange (Zone& zone, int value)
|
||||
void MPEZoneLayout::updatePerNotePitchbendRange (MPEZone& zone, int value)
|
||||
{
|
||||
if (zone.perNotePitchbendRange != value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,83 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This struct represents an MPE zone.
|
||||
|
||||
It can either be a lower or an upper zone, where:
|
||||
- A lower zone encompasses master channel 1 and an arbitrary number of ascending
|
||||
MIDI channels, increasing from channel 2.
|
||||
- An upper zone encompasses master channel 16 and an arbitrary number of descending
|
||||
MIDI channels, decreasing from channel 15.
|
||||
|
||||
It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and
|
||||
master pitchbends, respectively.
|
||||
*/
|
||||
struct MPEZone
|
||||
{
|
||||
enum class Type { lower, upper };
|
||||
|
||||
MPEZone() = default;
|
||||
MPEZone (const MPEZone& other) = default;
|
||||
|
||||
MPEZone (Type type, int memberChannels = 0, int perNotePitchbend = 48, int masterPitchbend = 2)
|
||||
: zoneType (type),
|
||||
numMemberChannels (memberChannels),
|
||||
perNotePitchbendRange (perNotePitchbend),
|
||||
masterPitchbendRange (masterPitchbend)
|
||||
{}
|
||||
|
||||
bool isLowerZone() const noexcept { return zoneType == Type::lower; }
|
||||
bool isUpperZone() const noexcept { return zoneType == Type::upper; }
|
||||
|
||||
bool isActive() const noexcept { return numMemberChannels > 0; }
|
||||
|
||||
int getMasterChannel() const noexcept { return isLowerZone() ? lowerZoneMasterChannel : upperZoneMasterChannel; }
|
||||
int getFirstMemberChannel() const noexcept { return isLowerZone() ? lowerZoneMasterChannel + 1 : upperZoneMasterChannel - 1; }
|
||||
int getLastMemberChannel() const noexcept { return isLowerZone() ? (lowerZoneMasterChannel + numMemberChannels)
|
||||
: (upperZoneMasterChannel - numMemberChannels); }
|
||||
|
||||
bool isUsingChannelAsMemberChannel (int channel) const noexcept
|
||||
{
|
||||
return isLowerZone() ? (lowerZoneMasterChannel < channel && channel <= getLastMemberChannel())
|
||||
: (channel < upperZoneMasterChannel && getLastMemberChannel() <= channel);
|
||||
}
|
||||
|
||||
bool isUsing (int channel) const noexcept
|
||||
{
|
||||
return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel();
|
||||
}
|
||||
|
||||
static auto tie (const MPEZone& z)
|
||||
{
|
||||
return std::tie (z.zoneType,
|
||||
z.numMemberChannels,
|
||||
z.perNotePitchbendRange,
|
||||
z.masterPitchbendRange);
|
||||
}
|
||||
|
||||
bool operator== (const MPEZone& other) const
|
||||
{
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool operator!= (const MPEZone& other) const
|
||||
{
|
||||
return tie (*this) != tie (other);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static constexpr int lowerZoneMasterChannel = 1,
|
||||
upperZoneMasterChannel = 16;
|
||||
|
||||
Type zoneType = Type::lower;
|
||||
|
||||
int numMemberChannels = 0;
|
||||
int perNotePitchbendRange = 48;
|
||||
int masterPitchbendRange = 2;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This class represents the current MPE zone layout of a device capable of handling MPE.
|
||||
|
|
@ -44,89 +121,28 @@ namespace juce
|
|||
class JUCE_API MPEZoneLayout
|
||||
{
|
||||
public:
|
||||
/** Default constructor.
|
||||
//==============================================================================
|
||||
/** Creates a layout with inactive upper and lower zones. */
|
||||
MPEZoneLayout() = default;
|
||||
|
||||
This will create a layout with inactive lower and upper zones, representing
|
||||
a device with MPE mode disabled.
|
||||
/** Creates a layout with the given upper and lower zones. */
|
||||
MPEZoneLayout (MPEZone lower, MPEZone upper);
|
||||
|
||||
You can set the lower or upper MPE zones using the setZone() method.
|
||||
/** Creates a layout with a single upper or lower zone, leaving the other zone uninitialised. */
|
||||
MPEZoneLayout (MPEZone singleZone);
|
||||
|
||||
@see setZone
|
||||
*/
|
||||
MPEZoneLayout() noexcept;
|
||||
|
||||
/** Copy constuctor.
|
||||
This will not copy the listeners registered to the MPEZoneLayout.
|
||||
*/
|
||||
MPEZoneLayout (const MPEZoneLayout& other);
|
||||
|
||||
/** Copy assignment operator.
|
||||
This will not copy the listeners registered to the MPEZoneLayout.
|
||||
*/
|
||||
MPEZoneLayout& operator= (const MPEZoneLayout& other);
|
||||
|
||||
bool operator== (const MPEZoneLayout& other) const { return lowerZone == other.lowerZone && upperZone == other.upperZone; }
|
||||
bool operator!= (const MPEZoneLayout& other) const { return ! operator== (other); }
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This struct represents an MPE zone.
|
||||
/** Returns a struct representing the lower MPE zone. */
|
||||
MPEZone getLowerZone() const noexcept { return lowerZone; }
|
||||
|
||||
It can either be a lower or an upper zone, where:
|
||||
- A lower zone encompasses master channel 1 and an arbitrary number of ascending
|
||||
MIDI channels, increasing from channel 2.
|
||||
- An upper zone encompasses master channel 16 and an arbitrary number of descending
|
||||
MIDI channels, decreasing from channel 15.
|
||||
|
||||
It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and
|
||||
master pitchbends, respectively.
|
||||
*/
|
||||
struct Zone
|
||||
{
|
||||
Zone (const Zone& other) = default;
|
||||
|
||||
bool isLowerZone() const noexcept { return lowerZone; }
|
||||
bool isUpperZone() const noexcept { return ! lowerZone; }
|
||||
|
||||
bool isActive() const noexcept { return numMemberChannels > 0; }
|
||||
|
||||
int getMasterChannel() const noexcept { return lowerZone ? 1 : 16; }
|
||||
int getFirstMemberChannel() const noexcept { return lowerZone ? 2 : 15; }
|
||||
int getLastMemberChannel() const noexcept { return lowerZone ? (1 + numMemberChannels)
|
||||
: (16 - numMemberChannels); }
|
||||
|
||||
bool isUsingChannelAsMemberChannel (int channel) const noexcept
|
||||
{
|
||||
return lowerZone ? (channel > 1 && channel <= 1 + numMemberChannels)
|
||||
: (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
|
||||
&& masterPitchbendRange == other.masterPitchbendRange; }
|
||||
|
||||
bool operator!= (const Zone& other) const noexcept { return ! operator== (other); }
|
||||
|
||||
int numMemberChannels;
|
||||
int perNotePitchbendRange;
|
||||
int masterPitchbendRange;
|
||||
|
||||
private:
|
||||
friend class MPEZoneLayout;
|
||||
|
||||
Zone (bool lower, int memberChans = 0, int perNotePb = 48, int masterPb = 2) noexcept
|
||||
: numMemberChannels (memberChans),
|
||||
perNotePitchbendRange (perNotePb),
|
||||
masterPitchbendRange (masterPb),
|
||||
lowerZone (lower)
|
||||
{
|
||||
}
|
||||
|
||||
bool lowerZone;
|
||||
};
|
||||
/** Returns a struct representing the upper MPE zone. */
|
||||
MPEZone getUpperZone() const noexcept { return upperZone; }
|
||||
|
||||
/** Sets the lower zone of this layout. */
|
||||
void setLowerZone (int numMemberChannels = 0,
|
||||
|
|
@ -138,17 +154,14 @@ public:
|
|||
int perNotePitchbendRange = 48,
|
||||
int masterPitchbendRange = 2) noexcept;
|
||||
|
||||
/** Returns a struct representing the lower MPE zone. */
|
||||
const Zone getLowerZone() const noexcept { return lowerZone; }
|
||||
|
||||
/** Returns a struct representing the upper MPE zone. */
|
||||
const Zone getUpperZone() const noexcept { return upperZone; }
|
||||
|
||||
/** Clears the lower and upper zones of this layout, making them both inactive
|
||||
and disabling MPE mode.
|
||||
*/
|
||||
void clearAllZones();
|
||||
|
||||
/** Returns true if either of the zones are active. */
|
||||
bool isActive() const { return lowerZone.isActive() || upperZone.isActive(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Pass incoming MIDI messages to an object of this class if you want the
|
||||
zone layout to properly react to MPE RPN messages like an
|
||||
|
|
@ -200,10 +213,14 @@ public:
|
|||
/** Removes a listener. */
|
||||
void removeListener (Listener* const listenerToRemove) noexcept;
|
||||
|
||||
#ifndef DOXYGEN
|
||||
using Zone = MPEZone;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Zone lowerZone { true, 0 };
|
||||
Zone upperZone { false, 0 };
|
||||
MPEZone lowerZone { MPEZone::Type::lower, 0 };
|
||||
MPEZone upperZone { MPEZone::Type::upper, 0 };
|
||||
|
||||
MidiRPNDetector rpnDetector;
|
||||
ListenerList<Listener> listeners;
|
||||
|
|
@ -215,8 +232,8 @@ private:
|
|||
void processZoneLayoutRpnMessage (MidiRPNMessage);
|
||||
void processPitchbendRangeRpnMessage (MidiRPNMessage);
|
||||
|
||||
void updateMasterPitchbend (Zone&, int);
|
||||
void updatePerNotePitchbendRange (Zone&, int);
|
||||
void updateMasterPitchbend (MPEZone&, int);
|
||||
void updatePerNotePitchbendRange (MPEZone&, int);
|
||||
|
||||
void sendLayoutChangeMessage();
|
||||
void checkAndLimitZoneParameters (int, int, int&) noexcept;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue