tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_SamplerPlugin.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14// this must be high enough for low freq sounds not to click
15static constexpr int minimumSamplesToPlayWhenStopping = 8;
16static constexpr int maximumSimultaneousNotes = 32;
17
18
20{
21public:
22 SampledNote (int midiNote, int keyNote,
23 float velocity,
24 const AudioFile& file,
25 double sampleRate,
26 int sampleDelayFromBufferStart,
27 const juce::AudioBuffer<float>& data,
28 int lengthInSamples,
29 float gainDb,
30 float pan,
31 bool openEnded_)
32 : note (midiNote),
33 offset (-sampleDelayFromBufferStart),
34 audioData (data),
35 openEnded (openEnded_)
36 {
37 resampler[0].reset();
38 resampler[1].reset();
39
40 const float volumeSliderPos = decibelsToVolumeFaderPosition (gainDb - (20.0f * (1.0f - velocity)));
41 getGainsFromVolumeFaderPositionAndPan (volumeSliderPos, pan, getDefaultPanLaw(), gains[0], gains[1]);
42
43 const double hz = juce::MidiMessage::getMidiNoteInHertz (midiNote);
44 playbackRatio = hz / juce::MidiMessage::getMidiNoteInHertz (keyNote);
45 playbackRatio *= file.getSampleRate() / sampleRate;
46 samplesLeftToPlay = playbackRatio > 0 ? (1 + (int) (lengthInSamples / playbackRatio)) : 0;
47 }
48
49 void addNextBlock (juce::AudioBuffer<float>& outBuffer, int startSamp, int numSamples)
50 {
51 jassert (! isFinished);
52
53 if (offset < 0)
54 {
55 const int num = std::min (-offset, numSamples);
56 startSamp += num;
57 numSamples -= num;
58 offset += num;
59 }
60
61 auto numSamps = std::min (numSamples, samplesLeftToPlay);
62
63 if (numSamps > 0)
64 {
65 int numUsed = 0;
66
67 for (int i = std::min (2, outBuffer.getNumChannels()); --i >= 0;)
68 {
69 numUsed = resampler[i]
70 .processAdding (playbackRatio,
71 audioData.getReadPointer (std::min (i, audioData.getNumChannels() - 1), offset),
72 outBuffer.getWritePointer (i, startSamp),
73 numSamps,
74 gains[i]);
75 }
76
77 offset += numUsed;
78 samplesLeftToPlay -= numSamps;
79
80 jassert (offset <= audioData.getNumSamples());
81 }
82
83 if (numSamples > numSamps && startFade > 0.0f)
84 {
85 startSamp += numSamps;
86 numSamps = numSamples - numSamps;
87 float endFade;
88
89 if (numSamps > 100)
90 {
91 endFade = 0.0f;
92 numSamps = 100;
93 }
94 else
95 {
96 endFade = std::max (0.0f, startFade - numSamps * 0.01f);
97 }
98
99 const int numSampsNeeded = 2 + juce::roundToInt ((numSamps + 2) * playbackRatio);
100 AudioScratchBuffer scratch (audioData.getNumChannels(), numSampsNeeded + 8);
101
102 if (offset + numSampsNeeded < audioData.getNumSamples())
103 {
104 for (int i = scratch.buffer.getNumChannels(); --i >= 0;)
105 scratch.buffer.copyFrom (i, 0, audioData, i, offset, numSampsNeeded);
106 }
107 else
108 {
109 scratch.buffer.clear();
110 }
111
112 if (numSampsNeeded > 2)
113 AudioFadeCurve::applyCrossfadeSection (scratch.buffer, 0, numSampsNeeded - 2,
114 AudioFadeCurve::linear, startFade, endFade);
115
116 startFade = endFade;
117
118 int numUsed = 0;
119
120 for (int i = std::min (2, outBuffer.getNumChannels()); --i >= 0;)
121 numUsed = resampler[i].processAdding (playbackRatio,
122 scratch.buffer.getReadPointer (std::min (i, scratch.buffer.getNumChannels() - 1)),
123 outBuffer.getWritePointer (i, startSamp),
124 numSamps, gains[i]);
125
126 offset += numUsed;
127
128 if (startFade <= 0.0f)
129 isFinished = true;
130 }
131 }
132
133 juce::LagrangeInterpolator resampler[2];
134 int note;
135 int offset, samplesLeftToPlay = 0;
136 float gains[2];
137 double playbackRatio = 1.0;
138 const juce::AudioBuffer<float>& audioData;
139 float lastVals[4] = { 0, 0, 0, 0 };
140 float startFade = 1.0f;
141 bool openEnded, isFinished = false;
142
143private:
145};
146
147//==============================================================================
148SamplerPlugin::SamplerPlugin (PluginCreationInfo info) : Plugin (info)
149{
151}
152
153SamplerPlugin::~SamplerPlugin()
154{
155 notifyListenersOfDeletion();
156}
157
158const char* SamplerPlugin::xmlTypeName = "sampler";
159
160void SamplerPlugin::valueTreeChanged()
161{
163 Plugin::valueTreeChanged();
164}
165
166void SamplerPlugin::handleAsyncUpdate()
167{
169
170 auto numSounds = state.getNumChildren();
171
172 for (int i = 0; i < numSounds; ++i)
173 {
174 auto v = getSound (i);
175
176 if (v.hasType (IDs::SOUND))
177 {
178 auto s = new SamplerSound (*this,
179 v[IDs::source].toString(),
180 v[IDs::name],
181 v[IDs::startTime],
182 v[IDs::length],
183 v[IDs::gainDb]);
184
185 s->keyNote = juce::jlimit (0, 127, static_cast<int> (v[IDs::keyNote]));
186 s->minNote = juce::jlimit (0, 127, static_cast<int> (v[IDs::minNote]));
187 s->maxNote = juce::jlimit (0, 127, static_cast<int> (v[IDs::maxNote]));
188 s->pan = juce::jlimit (-1.0f, 1.0f, static_cast<float> (v[IDs::pan]));
189 s->openEnded = v[IDs::openEnded];
190
191 newSounds.add (s);
192 }
193 }
194
195 for (auto newSound : newSounds)
196 {
197 for (auto s : soundList)
198 {
199 if (s->source == newSound->source
200 && s->startTime == newSound->startTime
201 && s->length == newSound->length)
202 {
203 newSound->audioFile = s->audioFile;
204 newSound->fileStartSample = s->fileStartSample;
205 newSound->fileLengthSamples = s->fileLengthSamples;
206 newSound->audioData = s->audioData;
207 }
208 }
209 }
210
211 {
212 const juce::ScopedLock sl (lock);
213 allNotesOff();
214 soundList.swapWith (newSounds);
215
217 }
218
219 newSounds.clear();
220 changed();
221}
222
223void SamplerPlugin::initialise (const PluginInitialisationInfo&)
224{
225 const juce::ScopedLock sl (lock);
226 allNotesOff();
227}
228
230{
231 allNotesOff();
232}
233
234//==============================================================================
235void SamplerPlugin::playNotes (const juce::BigInteger& keysDown)
236{
237 const juce::ScopedLock sl (lock);
238
239 if (highlightedNotes != keysDown)
240 {
241 for (int i = playingNotes.size(); --i >= 0;)
242 if ((! keysDown [playingNotes.getUnchecked(i)->note])
243 && highlightedNotes [playingNotes.getUnchecked(i)->note]
244 && ! playingNotes.getUnchecked(i)->openEnded)
245 playingNotes.getUnchecked(i)->samplesLeftToPlay = minimumSamplesToPlayWhenStopping;
246
247 for (int note = 128; --note >= 0;)
248 {
249 if (keysDown [note] && ! highlightedNotes [note])
250 {
251 for (auto ss : soundList)
252 {
253 if (ss->minNote <= note
254 && ss->maxNote >= note
255 && ss->audioData.getNumSamples() > 0
256 && (! ss->audioFile.isNull())
257 && playingNotes.size() < maximumSimultaneousNotes)
258 {
259 playingNotes.add (new SampledNote (note,
260 ss->keyNote,
261 0.75f,
262 ss->audioFile,
263 sampleRate,
264 0,
265 ss->audioData,
266 ss->fileLengthSamples,
267 ss->gainDb,
268 ss->pan,
269 ss->openEnded));
270 }
271 }
272 }
273 }
274
275 highlightedNotes = keysDown;
276 }
277}
278
279void SamplerPlugin::allNotesOff()
280{
281 const juce::ScopedLock sl (lock);
282 playingNotes.clear();
283 highlightedNotes.clear();
284}
285
287{
288 if (fc.destBuffer != nullptr)
289 {
290 SCOPED_REALTIME_CHECK
291
292 const juce::ScopedLock sl (lock);
293
294 clearChannels (*fc.destBuffer, 2, -1, fc.bufferStartSample, fc.bufferNumSamples);
295
296 if (fc.bufferForMidiMessages != nullptr)
297 {
298 if (fc.bufferForMidiMessages->isAllNotesOff)
299 {
300 playingNotes.clear();
301 highlightedNotes.clear();
302 }
303
304 for (auto& m : *fc.bufferForMidiMessages)
305 {
306 if (m.isNoteOn())
307 {
308 const int note = m.getNoteNumber();
309 const int noteTimeSample = juce::roundToInt (m.getTimeStamp() * sampleRate);
310
311 for (auto playingNote : playingNotes)
312 {
313 if (playingNote->note == note && ! playingNote->openEnded)
314 {
315 playingNote->samplesLeftToPlay = std::min (playingNote->samplesLeftToPlay,
316 std::max (minimumSamplesToPlayWhenStopping,
317 noteTimeSample));
318 highlightedNotes.clearBit (note);
319 }
320 }
321
322 for (auto ss : soundList)
323 {
324 if (ss->minNote <= note
325 && ss->maxNote >= note
326 && ss->audioData.getNumSamples() > 0
327 && playingNotes.size() < maximumSimultaneousNotes)
328 {
329 highlightedNotes.setBit (note);
330
331 playingNotes.add (new SampledNote (note,
332 ss->keyNote,
333 m.getVelocity() / 127.0f,
334 ss->audioFile,
335 sampleRate,
336 noteTimeSample,
337 ss->audioData,
338 ss->fileLengthSamples,
339 ss->gainDb,
340 ss->pan,
341 ss->openEnded));
342 }
343 }
344 }
345 else if (m.isNoteOff())
346 {
347 const int note = m.getNoteNumber();
348 const int noteTimeSample = juce::roundToInt (m.getTimeStamp() * sampleRate);
349
350 for (auto playingNote : playingNotes)
351 {
352 if (playingNote->note == note && ! playingNote->openEnded)
353 {
354 playingNote->samplesLeftToPlay = std::min (playingNote->samplesLeftToPlay,
355 std::max (minimumSamplesToPlayWhenStopping,
356 noteTimeSample));
357
358 highlightedNotes.clearBit (note);
359 }
360 }
361 }
362 else if (m.isAllNotesOff() || m.isAllSoundOff())
363 {
364 playingNotes.clear();
365 highlightedNotes.clear();
366 }
367 }
368 }
369
370 for (int i = playingNotes.size(); --i >= 0;)
371 {
372 auto sn = playingNotes.getUnchecked (i);
373
374 sn->addNextBlock (*fc.destBuffer, fc.bufferStartSample, fc.bufferNumSamples);
375
376 if (sn->isFinished)
377 playingNotes.remove (i);
378 }
379 }
380}
381
382//==============================================================================
383int SamplerPlugin::getNumSounds() const
384{
385 return std::accumulate (state.begin(), state.end(), 0,
386 [] (int total, auto v) { return total + (v.hasType (IDs::SOUND) ? 1 : 0); });
387}
388
389juce::String SamplerPlugin::getSoundName (int index) const
390{
391 return getSound (index)[IDs::name];
392}
393
394void SamplerPlugin::setSoundName (int index, const juce::String& n)
395{
396 getSound (index).setProperty (IDs::name, n, getUndoManager());
397}
398
400{
401 juce::String s;
402
403 {
404 const juce::ScopedLock sl (lock);
405
406 for (auto ss : soundList)
407 {
408 if (ss->minNote <= note && ss->maxNote >= note)
409 {
410 if (s.isNotEmpty())
411 s << " + " << ss->name;
412 else
413 s = ss->name;
414 }
415 }
416 }
417
418 noteName = s;
419 return true;
420}
421
422AudioFile SamplerPlugin::getSoundFile (int index) const
423{
424 const juce::ScopedLock sl (lock);
425
426 if (auto s = soundList[index])
427 return s->audioFile;
428
429 return AudioFile (edit.engine);
430}
431
432juce::String SamplerPlugin::getSoundMedia (int index) const
433{
434 const juce::ScopedLock sl (lock);
435
436 if (auto s = soundList[index])
437 return s->source;
438
439 return {};
440}
441
442int SamplerPlugin::getKeyNote (int index) const { return getSound (index)[IDs::keyNote]; }
443int SamplerPlugin::getMinKey (int index) const { return getSound (index)[IDs::minNote]; }
444int SamplerPlugin::getMaxKey (int index) const { return getSound (index)[IDs::maxNote]; }
445float SamplerPlugin::getSoundGainDb (int index) const { return getSound (index)[IDs::gainDb]; }
446float SamplerPlugin::getSoundPan (int index) const { return getSound (index)[IDs::pan]; }
447double SamplerPlugin::getSoundStartTime (int index) const { return getSound (index)[IDs::startTime]; }
448bool SamplerPlugin::isSoundOpenEnded (int index) const { return getSound (index)[IDs::openEnded]; }
449
450double SamplerPlugin::getSoundLength (int index) const
451{
452 const double l = getSound (index)[IDs::length];
453
454 if (l == 0.0)
455 {
456 const juce::ScopedLock sl (lock);
457
458 if (auto s = soundList[index])
459 return s->length;
460 }
461
462 return l;
463}
464
465juce::String SamplerPlugin::addSound (const juce::String& source, const juce::String& name,
466 double startTime, double length, float gainDb)
467{
468 const int maxNumSamples = 64;
469
470 if (getNumSounds() >= maxNumSamples)
471 return TRANS("Can't load any more samples");
472
473 auto v = createValueTree (IDs::SOUND,
474 IDs::source, source,
475 IDs::name, name,
476 IDs::startTime, startTime,
477 IDs::length, length,
478 IDs::keyNote, 72,
479 IDs::minNote, 72 - 24,
480 IDs::maxNote, 72 + 24,
481 IDs::gainDb, gainDb,
482 IDs::pan, (double) 0);
483
484 state.addChild (v, -1, getUndoManager());
485 return {};
486}
487
488void SamplerPlugin::removeSound (int index)
489{
490 state.removeChild (index, getUndoManager());
491
492 const juce::ScopedLock sl (lock);
493 playingNotes.clear();
494 highlightedNotes.clear();
495}
496
497void SamplerPlugin::setSoundParams (int index, int keyNote, int minNote, int maxNote)
498{
499 auto um = getUndoManager();
500
501 auto v = getSound (index);
502 v.setProperty (IDs::keyNote, juce::jlimit (0, 127, keyNote), um);
503 v.setProperty (IDs::minNote, juce::jlimit (0, 127, std::min (minNote, maxNote)), um);
504 v.setProperty (IDs::maxNote, juce::jlimit (0, 127, std::max (minNote, maxNote)), um);
505}
506
507void SamplerPlugin::setSoundGains (int index, float gainDb, float pan)
508{
509 auto um = getUndoManager();
510
511 auto v = getSound (index);
512 v.setProperty (IDs::gainDb, juce::jlimit (-48.0f, 48.0f, gainDb), um);
513 v.setProperty (IDs::pan, juce::jlimit (-1.0f, 1.0f, pan), um);
514}
515
516void SamplerPlugin::setSoundExcerpt (int index, double start, double length)
517{
518 auto um = getUndoManager();
519
520 auto v = getSound (index);
521 v.setProperty (IDs::startTime, start, um);
522 v.setProperty (IDs::length, length, um);
523}
524
525void SamplerPlugin::setSoundOpenEnded (int index, bool b)
526{
527 auto um = getUndoManager();
528
529 auto v = getSound (index);
530 v.setProperty (IDs::openEnded, b, um);
531}
532
533void SamplerPlugin::setSoundMedia (int index, const juce::String& source)
534{
535 auto v = getSound (index);
536 v.setProperty (IDs::source, source, getUndoManager());
538}
539
540juce::ValueTree SamplerPlugin::getSound (int soundIndex) const
541{
542 int index = 0;
543
544 for (auto v : state)
545 if (v.hasType (IDs::SOUND))
546 if (index++ == soundIndex)
547 return v;
548
549 return {};
550}
551
552//==============================================================================
553juce::Array<Exportable::ReferencedItem> SamplerPlugin::getReferencedItems()
554{
556
557 // must be careful to generate this list in the right order..
558 for (int i = 0; i < getNumSounds(); ++i)
559 {
560 auto v = getSound (i);
561
562 Exportable::ReferencedItem ref;
563 ref.itemID = ProjectItemID::fromProperty (v, IDs::source);
564 ref.firstTimeUsed = v[IDs::startTime];
565 ref.lengthUsed = v[IDs::length];
566 results.add (ref);
567 }
568
569 return results;
570}
571
572void SamplerPlugin::reassignReferencedItem (const ReferencedItem& item, ProjectItemID newID, double newStartTime)
573{
574 auto index = getReferencedItems().indexOf (item);
575
576 if (index >= 0)
577 {
578 auto um = getUndoManager();
579
580 auto v = getSound (index);
581 v.setProperty (IDs::source, newID.toString(), um);
582 v.setProperty (IDs::startTime, static_cast<double> (v[IDs::startTime]) - newStartTime, um);
583 }
584 else
585 {
587 }
588}
589
591{
592 const juce::ScopedLock sl (lock);
593
594 for (auto s : soundList)
595 s->refreshFile();
596}
597
598void SamplerPlugin::restorePluginStateFromValueTree (const juce::ValueTree& v)
599{
600 copyValueTree (state, v, getUndoManager());
601}
602
603//==============================================================================
604SamplerPlugin::SamplerSound::SamplerSound (SamplerPlugin& sf,
605 const juce::String& source_,
606 const juce::String& name_,
607 const double startTime_,
608 const double length_,
609 const float gainDb_)
610 : owner (sf),
611 source (source_),
612 name (name_),
613 gainDb (juce::jlimit (-48.0f, 48.0f, gainDb_)),
614 startTime (startTime_),
615 length (length_),
616 audioFile (owner.edit.engine, SourceFileReference::findFileFromString (owner.edit, source))
617{
618 setExcerpt (startTime_, length_);
619
620 keyNote = audioFile.getInfo().loopInfo.getRootNote();
621
622 if (keyNote < 0)
623 keyNote = 72;
624
625 maxNote = keyNote + 24;
626 minNote = keyNote - 24;
627}
628
629void SamplerPlugin::SamplerSound::setExcerpt (double startTime_, double length_)
630{
632
633 if (! audioFile.isValid())
634 {
635 audioFile = AudioFile (owner.edit.engine, SourceFileReference::findFileFromString (owner.edit, source));
636
637 #if JUCE_DEBUG
638 if (! audioFile.isValid() && ProjectItemID (source).isValid())
639 DBG ("Failed to find media: " << source);
640 #endif
641 }
642
643 if (audioFile.isValid())
644 {
645 const double minLength = 32.0 / audioFile.getSampleRate();
646
647 startTime = juce::jlimit (0.0, audioFile.getLength() - minLength, startTime_);
648
649 if (length_ > 0)
650 length = juce::jlimit (minLength, audioFile.getLength() - startTime, length_);
651 else
652 length = audioFile.getLength();
653
654 fileStartSample = juce::roundToInt (startTime * audioFile.getSampleRate());
655 fileLengthSamples = juce::roundToInt (length * audioFile.getSampleRate());
656
657 if (auto reader = owner.engine.getAudioFileManager().cache.createReader (audioFile))
658 {
659 audioData.setSize (audioFile.getNumChannels(), fileLengthSamples + 32);
660 audioData.clear();
661
662 auto audioDataChannelSet = juce::AudioChannelSet::canonicalChannelSet (audioFile.getNumChannels());
663 auto channelsToUse = juce::AudioChannelSet::stereo();
664
665 int total = fileLengthSamples;
666 int offset = 0;
667
668 while (total > 0)
669 {
670 const int numThisTime = std::min (8192, total);
671 reader->setReadPosition (fileStartSample + offset);
672
673 if (! reader->readSamples (numThisTime, audioData, audioDataChannelSet, offset, channelsToUse, 2000))
674 {
676 break;
677 }
678
679 offset += numThisTime;
680 total -= numThisTime;
681 }
682 }
683 else
684 {
685 audioData.clear();
686 }
687
688 // add a quick fade-in if needed..
689 int fadeLen = 0;
690 for (int i = audioData.getNumChannels(); --i >= 0;)
691 {
692 const float* d = audioData.getReadPointer (i);
693
694 if (std::abs (*d) > 0.01f)
695 fadeLen = 30;
696 }
697
698 if (fadeLen > 0)
699 AudioFadeCurve::applyCrossfadeSection (audioData, 0, fadeLen, AudioFadeCurve::concave, 0.0f, 1.0f);
700 }
701 else
702 {
703 audioFile = AudioFile (owner.edit.engine);
704 }
705}
706
707void SamplerPlugin::SamplerSound::refreshFile()
708{
709 audioFile = AudioFile (owner.edit.engine);
710 setExcerpt (startTime, length);
711}
712
713}} // namespace tracktion { inline namespace engine
T accumulate(T... args)
void add(const ElementType &newElement)
void setSize(int newNumChannels, int newNumSamples, bool keepExistingContent=false, bool clearExtraSpace=false, bool avoidReallocating=false)
Type * getWritePointer(int channelNumber) noexcept
int getNumChannels() const noexcept
int getNumSamples() const noexcept
void clear() noexcept
void copyFrom(int destChannel, int destStartSample, const AudioBuffer &source, int sourceChannel, int sourceStartSample, int numSamples) noexcept
const Type * getReadPointer(int channelNumber) const noexcept
static AudioChannelSet JUCE_CALLTYPE stereo()
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
BigInteger & clear() noexcept
BigInteger & clearBit(int bitNumber) noexcept
BigInteger & setBit(int bitNumber)
static double getMidiNoteInHertz(int noteNumber, double frequencyOfA=440.0) noexcept
void clear(bool deleteObjects=true)
ObjectClass * add(ObjectClass *newObject)
bool isNotEmpty() const noexcept
Iterator begin() const noexcept
void removeChild(const ValueTree &child, UndoManager *undoManager)
int getNumChildren() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
Iterator end() const noexcept
Reader::Ptr createReader(const AudioFile &)
Creates a Reader to read an AudioFile.
An audio scratch buffer that has pooled storage.
juce::AudioBuffer< float > & buffer
The buffer to use.
Engine & engine
A reference to the Engine.
AudioFileManager & getAudioFileManager() const
Returns the AudioFileManager instance.
void changed() override
method from Selectable, that's been overridden here to also tell the edit that it's changed.
void applyToBuffer(const PluginRenderContext &) override
Process the next block of data.
bool hasNameForMidiNoteNumber(int note, int midiChannel, juce::String &name) override
If it's a synth that names its notes, this can return the name it uses for this note 0-127.
void sourceMediaChanged() override
Called when ProjectItem sources are re-assigned so you can reload from the new source.
void deinitialise() override
Called after play stops to release resources.
This class wraps a string that is generally held in a 'source' property, and which is a reference to ...
#define TRANS(stringLiteral)
#define jassert(expression)
#define DBG(textToWrite)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
typedef int
T max(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
int roundToInt(const FloatType value) noexcept
Interpolators::Lagrange LagrangeInterpolator
Passed into Plugins when they are being initialised, to give them useful contextual information that ...
T ref(T... args)
The context passed to plugin render methods to provide it with buffers to fill.
int bufferNumSamples
The number of samples to write into the audio buffer.
MidiMessageArray * bufferForMidiMessages
A buffer of MIDI events to process.
juce::AudioBuffer< float > * destBuffer
The target audio buffer which needs to be filled.
int bufferStartSample
The index of the start point in the audio buffer from which data must be written.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.