OpenShot Library | libopenshot-audio 0.2.0
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 maintans 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) 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 //==============================================================================
107 /** The MPE note tracking mode. In case there is more than one note playing
108 simultaneously on the same MIDI channel, this determines which of these
109 notes will be modulated by an incoming MPE message on that channel
110 (pressure, pitchbend, or timbre).
111
112 The default is lastNotePlayedOnChannel.
113 */
115 {
116 lastNotePlayedOnChannel, /**< The most recent note on the channel that is still played (key down and/or sustained). */
117 lowestNoteOnChannel, /**< The lowest note (by initialNote) on the channel with the note key still down. */
118 highestNoteOnChannel, /**< The highest note (by initialNote) on the channel with the note key still down. */
119 allNotesOnChannel /**< All notes on the channel (key down and/or sustained). */
120 };
121
122 /** Set the MPE tracking mode for the pressure dimension. */
123 void setPressureTrackingMode (TrackingMode modeToUse);
124
125 /** Set the MPE tracking mode for the pitchbend dimension. */
126 void setPitchbendTrackingMode (TrackingMode modeToUse);
127
128 /** Set the MPE tracking mode for the timbre dimension. */
129 void setTimbreTrackingMode (TrackingMode modeToUse);
130
131 //==============================================================================
132 /** Process a MIDI message and trigger the appropriate method calls
133 (noteOn, noteOff etc.)
134
135 You can override this method if you need some special MIDI message
136 treatment on top of the standard MPE logic implemented here.
137 */
138 virtual void processNextMidiEvent (const MidiMessage& message);
139
140 //==============================================================================
141 /** Request a note-on on the given channel, with the given initial note
142 number and velocity.
143
144 If the message arrives on a valid note channel, this will create a
145 new MPENote and call the noteAdded callback.
146 */
147 virtual void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity);
148
149 /** Request a note-off.
150
151 If there is a matching playing note, this will release the note
152 (except if it is sustained by a sustain or sostenuto pedal) and call
153 the noteReleased callback.
154 */
155 virtual void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity);
156
157 /** Request a pitchbend on the given channel with the given value (in units
158 of MIDI pitchwheel position).
159
160 Internally, this will determine whether the pitchwheel move is a
161 per-note pitchbend or a master pitchbend (depending on midiChannel),
162 take the correct per-note or master pitchbend range of the affected MPE
163 zone, and apply the resulting pitchbend to the affected note(s) (if any).
164 */
165 virtual void pitchbend (int midiChannel, MPEValue pitchbend);
166
167 /** Request a pressure change on the given channel with the given value.
168
169 This will modify the pressure dimension of the note currently held down
170 on this channel (if any). If the channel is a zone master channel,
171 the pressure change will be broadcast to all notes in this zone.
172 */
173 virtual void pressure (int midiChannel, MPEValue value);
174
175 /** Request a third dimension (timbre) change on the given channel with the
176 given value.
177
178 This will modify the timbre dimension of the note currently held down
179 on this channel (if any). If the channel is a zone master channel,
180 the timbre change will be broadcast to all notes in this zone.
181 */
182 virtual void timbre (int midiChannel, MPEValue value);
183
184 /** Request a sustain pedal press or release.
185
186 If midiChannel is a zone's master channel, this will act on all notes in
187 that zone; otherwise, nothing will happen.
188 */
189 virtual void sustainPedal (int midiChannel, bool isDown);
190
191 /** Request a sostenuto pedal press or release.
192
193 If midiChannel is a zone's master channel, this will act on all notes in
194 that zone; otherwise, nothing will happen.
195 */
196 virtual void sostenutoPedal (int midiChannel, bool isDown);
197
198 /** Discard all currently playing notes.
199
200 This will also call the noteReleased listener callback for all of them.
201 */
202 void releaseAllNotes();
203
204 //==============================================================================
205 /** Returns the number of MPE notes currently played by the instrument. */
206 int getNumPlayingNotes() const noexcept;
207
208 /** Returns the note at the given index.
209
210 If there is no such note, returns an invalid MPENote. The notes are sorted
211 such that the most recently added note is the last element.
212 */
213 MPENote getNote (int index) const noexcept;
214
215 /** Returns the note currently playing on the given midiChannel with the
216 specified initial MIDI note number, if there is such a note. Otherwise,
217 this returns an invalid MPENote (check with note.isValid() before use!)
218 */
219 MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept;
220
221 /** Returns the most recent note that is playing on the given midiChannel
222 (this will be the note which has received the most recent note-on without
223 a corresponding note-off), if there is such a note. Otherwise, this returns an
224 invalid MPENote (check with note.isValid() before use!)
225 */
226 MPENote getMostRecentNote (int midiChannel) const noexcept;
227
228 /** Returns the most recent note that is not the note passed in. If there is no
229 such note, this returns an invalid MPENote (check with note.isValid() before use!).
230
231 This helper method might be useful for some custom voice handling algorithms.
232 */
233 MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept;
234
235 //==============================================================================
236 /** Derive from this class to be informed about any changes in the expressive
237 MIDI notes played by this instrument.
238
239 Note: This listener type receives its callbacks immediately, and not
240 via the message thread (so you might be for example in the MIDI thread).
241 Therefore you should never do heavy work such as graphics rendering etc.
242 inside those callbacks.
243 */
245 {
246 public:
247 /** Destructor. */
248 virtual ~Listener() = default;
249
250 /** Implement this callback to be informed whenever a new expressive MIDI
251 note is triggered.
252 */
253 virtual void noteAdded (MPENote newNote) = 0;
254
255 /** Implement this callback to be informed whenever a currently playing
256 MPE note's pressure value changes.
257 */
258 virtual void notePressureChanged (MPENote changedNote) = 0;
259
260 /** Implement this callback to be informed whenever a currently playing
261 MPE note's pitchbend value changes.
262
263 Note: This can happen if the note itself is bent, if there is a
264 master channel pitchbend event, or if both occur simultaneously.
265 Call MPENote::getFrequencyInHertz to get the effective note frequency.
266 */
267 virtual void notePitchbendChanged (MPENote changedNote) = 0;
268
269 /** Implement this callback to be informed whenever a currently playing
270 MPE note's timbre value changes.
271 */
272 virtual void noteTimbreChanged (MPENote changedNote) = 0;
273
274 /** Implement this callback to be informed whether a currently playing
275 MPE note's key state (whether the key is down and/or the note is
276 sustained) has changed.
277
278 Note: If the key state changes to MPENote::off, noteReleased is
279 called instead.
280 */
281 virtual void noteKeyStateChanged (MPENote changedNote) = 0;
282
283 /** Implement this callback to be informed whenever an MPE note
284 is released (either by a note-off message, or by a sustain/sostenuto
285 pedal release for a note that already received a note-off),
286 and should therefore stop playing.
287 */
288 virtual void noteReleased (MPENote finishedNote) = 0;
289 };
290
291 //==============================================================================
292 /** Adds a listener. */
293 void addListener (Listener* listenerToAdd);
294
295 /** Removes a listener. */
296 void removeListener (Listener* listenerToRemove);
297
298 //==============================================================================
299 /** Puts the instrument into legacy mode.
300 As a side effect, this will discard all currently playing notes,
301 and call noteReleased for all of them.
302
303 This special zone layout mode is for backwards compatibility with
304 non-MPE MIDI devices. In this mode, the instrument will ignore the
305 current MPE zone layout. It will instead take a range of MIDI channels
306 (default: all channels 1-16) and treat them as note channels, with no
307 master channel. MIDI channels outside of this range will be ignored.
308
309 @param pitchbendRange The note pitchbend range in semitones to use when in legacy mode.
310 Must be between 0 and 96, otherwise behaviour is undefined.
311 The default pitchbend range in legacy mode is +/- 2 semitones.
312
313 @param channelRange The range of MIDI channels to use for notes when in legacy mode.
314 The default is to use all MIDI channels (1-16).
315
316 To get out of legacy mode, set a new MPE zone layout using setZoneLayout.
317 */
318 void enableLegacyMode (int pitchbendRange = 2,
319 Range<int> channelRange = Range<int> (1, 17));
320
321 /** Returns true if the instrument is in legacy mode, false otherwise. */
322 bool isLegacyModeEnabled() const noexcept;
323
324 /** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
325 Range<int> getLegacyModeChannelRange() const noexcept;
326
327 /** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
328 void setLegacyModeChannelRange (Range<int> channelRange);
329
330 /** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
331 int getLegacyModePitchbendRange() const noexcept;
332
333 /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
334 void setLegacyModePitchbendRange (int pitchbendRange);
335
336protected:
337 //==============================================================================
338 CriticalSection lock;
339
340private:
341 //==============================================================================
342 Array<MPENote> notes;
343 MPEZoneLayout zoneLayout;
344 ListenerList<Listener> listeners;
345
346 uint8 lastPressureLowerBitReceivedOnChannel[16];
347 uint8 lastTimbreLowerBitReceivedOnChannel[16];
348 bool isMemberChannelSustained[16];
349
350 struct LegacyMode
351 {
352 bool isEnabled;
353 Range<int> channelRange;
354 int pitchbendRange;
355 };
356
357 struct MPEDimension
358 {
359 TrackingMode trackingMode = lastNotePlayedOnChannel;
360 MPEValue lastValueReceivedOnChannel[16];
361 MPEValue MPENote::* value;
362 MPEValue& getValue (MPENote& note) noexcept { return note.*(value); }
363 };
364
365 LegacyMode legacyMode;
366 MPEDimension pitchbendDimension, pressureDimension, timbreDimension;
367
368 void updateDimension (int midiChannel, MPEDimension&, MPEValue);
369 void updateDimensionMaster (bool, MPEDimension&, MPEValue);
370 void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue);
371 void callListenersDimensionChanged (const MPENote&, const MPEDimension&);
372 MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const;
373
374 void processMidiNoteOnMessage (const MidiMessage&);
375 void processMidiNoteOffMessage (const MidiMessage&);
376 void processMidiPitchWheelMessage (const MidiMessage&);
377 void processMidiChannelPressureMessage (const MidiMessage&);
378 void processMidiControllerMessage (const MidiMessage&);
379 void processMidiResetAllControllersMessage (const MidiMessage&);
380 void handlePressureMSB (int midiChannel, int value) noexcept;
381 void handlePressureLSB (int midiChannel, int value) noexcept;
382 void handleTimbreMSB (int midiChannel, int value) noexcept;
383 void handleTimbreLSB (int midiChannel, int value) noexcept;
384 void handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto);
385
386 const MPENote* getNotePtr (int midiChannel, int midiNoteNumber) const noexcept;
387 MPENote* getNotePtr (int midiChannel, int midiNoteNumber) noexcept;
388 const MPENote* getNotePtr (int midiChannel, TrackingMode) const noexcept;
389 MPENote* getNotePtr (int midiChannel, TrackingMode) noexcept;
390 const MPENote* getLastNotePlayedPtr (int midiChannel) const noexcept;
391 MPENote* getLastNotePlayedPtr (int midiChannel) noexcept;
392 const MPENote* getHighestNotePtr (int midiChannel) const noexcept;
393 MPENote* getHighestNotePtr (int midiChannel) noexcept;
394 const MPENote* getLowestNotePtr (int midiChannel) const noexcept;
395 MPENote* getLowestNotePtr (int midiChannel) noexcept;
396 void updateNoteTotalPitchbend (MPENote&);
397
398 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEInstrument)
399};
400
401} // namespace juce
402
403/** @}*/
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:60
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)=0
Implement this callback to be informed whenever a currently playing MPE note's pitchbend value change...
virtual void notePressureChanged(MPENote changedNote)=0
Implement this callback to be informed whenever a currently playing MPE note's pressure value changes...
virtual void noteTimbreChanged(MPENote changedNote)=0
Implement this callback to be informed whenever a currently playing MPE note's timbre value changes.
virtual void noteKeyStateChanged(MPENote changedNote)=0
Implement this callback to be informed whether a currently playing MPE note's key state (whether the ...
virtual void noteReleased(MPENote finishedNote)=0
Implement this callback to be informed whenever an MPE note is released (either by a note-off message...
virtual ~Listener()=default
Destructor.
virtual void noteAdded(MPENote newNote)=0
Implement this callback to be informed whenever a new expressive MIDI note is triggered.
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.
This class represents the current MPE zone layout of a device capable of handling MPE.
Encapsulates a MIDI message.
A general-purpose range object, that simply represents any linear range with a start and end point.
Definition juce_Range.h:44
#define JUCE_API
This macro is added to all JUCE public class declarations.
This struct represents a playing MPE note.