OpenShot Library | OpenShotAudio 0.2.2
juce_MPEInstrument.h
1
2/** @weakgroup juce_audio_basics-mpe
3 * @{
4 */
5/*
6 ==============================================================================
7
8 This file is part of the JUCE library.
9 Copyright (c) 2017 - ROLI Ltd.
10
11 JUCE is an open source library subject to commercial or open-source
12 licensing.
13
14 The code included in this file is provided under the terms of the ISC license
15 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
16 To use, copy, modify, and/or distribute this software for any purpose with or
17 without fee is hereby granted provided that the above copyright notice and
18 this permission notice appear in all copies.
19
20 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
21 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
22 DISCLAIMED.
23
24 ==============================================================================
25*/
26
27namespace juce
28{
29
30//==============================================================================
31/**
32 This class represents an instrument handling MPE.
33
34 It has an MPE zone layout and maintains a state of currently
35 active (playing) notes and the values of their dimensions of expression.
36
37 You can trigger and modulate notes:
38 - by passing MIDI messages with the method processNextMidiEvent;
39 - by directly calling the methods noteOn, noteOff etc.
40
41 The class implements the channel and note management logic specified in
42 MPE. If you pass it a message, it will know what notes on what
43 channels (if any) should be affected by that message.
44
45 The class has a Listener class with the three callbacks MPENoteAdded,
46 MPENoteChanged, and MPENoteFinished. Implement such a
47 Listener class to react to note changes and trigger some functionality for
48 your application that depends on the MPE note state.
49 For example, you can use this class to write an MPE visualiser.
50
51 If you want to write a real-time audio synth with MPE functionality,
52 you should instead use the classes MPESynthesiserBase, which adds
53 the ability to render audio and to manage voices.
54
55 @see MPENote, MPEZoneLayout, MPESynthesiser
56
57 @tags{Audio}
58*/
60{
61public:
62 /** Constructor.
63
64 This will construct an MPE instrument with inactive lower and upper zones.
65
66 In order to process incoming MIDI, call setZoneLayout, define the layout
67 via MIDI RPN messages, or set the instrument to legacy mode.
68 */
69 MPEInstrument() noexcept;
70
71 /** Destructor. */
72 virtual ~MPEInstrument();
73
74 //==============================================================================
75 /** Returns the current zone layout of the instrument.
76 This happens by value, to enforce thread-safety and class invariants.
77
78 Note: If the instrument is in legacy mode, the return value of this
79 method is unspecified.
80 */
81 MPEZoneLayout getZoneLayout() const noexcept;
82
83 /** Re-sets the zone layout of the instrument to the one passed in.
84 As a side effect, this will discard all currently playing notes,
85 and call noteReleased for all of them.
86
87 This will also disable legacy mode in case it was enabled previously.
88 */
89 void setZoneLayout (MPEZoneLayout newLayout);
90
91 /** Returns true if the given MIDI channel (1-16) is a note channel in any
92 of the MPEInstrument's MPE zones; false otherwise.
93
94 When in legacy mode, this will return true if the given channel is
95 contained in the current legacy mode channel range; false otherwise.
96 */
97 bool isMemberChannel (int midiChannel) const noexcept;
98
99 /** Returns true if the given MIDI channel (1-16) is a master channel (channel
100 1 or 16).
101
102 In legacy mode, this will always return false.
103 */
104 bool isMasterChannel (int midiChannel) const noexcept;
105
106 /** Returns true if the given MIDI channel (1-16) is used by any of the
107 MPEInstrument's MPE zones; false otherwise.
108
109 When in legacy mode, this will return true if the given channel is
110 contained in the current legacy mode channel range; false otherwise.
111 */
112 bool isUsingChannel (int midiChannel) const noexcept;
113
114 //==============================================================================
115 /** The MPE note tracking mode. In case there is more than one note playing
116 simultaneously on the same MIDI channel, this determines which of these
117 notes will be modulated by an incoming MPE message on that channel
118 (pressure, pitchbend, or timbre).
119
120 The default is lastNotePlayedOnChannel.
121 */
123 {
124 lastNotePlayedOnChannel, /**< The most recent note on the channel that is still played (key down and/or sustained). */
125 lowestNoteOnChannel, /**< The lowest note (by initialNote) on the channel with the note key still down. */
126 highestNoteOnChannel, /**< The highest note (by initialNote) on the channel with the note key still down. */
127 allNotesOnChannel /**< All notes on the channel (key down and/or sustained). */
128 };
129
130 /** Set the MPE tracking mode for the pressure dimension. */
131 void setPressureTrackingMode (TrackingMode modeToUse);
132
133 /** Set the MPE tracking mode for the pitchbend dimension. */
134 void setPitchbendTrackingMode (TrackingMode modeToUse);
135
136 /** Set the MPE tracking mode for the timbre dimension. */
137 void setTimbreTrackingMode (TrackingMode modeToUse);
138
139 //==============================================================================
140 /** Process a MIDI message and trigger the appropriate method calls
141 (noteOn, noteOff etc.)
142
143 You can override this method if you need some special MIDI message
144 treatment on top of the standard MPE logic implemented here.
145 */
146 virtual void processNextMidiEvent (const MidiMessage& message);
147
148 //==============================================================================
149 /** Request a note-on on the given channel, with the given initial note
150 number and velocity.
151
152 If the message arrives on a valid note channel, this will create a
153 new MPENote and call the noteAdded callback.
154 */
155 virtual void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity);
156
157 /** Request a note-off.
158
159 If there is a matching playing note, this will release the note
160 (except if it is sustained by a sustain or sostenuto pedal) and call
161 the noteReleased callback.
162 */
163 virtual void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity);
164
165 /** Request a pitchbend on the given channel with the given value (in units
166 of MIDI pitchwheel position).
167
168 Internally, this will determine whether the pitchwheel move is a
169 per-note pitchbend or a master pitchbend (depending on midiChannel),
170 take the correct per-note or master pitchbend range of the affected MPE
171 zone, and apply the resulting pitchbend to the affected note(s) (if any).
172 */
173 virtual void pitchbend (int midiChannel, MPEValue pitchbend);
174
175 /** Request a pressure change on the given channel with the given value.
176
177 This will modify the pressure dimension of the note currently held down
178 on this channel (if any). If the channel is a zone master channel,
179 the pressure change will be broadcast to all notes in this zone.
180 */
181 virtual void pressure (int midiChannel, MPEValue value);
182
183 /** Request a third dimension (timbre) change on the given channel with the
184 given value.
185
186 This will modify the timbre dimension of the note currently held down
187 on this channel (if any). If the channel is a zone master channel,
188 the timbre change will be broadcast to all notes in this zone.
189 */
190 virtual void timbre (int midiChannel, MPEValue value);
191
192 /** Request a poly-aftertouch change for a given note number.
193
194 The change will be broadcast to all notes sharing the channel and note
195 number of the change message.
196 */
197 virtual void polyAftertouch (int midiChannel, int midiNoteNumber, MPEValue value);
198
199 /** Request a sustain pedal press or release.
200
201 If midiChannel is a zone's master channel, this will act on all notes in
202 that zone; otherwise, nothing will happen.
203 */
204 virtual void sustainPedal (int midiChannel, bool isDown);
205
206 /** Request a sostenuto pedal press or release.
207
208 If midiChannel is a zone's master channel, this will act on all notes in
209 that zone; otherwise, nothing will happen.
210 */
211 virtual void sostenutoPedal (int midiChannel, bool isDown);
212
213 /** Discard all currently playing notes.
214
215 This will also call the noteReleased listener callback for all of them.
216 */
217 void releaseAllNotes();
218
219 //==============================================================================
220 /** Returns the number of MPE notes currently played by the instrument. */
221 int getNumPlayingNotes() const noexcept;
222
223 /** Returns the note at the given index.
224
225 If there is no such note, returns an invalid MPENote. The notes are sorted
226 such that the most recently added note is the last element.
227 */
228 MPENote getNote (int index) const noexcept;
229
230 /** Returns the note currently playing on the given midiChannel with the
231 specified initial MIDI note number, if there is such a note. Otherwise,
232 this returns an invalid MPENote (check with note.isValid() before use!)
233 */
234 MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept;
235
236 /** Returns the most recent note that is playing on the given midiChannel
237 (this will be the note which has received the most recent note-on without
238 a corresponding note-off), if there is such a note. Otherwise, this returns an
239 invalid MPENote (check with note.isValid() before use!)
240 */
241 MPENote getMostRecentNote (int midiChannel) const noexcept;
242
243 /** Returns the most recent note that is not the note passed in. If there is no
244 such note, this returns an invalid MPENote (check with note.isValid() before use!).
245
246 This helper method might be useful for some custom voice handling algorithms.
247 */
248 MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept;
249
250 //==============================================================================
251 /** Derive from this class to be informed about any changes in the expressive
252 MIDI notes played by this instrument.
253
254 Note: This listener type receives its callbacks immediately, and not
255 via the message thread (so you might be for example in the MIDI thread).
256 Therefore you should never do heavy work such as graphics rendering etc.
257 inside those callbacks.
258 */
260 {
261 public:
262 /** Destructor. */
263 virtual ~Listener() = default;
264
265 /** Implement this callback to be informed whenever a new expressive MIDI
266 note is triggered.
267 */
268 virtual void noteAdded (MPENote newNote) { ignoreUnused (newNote); }
269
270 /** Implement this callback to be informed whenever a currently playing
271 MPE note's pressure value changes.
272 */
273 virtual void notePressureChanged (MPENote changedNote) { ignoreUnused (changedNote); }
274
275 /** Implement this callback to be informed whenever a currently playing
276 MPE note's pitchbend value changes.
277
278 Note: This can happen if the note itself is bent, if there is a
279 master channel pitchbend event, or if both occur simultaneously.
280 Call MPENote::getFrequencyInHertz to get the effective note frequency.
281 */
282 virtual void notePitchbendChanged (MPENote changedNote) { ignoreUnused (changedNote); }
283
284 /** Implement this callback to be informed whenever a currently playing
285 MPE note's timbre value changes.
286 */
287 virtual void noteTimbreChanged (MPENote changedNote) { ignoreUnused (changedNote); }
288
289 /** Implement this callback to be informed whether a currently playing
290 MPE note's key state (whether the key is down and/or the note is
291 sustained) has changed.
292
293 Note: If the key state changes to MPENote::off, noteReleased is
294 called instead.
295 */
296 virtual void noteKeyStateChanged (MPENote changedNote) { ignoreUnused (changedNote); }
297
298 /** Implement this callback to be informed whenever an MPE note
299 is released (either by a note-off message, or by a sustain/sostenuto
300 pedal release for a note that already received a note-off),
301 and should therefore stop playing.
302 */
303 virtual void noteReleased (MPENote finishedNote) { ignoreUnused (finishedNote); }
304 };
305
306 //==============================================================================
307 /** Adds a listener. */
308 void addListener (Listener* listenerToAdd);
309
310 /** Removes a listener. */
311 void removeListener (Listener* listenerToRemove);
312
313 //==============================================================================
314 /** Puts the instrument into legacy mode.
315 As a side effect, this will discard all currently playing notes,
316 and call noteReleased for all of them.
317
318 This special zone layout mode is for backwards compatibility with
319 non-MPE MIDI devices. In this mode, the instrument will ignore the
320 current MPE zone layout. It will instead take a range of MIDI channels
321 (default: all channels 1-16) and treat them as note channels, with no
322 master channel. MIDI channels outside of this range will be ignored.
323
324 @param pitchbendRange The note pitchbend range in semitones to use when in legacy mode.
325 Must be between 0 and 96, otherwise behaviour is undefined.
326 The default pitchbend range in legacy mode is +/- 2 semitones.
327
328 @param channelRange The range of MIDI channels to use for notes when in legacy mode.
329 The default is to use all MIDI channels (1-16).
330
331 To get out of legacy mode, set a new MPE zone layout using setZoneLayout.
332 */
333 void enableLegacyMode (int pitchbendRange = 2,
334 Range<int> channelRange = Range<int> (1, 17));
335
336 /** Returns true if the instrument is in legacy mode, false otherwise. */
337 bool isLegacyModeEnabled() const noexcept;
338
339 /** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
340 Range<int> getLegacyModeChannelRange() const noexcept;
341
342 /** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
343 void setLegacyModeChannelRange (Range<int> channelRange);
344
345 /** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
346 int getLegacyModePitchbendRange() const noexcept;
347
348 /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
349 void setLegacyModePitchbendRange (int pitchbendRange);
350
351protected:
352 //==============================================================================
353 CriticalSection lock;
354
355private:
356 //==============================================================================
357 Array<MPENote> notes;
358 MPEZoneLayout zoneLayout;
359 ListenerList<Listener> listeners;
360
361 uint8 lastPressureLowerBitReceivedOnChannel[16];
362 uint8 lastTimbreLowerBitReceivedOnChannel[16];
363 bool isMemberChannelSustained[16];
364
365 struct LegacyMode
366 {
367 bool isEnabled;
368 Range<int> channelRange;
369 int pitchbendRange;
370 };
371
372 struct MPEDimension
373 {
374 TrackingMode trackingMode = lastNotePlayedOnChannel;
375 MPEValue lastValueReceivedOnChannel[16];
376 MPEValue MPENote::* value;
377 MPEValue& getValue (MPENote& note) noexcept { return note.*(value); }
378 };
379
380 LegacyMode legacyMode;
381 MPEDimension pitchbendDimension, pressureDimension, timbreDimension;
382
383 void updateDimension (int midiChannel, MPEDimension&, MPEValue);
384 void updateDimensionMaster (bool, MPEDimension&, MPEValue);
385 void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue);
386 void callListenersDimensionChanged (const MPENote&, const MPEDimension&);
387 MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const;
388
389 void processMidiNoteOnMessage (const MidiMessage&);
390 void processMidiNoteOffMessage (const MidiMessage&);
391 void processMidiPitchWheelMessage (const MidiMessage&);
392 void processMidiChannelPressureMessage (const MidiMessage&);
393 void processMidiControllerMessage (const MidiMessage&);
394 void processMidiResetAllControllersMessage (const MidiMessage&);
395 void processMidiAfterTouchMessage (const MidiMessage&);
396 void handlePressureMSB (int midiChannel, int value) noexcept;
397 void handlePressureLSB (int midiChannel, int value) noexcept;
398 void handleTimbreMSB (int midiChannel, int value) noexcept;
399 void handleTimbreLSB (int midiChannel, int value) noexcept;
400 void handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto);
401
402 const MPENote* getNotePtr (int midiChannel, int midiNoteNumber) const noexcept;
403 MPENote* getNotePtr (int midiChannel, int midiNoteNumber) noexcept;
404 const MPENote* getNotePtr (int midiChannel, TrackingMode) const noexcept;
405 MPENote* getNotePtr (int midiChannel, TrackingMode) noexcept;
406 const MPENote* getLastNotePlayedPtr (int midiChannel) const noexcept;
407 MPENote* getLastNotePlayedPtr (int midiChannel) noexcept;
408 const MPENote* getHighestNotePtr (int midiChannel) const noexcept;
409 MPENote* getHighestNotePtr (int midiChannel) noexcept;
410 const MPENote* getLowestNotePtr (int midiChannel) const noexcept;
411 MPENote* getLowestNotePtr (int midiChannel) noexcept;
412 void updateNoteTotalPitchbend (MPENote&);
413
414 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEInstrument)
415};
416
417} // namespace juce
418
419/** @}*/
Holds a resizable array of primitive or copy-by-value objects.
Definition: juce_Array.h:60
A re-entrant mutex.
Holds a set of objects and can invoke a member function callback on each object in the set with a sin...
Derive from this class to be informed about any changes in the expressive MIDI notes played by this i...
virtual void notePitchbendChanged(MPENote changedNote)
Implement this callback to be informed whenever a currently playing MPE note's pitchbend value change...
virtual void noteAdded(MPENote newNote)
Implement this callback to be informed whenever a new expressive MIDI note is triggered.
virtual void notePressureChanged(MPENote changedNote)
Implement this callback to be informed whenever a currently playing MPE note's pressure value changes...
virtual void noteTimbreChanged(MPENote changedNote)
Implement this callback to be informed whenever a currently playing MPE note's timbre value changes.
virtual void noteReleased(MPENote finishedNote)
Implement this callback to be informed whenever an MPE note is released (either by a note-off message...
virtual ~Listener()=default
Destructor.
virtual void noteKeyStateChanged(MPENote changedNote)
Implement this callback to be informed whether a currently playing MPE note's key state (whether the ...
This class represents an instrument handling MPE.
TrackingMode
The MPE note tracking mode.
@ highestNoteOnChannel
The highest note (by initialNote) on the channel with the note key still down.
@ lowestNoteOnChannel
The lowest note (by initialNote) on the channel with the note key still down.
@ lastNotePlayedOnChannel
The most recent note on the channel that is still played (key down and/or sustained).
This class represents a single value for any of the MPE dimensions of control.
Definition: juce_MPEValue.h:41
This class represents the current MPE zone layout of a device capable of handling MPE.
Encapsulates a MIDI message.
#define JUCE_API
This macro is added to all JUCE public class declarations.
This struct represents a playing MPE note.
Definition: juce_MPENote.h:44