226 lines
8.6 KiB
C
226 lines
8.6 KiB
C
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE library.
|
||
|
Copyright (c) 2020 - Raw Material Software Limited
|
||
|
|
||
|
JUCE is an open source library subject to commercial or open-source
|
||
|
licensing.
|
||
|
|
||
|
The code included in this file is provided under the terms of the ISC license
|
||
|
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||
|
To use, copy, modify, and/or distribute this software for any purpose with or
|
||
|
without fee is hereby granted provided that the above copyright notice and
|
||
|
this permission notice appear in all copies.
|
||
|
|
||
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||
|
DISCLAIMED.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
namespace juce
|
||
|
{
|
||
|
|
||
|
//==============================================================================
|
||
|
/**
|
||
|
This class represents the current MPE zone layout of a device capable of handling MPE.
|
||
|
|
||
|
An MPE device can have up to two zones: a lower zone with master channel 1 and
|
||
|
allocated MIDI channels increasing from channel 2, and an upper zone with master
|
||
|
channel 16 and allocated MIDI channels decreasing from channel 15. MPE mode is
|
||
|
enabled on a device when one of these zones is active and disabled when both
|
||
|
are inactive.
|
||
|
|
||
|
Use the MPEMessages helper class to convert the zone layout represented
|
||
|
by this object to MIDI message sequences that you can send to an Expressive
|
||
|
MIDI device to set its zone layout, add zones etc.
|
||
|
|
||
|
@see MPEInstrument
|
||
|
|
||
|
@tags{Audio}
|
||
|
*/
|
||
|
class JUCE_API MPEZoneLayout
|
||
|
{
|
||
|
public:
|
||
|
/** Default constructor.
|
||
|
|
||
|
This will create a layout with inactive lower and upper zones, representing
|
||
|
a device with MPE mode disabled.
|
||
|
|
||
|
You can set the lower or upper MPE zones using the setZone() method.
|
||
|
|
||
|
@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);
|
||
|
|
||
|
//==============================================================================
|
||
|
/**
|
||
|
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 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;
|
||
|
};
|
||
|
|
||
|
/** Sets the lower zone of this layout. */
|
||
|
void setLowerZone (int numMemberChannels = 0,
|
||
|
int perNotePitchbendRange = 48,
|
||
|
int masterPitchbendRange = 2) noexcept;
|
||
|
|
||
|
/** Sets the upper zone of this layout. */
|
||
|
void setUpperZone (int numMemberChannels = 0,
|
||
|
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();
|
||
|
|
||
|
//==============================================================================
|
||
|
/** 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
|
||
|
MPE device.
|
||
|
|
||
|
MPEMessages::rpnNumber will add or remove zones; RPN 0 will
|
||
|
set the per-note or master pitchbend ranges.
|
||
|
|
||
|
Any other MIDI messages will be ignored by this class.
|
||
|
|
||
|
@see MPEMessages
|
||
|
*/
|
||
|
void processNextMidiEvent (const MidiMessage& message);
|
||
|
|
||
|
/** Pass incoming MIDI buffers to an object of this class if you want the
|
||
|
zone layout to properly react to MPE RPN messages like an
|
||
|
MPE device.
|
||
|
|
||
|
MPEMessages::rpnNumber will add or remove zones; RPN 0 will
|
||
|
set the per-note or master pitchbend ranges.
|
||
|
|
||
|
Any other MIDI messages will be ignored by this class.
|
||
|
|
||
|
@see MPEMessages
|
||
|
*/
|
||
|
void processNextMidiBuffer (const MidiBuffer& buffer);
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Listener class. Derive from this class to allow your class to be
|
||
|
notified about changes to the zone layout.
|
||
|
*/
|
||
|
class Listener
|
||
|
{
|
||
|
public:
|
||
|
/** Destructor. */
|
||
|
virtual ~Listener() = default;
|
||
|
|
||
|
/** Implement this callback to be notified about any changes to this
|
||
|
MPEZoneLayout. Will be called whenever a zone is added, zones are
|
||
|
removed, or any zone's master or note pitchbend ranges change.
|
||
|
*/
|
||
|
virtual void zoneLayoutChanged (const MPEZoneLayout& layout) = 0;
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Adds a listener. */
|
||
|
void addListener (Listener* const listenerToAdd) noexcept;
|
||
|
|
||
|
/** Removes a listener. */
|
||
|
void removeListener (Listener* const listenerToRemove) noexcept;
|
||
|
|
||
|
private:
|
||
|
//==============================================================================
|
||
|
Zone lowerZone { true, 0 };
|
||
|
Zone upperZone { false, 0 };
|
||
|
|
||
|
MidiRPNDetector rpnDetector;
|
||
|
ListenerList<Listener> listeners;
|
||
|
|
||
|
//==============================================================================
|
||
|
void setZone (bool, int, int, int) noexcept;
|
||
|
|
||
|
void processRpnMessage (MidiRPNMessage);
|
||
|
void processZoneLayoutRpnMessage (MidiRPNMessage);
|
||
|
void processPitchbendRangeRpnMessage (MidiRPNMessage);
|
||
|
|
||
|
void updateMasterPitchbend (Zone&, int);
|
||
|
void updatePerNotePitchbendRange (Zone&, int);
|
||
|
|
||
|
void sendLayoutChangeMessage();
|
||
|
void checkAndLimitZoneParameters (int, int, int&) noexcept;
|
||
|
};
|
||
|
|
||
|
} // namespace juce
|