OpenShot Audio Library | OpenShotAudio  0.6.0
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 
23 namespace juce
24 {
25 
27 {
28 }
29 
31  : MPESynthesiserBase (mpeInstrument)
32 {
33 }
34 
36 {
37 }
38 
39 //==============================================================================
41 {
42  jassert (voice != nullptr);
43 
44  voice->currentlyPlayingNote = noteToStart;
45  voice->noteOnTime = lastNoteOnCounter++;
46  voice->noteStarted();
47 }
48 
49 void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff)
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))
63  startVoice (voice, newNote);
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))
131  stopVoice (voice, finishedNote, true);
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 
148 {
149  if (m.isController())
151  else if (m.isProgramChange())
153 
155 }
156 
157 MPESynthesiserVoice* MPESynthesiser::findFreeVoice (MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const
158 {
159  const ScopedLock sl (voicesLock);
160 
161  for (auto* voice : voices)
162  {
163  if (! voice->isActive())
164  return voice;
165  }
166 
167  if (stealIfNoneAvailable)
168  return findVoiceToSteal (noteToFindVoiceFor);
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
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 
287 void MPESynthesiser::removeVoice (const int index)
288 {
289  const ScopedLock sl (voicesLock);
290  voices.remove (index);
291 }
292 
293 void MPESynthesiser::reduceNumVoices (const int newNumVoices)
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 
309 void MPESynthesiser::turnOffAllVoices (bool allowTailOff)
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 //==============================================================================
330 void 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 
341 void 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
virtual void setCurrentSampleRate(double newRate)
MPENote getCurrentlyPlayingNote() const noexcept
virtual void noteStarted()=0
virtual void noteStopped(bool allowTailOff)=0
bool isPlayingButReleased() const noexcept
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
KeyState keyState
Definition: juce_MPENote.h:169
bool isValid() const noexcept
virtual void handleMidiEvent(const MidiMessage &)
virtual void setCurrentPlaybackSampleRate(double sampleRate)
double getSampleRate() const noexcept