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_Synthesiser.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
26SynthesiserSound::SynthesiserSound() {}
28
29//==============================================================================
32
33bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const
34{
35 return currentPlayingMidiChannel == midiChannel;
36}
37
39{
40 currentSampleRate = newRate;
41}
42
44{
45 return getCurrentlyPlayingNote() >= 0;
46}
47
49{
50 currentlyPlayingNote = -1;
51 currentlyPlayingSound = nullptr;
52 currentPlayingMidiChannel = 0;
53}
54
57
59{
60 return noteOnTime < other.noteOnTime;
61}
62
64 int startSample, int numSamples)
65{
66 AudioBuffer<double> subBuffer (outputBuffer.getArrayOfWritePointers(),
67 outputBuffer.getNumChannels(),
68 startSample, numSamples);
69
70 tempBuffer.makeCopyOf (subBuffer, true);
71 renderNextBlock (tempBuffer, 0, numSamples);
72 subBuffer.makeCopyOf (tempBuffer, true);
73}
74
75//==============================================================================
77{
78 for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i)
79 lastPitchWheelValues[i] = 0x2000;
80}
81
85
86//==============================================================================
88{
89 const ScopedLock sl (lock);
90 return voices [index];
91}
92
94{
95 const ScopedLock sl (lock);
96 voices.clear();
97}
98
100{
102
103 {
104 const ScopedLock sl (lock);
105 newVoice->setCurrentPlaybackSampleRate (sampleRate);
106 voice = voices.add (newVoice);
107 }
108
109 {
110 const ScopedLock sl (stealLock);
111 usableVoicesToStealArray.ensureStorageAllocated (voices.size() + 1);
112 }
113
114 return voice;
115}
116
117void Synthesiser::removeVoice (const int index)
118{
119 const ScopedLock sl (lock);
120 voices.remove (index);
121}
122
124{
125 const ScopedLock sl (lock);
126 sounds.clear();
127}
128
130{
131 const ScopedLock sl (lock);
132 return sounds.add (newSound);
133}
134
135void Synthesiser::removeSound (const int index)
136{
137 const ScopedLock sl (lock);
138 sounds.remove (index);
139}
140
142{
143 shouldStealNotes = shouldSteal;
144}
145
147{
148 jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1
149 minimumSubBlockSize = numSamples;
150 subBlockSubdivisionIsStrict = shouldBeStrict;
151}
152
153//==============================================================================
155{
156 if (! approximatelyEqual (sampleRate, newRate))
157 {
158 const ScopedLock sl (lock);
159 allNotesOff (0, false);
160 sampleRate = newRate;
161
162 for (auto* voice : voices)
163 voice->setCurrentPlaybackSampleRate (newRate);
164 }
165}
166
167template <typename floatType>
168void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio,
169 const MidiBuffer& midiData,
170 int startSample,
171 int numSamples)
172{
173 // must set the sample rate before using this!
174 jassert (! exactlyEqual (sampleRate, 0.0));
175 const int targetChannels = outputAudio.getNumChannels();
176
177 auto midiIterator = midiData.findNextSamplePosition (startSample);
178
179 bool firstEvent = true;
180
181 const ScopedLock sl (lock);
182
183 for (; numSamples > 0; ++midiIterator)
184 {
185 if (midiIterator == midiData.cend())
186 {
187 if (targetChannels > 0)
188 renderVoices (outputAudio, startSample, numSamples);
189
190 return;
192
193 const auto metadata = *midiIterator;
194 const int samplesToNextMidiMessage = metadata.samplePosition - startSample;
195
196 if (samplesToNextMidiMessage >= numSamples)
197 {
198 if (targetChannels > 0)
199 renderVoices (outputAudio, startSample, numSamples);
200
201 handleMidiEvent (metadata.getMessage());
202 break;
203 }
204
205 if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize))
206 {
207 handleMidiEvent (metadata.getMessage());
208 continue;
209 }
210
211 firstEvent = false;
212
213 if (targetChannels > 0)
215
216 handleMidiEvent (metadata.getMessage());
217 startSample += samplesToNextMidiMessage;
218 numSamples -= samplesToNextMidiMessage;
219 }
220
222 midiData.cend(),
223 [&] (const MidiMessageMetadata& meta) { handleMidiEvent (meta.getMessage()); });
224}
225
226// explicit template instantiation
227template void Synthesiser::processNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int);
228template void Synthesiser::processNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int);
229
231 int startSample, int numSamples)
232{
233 processNextBlock (outputAudio, inputMidi, startSample, numSamples);
234}
235
237 int startSample, int numSamples)
238{
239 processNextBlock (outputAudio, inputMidi, startSample, numSamples);
240}
241
242void Synthesiser::renderVoices (AudioBuffer<float>& buffer, int startSample, int numSamples)
243{
244 for (auto* voice : voices)
245 voice->renderNextBlock (buffer, startSample, numSamples);
246}
247
248void Synthesiser::renderVoices (AudioBuffer<double>& buffer, int startSample, int numSamples)
249{
250 for (auto* voice : voices)
251 voice->renderNextBlock (buffer, startSample, numSamples);
252}
253
255{
256 const int channel = m.getChannel();
257
258 if (m.isNoteOn())
259 {
260 noteOn (channel, m.getNoteNumber(), m.getFloatVelocity());
261 }
262 else if (m.isNoteOff())
263 {
264 noteOff (channel, m.getNoteNumber(), m.getFloatVelocity(), true);
265 }
266 else if (m.isAllNotesOff() || m.isAllSoundOff())
267 {
268 allNotesOff (channel, true);
269 }
270 else if (m.isPitchWheel())
271 {
272 const int wheelPos = m.getPitchWheelValue();
273 lastPitchWheelValues [channel - 1] = wheelPos;
274 handlePitchWheel (channel, wheelPos);
275 }
276 else if (m.isAftertouch())
277 {
279 }
280 else if (m.isChannelPressure())
281 {
283 }
284 else if (m.isController())
285 {
287 }
288 else if (m.isProgramChange())
289 {
291 }
292}
293
294//==============================================================================
295void Synthesiser::noteOn (const int midiChannel,
296 const int midiNoteNumber,
297 const float velocity)
298{
299 const ScopedLock sl (lock);
300
301 for (auto* sound : sounds)
302 {
303 if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel))
304 {
305 // If hitting a note that's still ringing, stop it first (it could be
306 // still playing because of the sustain or sostenuto pedal).
307 for (auto* voice : voices)
308 if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel))
309 stopVoice (voice, 1.0f, true);
310
311 startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes),
312 sound, midiChannel, midiNoteNumber, velocity);
313 }
314 }
315}
316
318 SynthesiserSound* const sound,
319 const int midiChannel,
320 const int midiNoteNumber,
321 const float velocity)
322{
323 if (voice != nullptr && sound != nullptr)
324 {
325 if (voice->currentlyPlayingSound != nullptr)
326 voice->stopNote (0.0f, false);
327
328 voice->currentlyPlayingNote = midiNoteNumber;
329 voice->currentPlayingMidiChannel = midiChannel;
330 voice->noteOnTime = ++lastNoteOnCounter;
331 voice->currentlyPlayingSound = sound;
332 voice->setKeyDown (true);
333 voice->setSostenutoPedalDown (false);
334 voice->setSustainPedalDown (sustainPedalsDown[midiChannel]);
335
336 voice->startNote (midiNoteNumber, velocity, sound,
337 lastPitchWheelValues [midiChannel - 1]);
338 }
339}
340
342{
343 jassert (voice != nullptr);
344
345 voice->stopNote (velocity, allowTailOff);
346
347 // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()!
348 jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == nullptr));
349}
350
351void Synthesiser::noteOff (const int midiChannel,
352 const int midiNoteNumber,
353 const float velocity,
354 const bool allowTailOff)
355{
356 const ScopedLock sl (lock);
357
358 for (auto* voice : voices)
359 {
360 if (voice->getCurrentlyPlayingNote() == midiNoteNumber
361 && voice->isPlayingChannel (midiChannel))
362 {
363 if (auto sound = voice->getCurrentlyPlayingSound())
364 {
365 if (sound->appliesToNote (midiNoteNumber)
366 && sound->appliesToChannel (midiChannel))
367 {
368 jassert (! voice->keyIsDown || voice->isSustainPedalDown() == sustainPedalsDown [midiChannel]);
369
370 voice->setKeyDown (false);
371
372 if (! (voice->isSustainPedalDown() || voice->isSostenutoPedalDown()))
373 stopVoice (voice, velocity, allowTailOff);
374 }
375 }
376 }
377 }
378}
379
380void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff)
381{
382 const ScopedLock sl (lock);
383
384 for (auto* voice : voices)
385 if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
386 voice->stopNote (1.0f, allowTailOff);
387
388 sustainPedalsDown.clear();
389}
390
391void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue)
392{
393 const ScopedLock sl (lock);
394
395 for (auto* voice : voices)
396 if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
397 voice->pitchWheelMoved (wheelValue);
398}
399
400void Synthesiser::handleController (const int midiChannel,
401 const int controllerNumber,
402 const int controllerValue)
403{
404 switch (controllerNumber)
405 {
406 case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break;
407 case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break;
408 case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break;
409 default: break;
410 }
411
412 const ScopedLock sl (lock);
413
414 for (auto* voice : voices)
415 if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
416 voice->controllerMoved (controllerNumber, controllerValue);
417}
418
420{
421 const ScopedLock sl (lock);
422
423 for (auto* voice : voices)
424 if (voice->getCurrentlyPlayingNote() == midiNoteNumber
425 && (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)))
426 voice->aftertouchChanged (aftertouchValue);
427}
428
430{
431 const ScopedLock sl (lock);
432
433 for (auto* voice : voices)
434 if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
435 voice->channelPressureChanged (channelPressureValue);
436}
437
438void Synthesiser::handleSustainPedal (int midiChannel, bool isDown)
439{
440 jassert (midiChannel > 0 && midiChannel <= 16);
441 const ScopedLock sl (lock);
442
443 if (isDown)
444 {
445 sustainPedalsDown.setBit (midiChannel);
446
447 for (auto* voice : voices)
448 if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown())
449 voice->setSustainPedalDown (true);
450 }
451 else
452 {
453 for (auto* voice : voices)
454 {
455 if (voice->isPlayingChannel (midiChannel))
456 {
457 voice->setSustainPedalDown (false);
458
459 if (! (voice->isKeyDown() || voice->isSostenutoPedalDown()))
460 stopVoice (voice, 1.0f, true);
461 }
462 }
463
464 sustainPedalsDown.clearBit (midiChannel);
465 }
466}
467
468void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown)
469{
470 jassert (midiChannel > 0 && midiChannel <= 16);
471 const ScopedLock sl (lock);
472
473 for (auto* voice : voices)
474 {
475 if (voice->isPlayingChannel (midiChannel))
476 {
477 if (isDown)
478 voice->setSostenutoPedalDown (true);
479 else if (voice->isSostenutoPedalDown())
480 stopVoice (voice, 1.0f, true);
481 }
482 }
483}
484
485void Synthesiser::handleSoftPedal ([[maybe_unused]] int midiChannel, bool /*isDown*/)
486{
487 jassert (midiChannel > 0 && midiChannel <= 16);
488}
489
492{
493 jassert (midiChannel > 0 && midiChannel <= 16);
494}
495
496//==============================================================================
498 int midiChannel, int midiNoteNumber,
499 const bool stealIfNoneAvailable) const
500{
501 const ScopedLock sl (lock);
502
503 for (auto* voice : voices)
504 if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay))
505 return voice;
506
508 return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber);
509
510 return nullptr;
511}
512
514 int /*midiChannel*/, int midiNoteNumber) const
515{
516 // This voice-stealing algorithm applies the following heuristics:
517 // - Re-use the oldest notes first
518 // - Protect the lowest & topmost notes, even if sustained, but not if they've been released.
519
520 // apparently you are trying to render audio without having any voices...
521 jassert (! voices.isEmpty());
522
523 // These are the voices we want to protect (ie: only steal if unavoidable)
524 SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase
525 SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase
526
527 // All major OSes use double-locking so this will be lock- and wait-free as long as the lock is not
528 // contended. This is always the case if you do not call findVoiceToSteal on multiple threads at
529 // the same time.
530 const ScopedLock sl (stealLock);
531
532 // this is a list of voices we can steal, sorted by how long they've been running
533 usableVoicesToStealArray.clear();
534
535 for (auto* voice : voices)
536 {
537 if (voice->canPlaySound (soundToPlay))
538 {
539 jassert (voice->isVoiceActive()); // We wouldn't be here otherwise
540
541 usableVoicesToStealArray.add (voice);
542
543 // NB: Using a functor rather than a lambda here due to scare-stories about
544 // compilers generating code containing heap allocations..
545 struct Sorter
546 {
547 bool operator() (const SynthesiserVoice* a, const SynthesiserVoice* b) const noexcept { return a->wasStartedBefore (*b); }
548 };
549
550 std::sort (usableVoicesToStealArray.begin(), usableVoicesToStealArray.end(), Sorter());
551
552 if (! voice->isPlayingButReleased()) // Don't protect released notes
553 {
554 auto note = voice->getCurrentlyPlayingNote();
555
556 if (low == nullptr || note < low->getCurrentlyPlayingNote())
557 low = voice;
558
559 if (top == nullptr || note > top->getCurrentlyPlayingNote())
560 top = voice;
561 }
562 }
563 }
564
565 // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s)
566 if (top == low)
567 top = nullptr;
568
569 // The oldest note that's playing with the target pitch is ideal..
570 for (auto* voice : usableVoicesToStealArray)
571 if (voice->getCurrentlyPlayingNote() == midiNoteNumber)
572 return voice;
573
574 // Oldest voice that has been released (no finger on it and not held by sustain pedal)
575 for (auto* voice : usableVoicesToStealArray)
576 if (voice != low && voice != top && voice->isPlayingButReleased())
577 return voice;
578
579 // Oldest voice that doesn't have a finger on it:
580 for (auto* voice : usableVoicesToStealArray)
581 if (voice != low && voice != top && ! voice->isKeyDown())
582 return voice;
583
584 // Oldest voice that isn't protected
585 for (auto* voice : usableVoicesToStealArray)
586 if (voice != low && voice != top)
587 return voice;
588
589 // We've only got "protected" voices now: lowest note takes priority
590 jassert (low != nullptr);
591
592 // Duophonic synth: give priority to the bass note:
593 if (top != nullptr)
594 return top;
595
596 return low;
597}
598
599} // namespace juce
A multi-channel buffer containing floating point audio samples.
void makeCopyOf(const AudioBuffer< OtherType > &other, bool avoidReallocating=false)
Resizes this buffer to match the given one, and copies all of its content across.
BigInteger & clear() noexcept
Resets the value to 0.
BigInteger & clearBit(int bitNumber) noexcept
Clears a particular bit in the number.
BigInteger & setBit(int bitNumber)
Sets a specified bit to 1.
Automatically locks and unlocks a mutex object.
Holds a sequence of time-stamped midi events.
Encapsulates a MIDI message.
bool isAftertouch() const noexcept
Returns true if the message is an aftertouch event.
bool isNoteOn(bool returnTrueForVelocity0=false) const noexcept
Returns true if this message is a 'key-down' event.
float getFloatVelocity() const noexcept
Returns the velocity of a note-on or note-off 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.
bool isAllSoundOff() const noexcept
Checks whether this message is an all-sound-off message.
int getControllerNumber() const noexcept
Returns the controller number of a controller message.
int getChannelPressureValue() const noexcept
Returns the pressure from a channel pressure change message.
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
Returns true if this message is a 'key-up' event.
bool isPitchWheel() const noexcept
Returns true if the message is a pitch-wheel move.
int getNoteNumber() const noexcept
Returns the midi note number for note-on and note-off messages.
int getProgramChangeNumber() const noexcept
Returns the new program number of a program change message.
int getAfterTouchValue() const noexcept
Returns the amount of aftertouch from an aftertouch messages.
int getControllerValue() const noexcept
Returns the controller value from a controller message.
bool isAllNotesOff() const noexcept
Checks whether this message is an all-notes-off message.
bool isChannelPressure() const noexcept
Returns true if the message is a channel-pressure change event.
int getPitchWheelValue() const noexcept
Returns the pitch wheel position from a pitch-wheel move message.
Describes one of the sounds that a Synthesiser can play.
~SynthesiserSound() override
Destructor.
Represents a voice that a Synthesiser can use to play a SynthesiserSound.
SynthesiserVoice()
Creates a voice.
virtual void channelPressureChanged(int newChannelPressureValue)
Called to let the voice know that the channel pressure has changed.
virtual void renderNextBlock(AudioBuffer< float > &outputBuffer, int startSample, int numSamples)=0
Renders the next block of data for this voice.
virtual bool isPlayingChannel(int midiChannel) const
Returns true if the voice is currently playing a sound which is mapped to the given midi channel.
bool isKeyDown() const noexcept
Returns true if the key that triggered this voice is still held down.
virtual void setCurrentPlaybackSampleRate(double newRate)
Changes the voice's reference sample rate.
virtual void aftertouchChanged(int newAftertouchValue)
Called to let the voice know that the aftertouch has changed.
int getCurrentlyPlayingNote() const noexcept
Returns the midi note that this voice is currently playing.
void clearCurrentNote()
Resets the state of this voice after a sound has finished playing.
bool wasStartedBefore(const SynthesiserVoice &other) const noexcept
Returns true if this voice started playing its current note before the other voice did.
virtual ~SynthesiserVoice()
Destructor.
bool isPlayingButReleased() const noexcept
Returns true if a voice is sounding in its release phase.
virtual bool isVoiceActive() const
Returns true if this voice is currently busy playing a sound.
Synthesiser()
Creates a new synthesiser.
virtual ~Synthesiser()
Destructor.
virtual SynthesiserVoice * findFreeVoice(SynthesiserSound *soundToPlay, int midiChannel, int midiNoteNumber, bool stealIfNoneAvailable) const
Searches through the voices to find one that's not currently playing, and which can play the given so...
virtual void handleProgramChange(int midiChannel, int programNumber)
Can be overridden to handle an incoming program change message.
void removeVoice(int index)
Deletes one of the voices.
void startVoice(SynthesiserVoice *voice, SynthesiserSound *sound, int midiChannel, int midiNoteNumber, float velocity)
Starts a specified voice playing a particular sound.
virtual void handleAftertouch(int midiChannel, int midiNoteNumber, int aftertouchValue)
Sends an aftertouch message.
void renderNextBlock(AudioBuffer< float > &outputAudio, const MidiBuffer &inputMidi, int startSample, int numSamples)
Creates the next block of audio output.
virtual void handleController(int midiChannel, int controllerNumber, int controllerValue)
Sends a midi controller message to any active voices.
void clearSounds()
Deletes all sounds.
virtual SynthesiserVoice * findVoiceToSteal(SynthesiserSound *soundToPlay, int midiChannel, int midiNoteNumber) const
Chooses a voice that is most suitable for being re-used.
virtual void handleSoftPedal(int midiChannel, bool isDown)
Can be overridden to handle soft pedal events.
void removeSound(int index)
Removes and deletes one of the sounds.
virtual void allNotesOff(int midiChannel, bool allowTailOff)
Turns off all notes.
SynthesiserVoice * addVoice(SynthesiserVoice *newVoice)
Adds a new voice to the synth.
void stopVoice(SynthesiserVoice *, float velocity, bool allowTailOff)
Stops a given voice.
SynthesiserSound * addSound(const SynthesiserSound::Ptr &newSound)
Adds a new sound to the synthesiser.
virtual void renderVoices(AudioBuffer< float > &outputAudio, int startSample, int numSamples)
Renders the voices for the given range.
virtual void handleMidiEvent(const MidiMessage &)
Can be overridden to do custom handling of incoming midi events.
void setNoteStealingEnabled(bool shouldStealNotes)
If set to true, then the synth will try to take over an existing voice if it runs out and needs to pl...
virtual void handlePitchWheel(int midiChannel, int wheelValue)
Sends a pitch-wheel message to any active voices.
CriticalSection lock
This is used to control access to the rendering callback and the note trigger methods.
int lastPitchWheelValues[16]
The last pitch-wheel values for each midi channel.
virtual void noteOn(int midiChannel, int midiNoteNumber, float velocity)
Triggers a note-on event.
SynthesiserVoice * getVoice(int index) const
Returns one of the voices that have been added.
virtual void noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff)
Triggers a note-off event.
virtual void handleChannelPressure(int midiChannel, int channelPressureValue)
Sends a channel pressure message.
void clearVoices()
Deletes all voices.
virtual void setCurrentPlaybackSampleRate(double sampleRate)
Tells the synthesiser what the sample rate is for the audio it's being used to render.
void setMinimumRenderingSubdivisionSize(int numSamples, bool shouldBeStrict=false) noexcept
Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering.
virtual void handleSustainPedal(int midiChannel, bool isDown)
Handles a sustain pedal event.
virtual void handleSostenutoPedal(int midiChannel, bool isDown)
Handles a sostenuto pedal event.
T for_each(T... args)
#define jassert(expression)
Platform-independent assertion macro.
JUCE Namespace.
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr bool exactlyEqual(Type a, Type b)
Equivalent to operator==, but suppresses float-equality warnings.
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
constexpr int numElementsInArray(Type(&)[N]) noexcept
Handy function for getting the number of elements in a simple const C array.
T sort(T... args)