JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_MPESynthesiser.cpp
Go to the documentation of this file.
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
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.
326 instrument.releaseAllNotes();
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
A multi-channel buffer containing floating point audio samples.
Automatically locks and unlocks a mutex object.
This class represents an instrument handling MPE.
void releaseAllNotes()
Discard all currently playing notes.
Represents an MPE voice that an MPESynthesiser can use to play a sound.
MPENote getCurrentlyPlayingNote() const noexcept
Returns the MPENote that this voice is currently playing.
uint32 noteOnTime
This will be set to an incrementing counter value in MPESynthesiser::startVoice() and can be used to ...
bool isPlayingButReleased() const noexcept
Returns true if a voice is sounding in its release phase.
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.
static MPEValue from7BitInt(int value) noexcept
Constructs an MPEValue from an integer between 0 and 127 (using 7-bit precision).
Encapsulates a MIDI message.
int getChannel() const noexcept
Returns the midi channel associated with the message.
bool isProgramChange() const noexcept
Returns true if the message is a program (patch) change message.
bool isController() const noexcept
Returns true if this is a midi controller message.
int getControllerNumber() const noexcept
Returns the controller number of a controller message.
int getProgramChangeNumber() const noexcept
Returns the new program number of a program change message.
int getControllerValue() const noexcept
Returns the controller value from a controller message.
#define jassert(expression)
Platform-independent assertion macro.
JUCE Namespace.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
T sort(T... args)
This struct represents a playing MPE note.
@ keyDown
The note key is currently down (pressed).
@ off
The key is up (off).
@ keyDownAndSustained
The note key is down and sustained (by a sustain or sostenuto pedal).
uint8 initialNote
The MIDI note number that was sent when the note was triggered.
KeyState keyState
Current key state.
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).
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.