OpenShot Library | OpenShotAudio  0.2.1
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 
27 namespace 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 {
61 public:
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 
351 protected:
352  //==============================================================================
353  CriticalSection lock;
354 
355 private:
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 /** @}*/
#define JUCE_API
This macro is added to all JUCE public class declarations.
This class represents the current MPE zone layout of a device capable of handling MPE...
Encapsulates a MIDI message.
virtual void noteKeyStateChanged(MPENote changedNote)
Implement this callback to be informed whether a currently playing MPE note&#39;s key state (whether the ...
The highest note (by initialNote) on the channel with the note key still down.
The most recent note on the channel that is still played (key down and/or sustained).
Derive from this class to be informed about any changes in the expressive MIDI notes played by this i...
virtual void noteAdded(MPENote newNote)
Implement this callback to be informed whenever a new expressive MIDI note is triggered.
Holds a set of objects and can invoke a member function callback on each object in the set with a sin...
TrackingMode
The MPE note tracking mode.
The lowest note (by initialNote) on the channel with the note key still down.
virtual void noteTimbreChanged(MPENote changedNote)
Implement this callback to be informed whenever a currently playing MPE note&#39;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...
Holds a resizable array of primitive or copy-by-value objects.
Definition: juce_Array.h:59
virtual void notePitchbendChanged(MPENote changedNote)
Implement this callback to be informed whenever a currently playing MPE note&#39;s pitchbend value change...
virtual void notePressureChanged(MPENote changedNote)
Implement this callback to be informed whenever a currently playing MPE note&#39;s pressure value changes...
A re-entrant mutex.
This struct represents a playing MPE note.
Definition: juce_MPENote.h:43
This class represents an instrument handling MPE.
This class represents a single value for any of the MPE dimensions of control.
Definition: juce_MPEValue.h:40