1 /* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 The code included in this file is provided under the terms of the ISC license 11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission 12 To use, copy, modify, and/or distribute this software for any purpose with or 13 without fee is hereby granted provided that the above copyright notice and 14 this permission notice appear in all copies. 15 16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 18 DISCLAIMED. 19 20 ============================================================================== 21 */ 22 23 namespace juce 24 { 25 26 //============================================================================== 27 /** 28 This class represents the current MPE zone layout of a device capable of handling MPE. 29 30 An MPE device can have up to two zones: a lower zone with master channel 1 and 31 allocated MIDI channels increasing from channel 2, and an upper zone with master 32 channel 16 and allocated MIDI channels decreasing from channel 15. MPE mode is 33 enabled on a device when one of these zones is active and disabled when both 34 are inactive. 35 36 Use the MPEMessages helper class to convert the zone layout represented 37 by this object to MIDI message sequences that you can send to an Expressive 38 MIDI device to set its zone layout, add zones etc. 39 40 @see MPEInstrument 41 42 @tags{Audio} 43 */ 44 class JUCE_API MPEZoneLayout 45 { 46 public: 47 /** Default constructor. 48 49 This will create a layout with inactive lower and upper zones, representing 50 a device with MPE mode disabled. 51 52 You can set the lower or upper MPE zones using the setZone() method. 53 54 @see setZone 55 */ 56 MPEZoneLayout() noexcept; 57 58 /** Copy constuctor. 59 This will not copy the listeners registered to the MPEZoneLayout. 60 */ 61 MPEZoneLayout (const MPEZoneLayout& other); 62 63 /** Copy assignment operator. 64 This will not copy the listeners registered to the MPEZoneLayout. 65 */ 66 MPEZoneLayout& operator= (const MPEZoneLayout& other); 67 68 //============================================================================== 69 /** 70 This struct represents an MPE zone. 71 72 It can either be a lower or an upper zone, where: 73 - A lower zone encompasses master channel 1 and an arbitrary number of ascending 74 MIDI channels, increasing from channel 2. 75 - An upper zone encompasses master channel 16 and an arbitrary number of descending 76 MIDI channels, decreasing from channel 15. 77 78 It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and 79 master pitchbends, respectively. 80 */ 81 struct Zone 82 { 83 Zone (const Zone& other) = default; 84 isLowerZoneZone85 bool isLowerZone() const noexcept { return lowerZone; } isUpperZoneZone86 bool isUpperZone() const noexcept { return ! lowerZone; } 87 isActiveZone88 bool isActive() const noexcept { return numMemberChannels > 0; } 89 getMasterChannelZone90 int getMasterChannel() const noexcept { return lowerZone ? 1 : 16; } getFirstMemberChannelZone91 int getFirstMemberChannel() const noexcept { return lowerZone ? 2 : 15; } getLastMemberChannelZone92 int getLastMemberChannel() const noexcept { return lowerZone ? (1 + numMemberChannels) 93 : (16 - numMemberChannels); } 94 isUsingChannelAsMemberChannelZone95 bool isUsingChannelAsMemberChannel (int channel) const noexcept 96 { 97 return lowerZone ? (channel > 1 && channel <= 1 + numMemberChannels) 98 : (channel < 16 && channel >= 16 - numMemberChannels); 99 } 100 isUsingZone101 bool isUsing (int channel) const noexcept 102 { 103 return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel(); 104 } 105 106 bool operator== (const Zone& other) const noexcept { return lowerZone == other.lowerZone 107 && numMemberChannels == other.numMemberChannels 108 && perNotePitchbendRange == other.perNotePitchbendRange 109 && masterPitchbendRange == other.masterPitchbendRange; } 110 111 bool operator!= (const Zone& other) const noexcept { return ! operator== (other); } 112 113 int numMemberChannels; 114 int perNotePitchbendRange; 115 int masterPitchbendRange; 116 117 private: 118 friend class MPEZoneLayout; 119 120 Zone (bool lower, int memberChans = 0, int perNotePb = 48, int masterPb = 2) noexcept numMemberChannelsZone121 : numMemberChannels (memberChans), 122 perNotePitchbendRange (perNotePb), 123 masterPitchbendRange (masterPb), 124 lowerZone (lower) 125 { 126 } 127 128 bool lowerZone; 129 }; 130 131 /** Sets the lower zone of this layout. */ 132 void setLowerZone (int numMemberChannels = 0, 133 int perNotePitchbendRange = 48, 134 int masterPitchbendRange = 2) noexcept; 135 136 /** Sets the upper zone of this layout. */ 137 void setUpperZone (int numMemberChannels = 0, 138 int perNotePitchbendRange = 48, 139 int masterPitchbendRange = 2) noexcept; 140 141 /** Returns a struct representing the lower MPE zone. */ getLowerZone()142 const Zone getLowerZone() const noexcept { return lowerZone; } 143 144 /** Returns a struct representing the upper MPE zone. */ getUpperZone()145 const Zone getUpperZone() const noexcept { return upperZone; } 146 147 /** Clears the lower and upper zones of this layout, making them both inactive 148 and disabling MPE mode. 149 */ 150 void clearAllZones(); 151 152 //============================================================================== 153 /** Pass incoming MIDI messages to an object of this class if you want the 154 zone layout to properly react to MPE RPN messages like an 155 MPE device. 156 157 MPEMessages::rpnNumber will add or remove zones; RPN 0 will 158 set the per-note or master pitchbend ranges. 159 160 Any other MIDI messages will be ignored by this class. 161 162 @see MPEMessages 163 */ 164 void processNextMidiEvent (const MidiMessage& message); 165 166 /** Pass incoming MIDI buffers to an object of this class if you want the 167 zone layout to properly react to MPE RPN messages like an 168 MPE device. 169 170 MPEMessages::rpnNumber will add or remove zones; RPN 0 will 171 set the per-note or master pitchbend ranges. 172 173 Any other MIDI messages will be ignored by this class. 174 175 @see MPEMessages 176 */ 177 void processNextMidiBuffer (const MidiBuffer& buffer); 178 179 //============================================================================== 180 /** Listener class. Derive from this class to allow your class to be 181 notified about changes to the zone layout. 182 */ 183 class Listener 184 { 185 public: 186 /** Destructor. */ 187 virtual ~Listener() = default; 188 189 /** Implement this callback to be notified about any changes to this 190 MPEZoneLayout. Will be called whenever a zone is added, zones are 191 removed, or any zone's master or note pitchbend ranges change. 192 */ 193 virtual void zoneLayoutChanged (const MPEZoneLayout& layout) = 0; 194 }; 195 196 //============================================================================== 197 /** Adds a listener. */ 198 void addListener (Listener* const listenerToAdd) noexcept; 199 200 /** Removes a listener. */ 201 void removeListener (Listener* const listenerToRemove) noexcept; 202 203 private: 204 //============================================================================== 205 Zone lowerZone { true, 0 }; 206 Zone upperZone { false, 0 }; 207 208 MidiRPNDetector rpnDetector; 209 ListenerList<Listener> listeners; 210 211 //============================================================================== 212 void setZone (bool, int, int, int) noexcept; 213 214 void processRpnMessage (MidiRPNMessage); 215 void processZoneLayoutRpnMessage (MidiRPNMessage); 216 void processPitchbendRangeRpnMessage (MidiRPNMessage); 217 218 void updateMasterPitchbend (Zone&, int); 219 void updatePerNotePitchbendRange (Zone&, int); 220 221 void sendLayoutChangeMessage(); 222 void checkAndLimitZoneParameters (int, int, int&) noexcept; 223 }; 224 225 } // namespace juce 226