OpenShot Audio Library | OpenShotAudio 0.4.0
Loading...
Searching...
No Matches
juce_MPESynthesiser.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
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
29
34
38
39//==============================================================================
41{
42 jassert (voice != nullptr);
43
44 voice->currentlyPlayingNote = noteToStart;
45 voice->noteOnTime = lastNoteOnCounter++;
46 voice->noteStarted();
47}
48
50{
51 jassert (voice != nullptr);
52
53 voice->currentlyPlayingNote = noteToStop;
54 voice->noteStopped (allowTailOff);
55}
56
57//==============================================================================
59{
60 const ScopedLock sl (voicesLock);
61
62 if (auto* voice = findFreeVoice (newNote, shouldStealVoices))
64}
65
67{
68 const ScopedLock sl (voicesLock);
69
70 for (auto* voice : voices)
71 {
72 if (voice->isCurrentlyPlayingNote (changedNote))
73 {
74 voice->currentlyPlayingNote = changedNote;
75 voice->notePressureChanged();
76 }
77 }
78}
79
81{
82 const ScopedLock sl (voicesLock);
83
84 for (auto* voice : voices)
85 {
86 if (voice->isCurrentlyPlayingNote (changedNote))
87 {
88 voice->currentlyPlayingNote = changedNote;
89 voice->notePitchbendChanged();
90 }
91 }
92}
93
95{
96 const ScopedLock sl (voicesLock);
97
98 for (auto* voice : voices)
99 {
100 if (voice->isCurrentlyPlayingNote (changedNote))
101 {
102 voice->currentlyPlayingNote = changedNote;
103 voice->noteTimbreChanged();
104 }
105 }
106}
107
109{
110 const ScopedLock sl (voicesLock);
111
112 for (auto* voice : voices)
113 {
114 if (voice->isCurrentlyPlayingNote (changedNote))
115 {
116 voice->currentlyPlayingNote = changedNote;
117 voice->noteKeyStateChanged();
118 }
119 }
120}
121
123{
124 const ScopedLock sl (voicesLock);
125
126 for (auto i = voices.size(); --i >= 0;)
127 {
128 auto* voice = voices.getUnchecked (i);
129
130 if (voice->isCurrentlyPlayingNote (finishedNote))
132 }
133}
134
136{
138
139 const ScopedLock sl (voicesLock);
140
141 turnOffAllVoices (false);
142
143 for (auto i = voices.size(); --i >= 0;)
144 voices.getUnchecked (i)->setCurrentSampleRate (newRate);
145}
146
156
158{
159 const ScopedLock sl (voicesLock);
160
161 for (auto* voice : voices)
162 {
163 if (! voice->isActive())
164 return voice;
165 }
166
169
170 return nullptr;
171}
172
174{
175 // This voice-stealing algorithm applies the following heuristics:
176 // - Re-use the oldest notes first
177 // - Protect the lowest & topmost notes, even if sustained, but not if they've been released.
178
179
180 // apparently you are trying to render audio without having any voices...
181 jassert (voices.size() > 0);
182
183 // These are the voices we want to protect (ie: only steal if unavoidable)
184 MPESynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase
185 MPESynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase
186
187 // All major OSes use double-locking so this will be lock- and wait-free as long as stealLock is not
188 // contended. This is always the case if you do not call findVoiceToSteal on multiple threads at
189 // the same time.
190 const ScopedLock sl (stealLock);
191
192 // this is a list of voices we can steal, sorted by how long they've been running
193 usableVoicesToStealArray.clear();
194
195 for (auto* voice : voices)
196 {
197 jassert (voice->isActive()); // We wouldn't be here otherwise
198
199 usableVoicesToStealArray.add (voice);
200
201 // NB: Using a functor rather than a lambda here due to scare-stories about
202 // compilers generating code containing heap allocations..
203 struct Sorter
204 {
205 bool operator() (const MPESynthesiserVoice* a, const MPESynthesiserVoice* b) const noexcept { return a->noteOnTime < b->noteOnTime; }
206 };
207
208 std::sort (usableVoicesToStealArray.begin(), usableVoicesToStealArray.end(), Sorter());
209
210 if (! voice->isPlayingButReleased()) // Don't protect released notes
211 {
212 auto noteNumber = voice->getCurrentlyPlayingNote().initialNote;
213
214 if (low == nullptr || noteNumber < low->getCurrentlyPlayingNote().initialNote)
215 low = voice;
216
217 if (top == nullptr || noteNumber > top->getCurrentlyPlayingNote().initialNote)
218 top = voice;
219 }
220 }
221
222 // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s)
223 if (top == low)
224 top = nullptr;
225
226 // If we want to re-use the voice to trigger a new note,
227 // then The oldest note that's playing the same note number is ideal.
228 if (noteToStealVoiceFor.isValid())
229 for (auto* voice : usableVoicesToStealArray)
230 if (voice->getCurrentlyPlayingNote().initialNote == noteToStealVoiceFor.initialNote)
231 return voice;
232
233 // Oldest voice that has been released (no finger on it and not held by sustain pedal)
234 for (auto* voice : usableVoicesToStealArray)
235 if (voice != low && voice != top && voice->isPlayingButReleased())
236 return voice;
237
238 // Oldest voice that doesn't have a finger on it:
239 for (auto* voice : usableVoicesToStealArray)
240 if (voice != low && voice != top
241 && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown
242 && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained)
243 return voice;
244
245 // Oldest voice that isn't protected
246 for (auto* voice : usableVoicesToStealArray)
247 if (voice != low && voice != top)
248 return voice;
249
250 // We've only got "protected" voices now: lowest note takes priority
251 jassert (low != nullptr);
252
253 // Duophonic synth: give priority to the bass note:
254 if (top != nullptr)
255 return top;
256
257 return low;
258}
259
260//==============================================================================
262{
263 {
264 const ScopedLock sl (voicesLock);
265 newVoice->setCurrentSampleRate (getSampleRate());
266 voices.add (newVoice);
267 }
268
269 {
270 const ScopedLock sl (stealLock);
271 usableVoicesToStealArray.ensureStorageAllocated (voices.size() + 1);
272 }
273}
274
276{
277 const ScopedLock sl (voicesLock);
278 voices.clear();
279}
280
282{
283 const ScopedLock sl (voicesLock);
284 return voices [index];
285}
286
287void MPESynthesiser::removeVoice (const int index)
288{
289 const ScopedLock sl (voicesLock);
290 voices.remove (index);
291}
292
294{
295 // we can't possibly get to a negative number of voices...
296 jassert (newNumVoices >= 0);
297
298 const ScopedLock sl (voicesLock);
299
300 while (voices.size() > newNumVoices)
301 {
302 if (auto* voice = findFreeVoice ({}, true))
303 voices.removeObject (voice);
304 else
305 voices.remove (0); // if there's no voice to steal, kill the oldest voice
306 }
307}
308
310{
311 {
312 const ScopedLock sl (voicesLock);
313
314 // first turn off all voices (it's more efficient to do this immediately
315 // rather than to go through the MPEInstrument for this).
316 for (auto* voice : voices)
317 {
318 voice->currentlyPlayingNote.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number
319 voice->currentlyPlayingNote.keyState = MPENote::off;
320
321 voice->noteStopped (allowTailOff);
322 }
323 }
324
325 // finally make sure the MPE Instrument also doesn't have any notes anymore.
327}
328
329//==============================================================================
330void MPESynthesiser::renderNextSubBlock (AudioBuffer<float>& buffer, int startSample, int numSamples)
331{
332 const ScopedLock sl (voicesLock);
333
334 for (auto* voice : voices)
335 {
336 if (voice->isActive())
337 voice->renderNextBlock (buffer, startSample, numSamples);
338 }
339}
340
341void MPESynthesiser::renderNextSubBlock (AudioBuffer<double>& buffer, int startSample, int numSamples)
342{
343 const ScopedLock sl (voicesLock);
344
345 for (auto* voice : voices)
346 {
347 if (voice->isActive())
348 voice->renderNextBlock (buffer, startSample, numSamples);
349 }
350}
351
352} // namespace juce
void reduceNumVoices(int newNumVoices)
virtual MPESynthesiserVoice * findFreeVoice(MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const
void stopVoice(MPESynthesiserVoice *voice, MPENote noteToStop, bool allowTailOff)
void setCurrentPlaybackSampleRate(double newRate) override
void startVoice(MPESynthesiserVoice *voice, MPENote noteToStart)
void notePressureChanged(MPENote changedNote) override
void noteReleased(MPENote finishedNote) override
void addVoice(MPESynthesiserVoice *newVoice)
virtual MPESynthesiserVoice * findVoiceToSteal(MPENote noteToStealVoiceFor=MPENote()) const
void noteTimbreChanged(MPENote changedNote) override
void noteAdded(MPENote newNote) override
MPESynthesiserVoice * getVoice(int index) const
void noteKeyStateChanged(MPENote changedNote) override
virtual void turnOffAllVoices(bool allowTailOff)
virtual void handleProgramChange(int, int)
void renderNextSubBlock(AudioBuffer< float > &outputAudio, int startSample, int numSamples) override
void notePitchbendChanged(MPENote changedNote) override
void handleMidiEvent(const MidiMessage &) override
virtual void handleController(int, int, int)
static MPEValue from7BitInt(int value) noexcept
int getChannel() const noexcept
bool isProgramChange() const noexcept
bool isController() const noexcept
int getControllerNumber() const noexcept
int getProgramChangeNumber() const noexcept
int getControllerValue() const noexcept
virtual void handleMidiEvent(const MidiMessage &)
virtual void setCurrentPlaybackSampleRate(double sampleRate)
double getSampleRate() const noexcept