OpenShot Library | libopenshot-audio 0.2.0
juce_MPESynthesiser.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2017 - ROLI Ltd.
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
23namespace juce
24{
25
27{
28 MPEZoneLayout zoneLayout;
29 zoneLayout.setLowerZone (15);
30 setZoneLayout (zoneLayout);
31}
32
36
40
41//==============================================================================
43{
44 jassert (voice != nullptr);
45
46 voice->currentlyPlayingNote = noteToStart;
47 voice->noteOnTime = lastNoteOnCounter++;
48 voice->noteStarted();
49}
50
52{
53 jassert (voice != nullptr);
54
55 voice->currentlyPlayingNote = noteToStop;
56 voice->noteStopped (allowTailOff);
57}
58
59//==============================================================================
61{
62 const ScopedLock sl (voicesLock);
63
64 if (auto* voice = findFreeVoice (newNote, shouldStealVoices))
66}
67
69{
70 const ScopedLock sl (voicesLock);
71
72 for (auto* voice : voices)
73 {
74 if (voice->isCurrentlyPlayingNote (changedNote))
75 {
76 voice->currentlyPlayingNote = changedNote;
77 voice->notePressureChanged();
78 }
79 }
80}
81
83{
84 const ScopedLock sl (voicesLock);
85
86 for (auto* voice : voices)
87 {
88 if (voice->isCurrentlyPlayingNote (changedNote))
89 {
90 voice->currentlyPlayingNote = changedNote;
91 voice->notePitchbendChanged();
92 }
93 }
94}
95
97{
98 const ScopedLock sl (voicesLock);
99
100 for (auto* voice : voices)
101 {
102 if (voice->isCurrentlyPlayingNote (changedNote))
103 {
104 voice->currentlyPlayingNote = changedNote;
105 voice->noteTimbreChanged();
106 }
107 }
108}
109
111{
112 const ScopedLock sl (voicesLock);
113
114 for (auto* voice : voices)
115 {
116 if (voice->isCurrentlyPlayingNote (changedNote))
117 {
118 voice->currentlyPlayingNote = changedNote;
119 voice->noteKeyStateChanged();
120 }
121 }
122}
123
125{
126 const ScopedLock sl (voicesLock);
127
128 for (auto i = voices.size(); --i >= 0;)
129 {
130 auto* voice = voices.getUnchecked (i);
131
132 if (voice->isCurrentlyPlayingNote(finishedNote))
134 }
135}
136
138{
140
141 const ScopedLock sl (voicesLock);
142
143 turnOffAllVoices (false);
144
145 for (auto i = voices.size(); --i >= 0;)
146 voices.getUnchecked (i)->setCurrentSampleRate (newRate);
147}
148
150{
151 if (m.isController())
152 handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue());
153 else if (m.isProgramChange())
154 handleProgramChange (m.getChannel(), m.getProgramChangeNumber());
155
157}
158
160{
161 const ScopedLock sl (voicesLock);
162
163 for (auto* voice : voices)
164 {
165 if (! voice->isActive())
166 return voice;
167 }
168
171
172 return nullptr;
173}
174
176{
177 // This voice-stealing algorithm applies the following heuristics:
178 // - Re-use the oldest notes first
179 // - Protect the lowest & topmost notes, even if sustained, but not if they've been released.
180
181
182 // apparently you are trying to render audio without having any voices...
183 jassert (voices.size() > 0);
184
185 // These are the voices we want to protect (ie: only steal if unavoidable)
186 MPESynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase
187 MPESynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase
188
189 // this is a list of voices we can steal, sorted by how long they've been running
192
193 for (auto* voice : voices)
194 {
195 jassert (voice->isActive()); // We wouldn't be here otherwise
196
198
199 // NB: Using a functor rather than a lambda here due to scare-stories about
200 // compilers generating code containing heap allocations..
201 struct Sorter
202 {
203 bool operator() (const MPESynthesiserVoice* a, const MPESynthesiserVoice* b) const noexcept { return a->noteOnTime < b->noteOnTime; }
204 };
205
206 std::sort (usableVoices.begin(), usableVoices.end(), Sorter());
207
208 if (! voice->isPlayingButReleased()) // Don't protect released notes
209 {
210 auto noteNumber = voice->getCurrentlyPlayingNote().initialNote;
211
212 if (low == nullptr || noteNumber < low->getCurrentlyPlayingNote().initialNote)
213 low = voice;
214
215 if (top == nullptr || noteNumber > top->getCurrentlyPlayingNote().initialNote)
216 top = voice;
217 }
218 }
219
220 // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s)
221 if (top == low)
222 top = nullptr;
223
224 // If we want to re-use the voice to trigger a new note,
225 // then The oldest note that's playing the same note number is ideal.
226 if (noteToStealVoiceFor.isValid())
227 for (auto* voice : usableVoices)
228 if (voice->getCurrentlyPlayingNote().initialNote == noteToStealVoiceFor.initialNote)
229 return voice;
230
231 // Oldest voice that has been released (no finger on it and not held by sustain pedal)
232 for (auto* voice : usableVoices)
233 if (voice != low && voice != top && voice->isPlayingButReleased())
234 return voice;
235
236 // Oldest voice that doesn't have a finger on it:
237 for (auto* voice : usableVoices)
238 if (voice != low && voice != top
239 && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown
240 && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained)
241 return voice;
242
243 // Oldest voice that isn't protected
244 for (auto* voice : usableVoices)
245 if (voice != low && voice != top)
246 return voice;
247
248 // We've only got "protected" voices now: lowest note takes priority
249 jassert (low != nullptr);
250
251 // Duophonic synth: give priority to the bass note:
252 if (top != nullptr)
253 return top;
254
255 return low;
256}
257
258//==============================================================================
260{
261 const ScopedLock sl (voicesLock);
262 newVoice->setCurrentSampleRate (getSampleRate());
263 voices.add (newVoice);
264}
265
267{
268 const ScopedLock sl (voicesLock);
269 voices.clear();
270}
271
273{
274 const ScopedLock sl (voicesLock);
275 return voices [index];
276}
277
278void MPESynthesiser::removeVoice (const int index)
279{
280 const ScopedLock sl (voicesLock);
281 voices.remove (index);
282}
283
285{
286 // we can't possibly get to a negative number of voices...
287 jassert (newNumVoices >= 0);
288
289 const ScopedLock sl (voicesLock);
290
291 while (voices.size() > newNumVoices)
292 {
293 if (auto* voice = findFreeVoice ({}, true))
294 voices.removeObject (voice);
295 else
296 voices.remove (0); // if there's no voice to steal, kill the oldest voice
297 }
298}
299
301{
302 // first turn off all voices (it's more efficient to do this immediately
303 // rather than to go through the MPEInstrument for this).
304 for (auto* voice : voices)
305 voice->noteStopped (allowTailOff);
306
307 // finally make sure the MPE Instrument also doesn't have any notes anymore.
308 instrument->releaseAllNotes();
309}
310
311//==============================================================================
312void MPESynthesiser::renderNextSubBlock (AudioBuffer<float>& buffer, int startSample, int numSamples)
313{
314 for (auto* voice : voices)
315 {
316 if (voice->isActive())
317 voice->renderNextBlock (buffer, startSample, numSamples);
318 }
319}
320
321void MPESynthesiser::renderNextSubBlock (AudioBuffer<double>& buffer, int startSample, int numSamples)
322{
323 for (auto* voice : voices)
324 {
325 if (voice->isActive())
326 voice->renderNextBlock (buffer, startSample, numSamples);
327 }
328}
329
330} // namespace juce
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:60
ElementType getUnchecked(int index) const
Returns one of the elements in the array, without checking the index passed in.
Definition juce_Array.h:256
void ensureStorageAllocated(int minNumElements)
Increases the array's internal storage to hold a minimum number of elements.
int size() const noexcept
Returns the current number of elements in the array.
Definition juce_Array.h:219
ElementType * begin() const noexcept
Returns a pointer to the first element in the array.
Definition juce_Array.h:309
void remove(int indexToRemove)
Removes an element from the array.
Definition juce_Array.h:724
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:375
void clear()
Removes all elements from the array.
Definition juce_Array.h:192
ElementType * end() const noexcept
Returns a pointer to the element which follows the last element in the array.
Definition juce_Array.h:317
This class represents an instrument handling MPE.
Represents an MPE voice that an MPESynthesiser can use to play a sound.
void removeVoice(int index)
Deletes one of the voices.
void reduceNumVoices(int newNumVoices)
Reduces the number of voices to newNumVoices.
virtual MPESynthesiserVoice * findFreeVoice(MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const
Searches through the voices to find one that's not currently playing, and which can play the given MP...
void stopVoice(MPESynthesiserVoice *voice, MPENote noteToStop, bool allowTailOff)
Stops a given voice and tells it to stop playing a particular MPENote (which should be the same note ...
void setCurrentPlaybackSampleRate(double newRate) override
Tells the synthesiser what the sample rate is for the audio it's being used to render.
void startVoice(MPESynthesiserVoice *voice, MPENote noteToStart)
Starts a specified voice and tells it to play a particular MPENote.
void notePressureChanged(MPENote changedNote) override
Will find any voice that is currently playing changedNote, update its currently playing note,...
void noteReleased(MPENote finishedNote) override
Stops playing a note.
void clearVoices()
Deletes all voices.
void addVoice(MPESynthesiserVoice *newVoice)
Adds a new voice to the synth.
~MPESynthesiser() override
Destructor.
virtual MPESynthesiserVoice * findVoiceToSteal(MPENote noteToStealVoiceFor=MPENote()) const
Chooses a voice that is most suitable for being re-used to play a new note, or for being deleted by r...
void noteTimbreChanged(MPENote changedNote) override
Will find any voice that is currently playing changedNote, update its currently playing note,...
void noteAdded(MPENote newNote) override
Attempts to start playing a new note.
MPESynthesiserVoice * getVoice(int index) const
Returns one of the voices that have been added.
void noteKeyStateChanged(MPENote changedNote) override
Will find any voice that is currently playing changedNote, update its currently playing note,...
virtual void turnOffAllVoices(bool allowTailOff)
Release all MPE notes and turn off all voices.
virtual void handleProgramChange(int, int)
Callback for MIDI program change messages.
void renderNextSubBlock(AudioBuffer< float > &outputAudio, int startSample, int numSamples) override
This will simply call renderNextBlock for each currently active voice and fill the buffer with the su...
void notePitchbendChanged(MPENote changedNote) override
Will find any voice that is currently playing changedNote, update its currently playing note,...
void handleMidiEvent(const MidiMessage &) override
Handle incoming MIDI events.
virtual void handleController(int, int, int)
Callback for MIDI controller messages.
This class represents the current MPE zone layout of a device capable of handling MPE.
void setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2) noexcept
Sets the lower zone of this layout.
Encapsulates a MIDI message.
This struct represents a playing MPE note.
@ keyDown
The note key is currently down (pressed).
@ keyDownAndSustained
The note key is down and sustained (by a sustain or sostenuto pedal).
Derive from this class to create a basic audio generator capable of MPE.
virtual void handleMidiEvent(const MidiMessage &)
Handle incoming MIDI events (called from renderNextBlock).
void setZoneLayout(MPEZoneLayout newLayout)
Re-sets the synthesiser's internal MPE zone layout to the one passed in.
std::unique_ptr< MPEInstrument > instrument
virtual void setCurrentPlaybackSampleRate(double sampleRate)
Tells the synthesiser what the sample rate is for the audio it's being used to render.
double getSampleRate() const noexcept
Returns the current target sample rate at which rendering is being done.