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 
27 namespace 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 {
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) 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 
336 protected:
337  //==============================================================================
338  CriticalSection lock;
339 
340 private:
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 /** @}*/
#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.
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...
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.
Holds a resizable array of primitive or copy-by-value objects.
Definition: juce_Array.h:59
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