iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
ISender.h
Go to the documentation of this file.
1/*
2 ==============================================================================
3
4 This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5
6 See LICENSE.txt for more info.
7
8 ==============================================================================
9*/
10
11#pragma once
12
19#include "denormal.h"
20#include "fft.h"
21
22#include "IPlugPlatform.h"
23#include "IPlugQueue.h"
24#include <array>
25
26#if defined OS_IOS || defined OS_MAC
27#include <Accelerate/Accelerate.h>
28#endif
29
30BEGIN_IPLUG_NAMESPACE
31
33template <int MAXNC = 1, typename T = float>
35{
36 int ctrlTag = kNoTag;
37 int nChans = MAXNC;
38 int chanOffset = 0;
39 std::array<T, MAXNC> vals;
40
42 : vals{}
43 {
44 }
45
46 ISenderData(int ctrlTag, int nChans, int chanOffset)
47 : ctrlTag(ctrlTag)
48 , nChans(nChans)
49 , chanOffset(chanOffset)
50 , vals{}
51 {
52 }
53
54 ISenderData(int ctrlTag, const std::array<T, MAXNC>& vals, int nChans = MAXNC, int chanOffset = 0)
55 : ctrlTag(ctrlTag)
56 , nChans(nChans)
57 , chanOffset(chanOffset)
58 , vals(vals)
59 {
60 }
61};
62
64template <int MAXNC = 1, int QUEUE_SIZE = 64, typename T = float>
66{
67public:
68 static constexpr int kUpdateMessage = 0;
69
72 {
73 mQueue.Push(d);
74 }
75
77 virtual void PrepareDataForUI(ISenderData<MAXNC, T>& d) { /* NO-OP*/ }
78
82 {
83 while (mQueue.ElementsAvailable())
84 {
86 mQueue.Pop(d);
87 assert(d.ctrlTag != kNoTag && "You must supply a control tag");
89 dlg.SendControlMsgFromDelegate(d.ctrlTag, kUpdateMessage, sizeof(ISenderData<MAXNC, T>), (void*) &d);
90 mLastData = d;
91 }
92 }
93
97 void TransmitDataToControlsWithTags(IEditorDelegate& dlg, const std::initializer_list<int>& ctrlTags)
98 {
99 while(mQueue.ElementsAvailable())
100 {
102 mQueue.Pop(d);
103
104 for (auto tag : ctrlTags)
105 {
106 d.ctrlTag = tag;
107 dlg.SendControlMsgFromDelegate(tag, kUpdateMessage, sizeof(ISenderData<MAXNC, T>), (void*) &d);
108 }
109 }
110 }
111
113 ISenderData<MAXNC, T> GetLastData() { return mLastData; }
114
115protected:
116 IPlugQueue<ISenderData<MAXNC, T>> mQueue {QUEUE_SIZE};
117 ISenderData<MAXNC, T> mLastData;
118};
119
123template <int MAXNC = 1, int QUEUE_SIZE = 64>
124class IPeakSender : public ISender<MAXNC, QUEUE_SIZE, float>
125{
126public:
127 IPeakSender(double minThresholdDb = -90., float windowSizeMs = 5.0f)
129 , mThreshold(static_cast<float>(DBToAmp(minThresholdDb)))
130 {
131 Reset(DEFAULT_SAMPLE_RATE);
132 }
133
134 void Reset(double sampleRate)
135 {
136 SetWindowSizeMs(mWindowSizeMs, sampleRate);
137 std::fill(mPeaks.begin(), mPeaks.end(), 0.0f);
138 }
139
140 void SetWindowSizeMs(double timeMs, double sampleRate)
141 {
142 mWindowSizeMs = static_cast<float>(timeMs);
143 mWindowSize = static_cast<int>(timeMs * 0.001 * sampleRate);
144 }
145
152 void ProcessBlock(sample** inputs, int nFrames, int ctrlTag = kNoTag, int nChans = MAXNC, int chanOffset = 0)
153 {
154 for (auto s = 0; s < nFrames; s++)
155 {
156 if (mCount == 0)
157 {
158 ISenderData<MAXNC, float> d {ctrlTag, nChans, chanOffset};
159
160 float sum = 0.0f;
161
162 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
163 {
164 d.vals[c] = mPeaks[c] / mWindowSize;
165 mPeaks[c] = 0.0f;
166 sum += d.vals[c];
167 }
168
169 if (sum > mThreshold || mPreviousSum > mThreshold)
171
172 mPreviousSum = sum;
173 }
174
175 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
176 {
177 mPeaks[c] += std::fabs(static_cast<float>(inputs[c][s]));
178 }
179
180 mCount++;
181 mCount %= mWindowSize;
182 }
183 }
184private:
185 float mPreviousSum = 1.f;
186 float mThreshold = 0.01f;
187 float mWindowSizeMs = 5.0f;
188 int mWindowSize = 32;
189 int mCount = 0;
190 std::array<float, MAXNC> mPeaks = {0.0};
191};
192
196template <int MAXNC = 1, int QUEUE_SIZE = 64>
197class IPeakAvgSender : public ISender<MAXNC, QUEUE_SIZE, std::pair<float, float>>
198{
199public:
201 {
202 public:
203 inline float Process(float& input, float& attackTimeSamples, float& decayTimeSamples)
204 {
205 if (input > mPreviousOutput)
206 {
207 if (attackTimeSamples > 1.0f)
208 {
209 mPreviousOutput += (input - mPreviousOutput) / attackTimeSamples;
210 }
211 else
212 {
213 mPreviousOutput = input;
214 }
215 }
216 else if (input < mPreviousOutput)
217 {
218 if (decayTimeSamples > 1.0f)
219 {
220 mPreviousOutput += (input - mPreviousOutput) / decayTimeSamples;
221 }
222 else
223 {
224 mPreviousOutput = input;
225 }
226 }
227
228 denormal_fix(&mPreviousOutput);
229 return mPreviousOutput;
230 }
231
232 private:
233 float mPreviousOutput = 0.0f;
234 };
235
236 IPeakAvgSender(double minThresholdDb = -90.0, bool rmsMode = true, float windowSizeMs = 5.0f, float attackTimeMs = 1.0f, float decayTimeMs = 100.0f, float peakHoldTimeMs = 500.0f)
237 : ISender<MAXNC, QUEUE_SIZE, std::pair<float, float>>()
238 , mThreshold(static_cast<float>(DBToAmp(minThresholdDb)))
239 , mRMSMode(rmsMode)
240 , mWindowSizeMs(windowSizeMs)
241 , mAttackTimeMs(attackTimeMs)
242 , mDecayTimeMs(decayTimeMs)
243 , mPeakHoldTimeMs(peakHoldTimeMs)
244 {
245 Reset(DEFAULT_SAMPLE_RATE);
246 }
247
248 void Reset(double sampleRate)
249 {
250 SetWindowSizeMs(mWindowSizeMs, sampleRate);
251 SetAttackTimeMs(mAttackTimeMs, sampleRate);
252 SetDecayTimeMs(mDecayTimeMs, sampleRate);
253 SetPeakHoldTimeMs(mPeakHoldTimeMs, sampleRate);
254 std::fill(mHeldPeaks.begin(), mHeldPeaks.end(), 0.0f);
255 }
256
257 void SetAttackTimeMs(double timeMs, double sampleRate)
258 {
259 mAttackTimeMs = static_cast<float>(timeMs);
260 mAttackTimeSamples = static_cast<float>(timeMs * 0.001 * (sampleRate / double(mWindowSize)));
261 }
262
263 void SetDecayTimeMs(double timeMs, double sampleRate)
264 {
265 mDecayTimeMs = static_cast<float>(timeMs);
266 mDecayTimeSamples = static_cast<float>(timeMs * 0.001 * (sampleRate / mWindowSize));
267 }
268
269 void SetWindowSizeMs(double timeMs, double sampleRate)
270 {
271 mWindowSizeMs = static_cast<float>(timeMs);
272 mWindowSize = static_cast<int>(timeMs * 0.001 * sampleRate);
273
274 for (auto i=0; i<MAXNC; i++)
275 {
276 mBuffers[i].resize(mWindowSize);
277 std::fill(mBuffers[i].begin(), mBuffers[i].end(), 0.0f);
278 }
279 }
280
281 void SetPeakHoldTimeMs(double timeMs, double sampleRate)
282 {
283 mPeakHoldTimeMs = static_cast<float>(timeMs);
284 mPeakHoldTime = static_cast<int>(timeMs * 0.001 * sampleRate);
285 std::fill(mPeakHoldCounters.begin(), mPeakHoldCounters.end(), mPeakHoldTime);
286 }
287
294 void ProcessBlock(sample** inputs, int nFrames, int ctrlTag = kNoTag, int nChans = MAXNC, int chanOffset = 0)
295 {
296 for (auto s = 0; s < nFrames; s++)
297 {
298 int windowPos = s % mWindowSize;
299
300 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
301 {
302 mBuffers[c][windowPos] = static_cast<float>(inputs[c][s]);
303 }
304
305 if (mCount == 0)
306 {
307 ISenderData<MAXNC, std::pair<float, float>> d {ctrlTag, nChans, chanOffset};
308
309 auto avgSum = 0.0f;
310
311 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
312 {
313 auto peakVal = 0.0f;
314 auto avgVal = 0.0f;
315#if defined OS_IOS || defined OS_MAC
316 vDSP_vabs(mBuffers[c].data(), 1, mBuffers[c].data(), 1, mWindowSize);
317 vDSP_maxv(mBuffers[c].data(), 1, &peakVal, mWindowSize);
318
319 if (mRMSMode)
320 {
321 vDSP_rmsqv(mBuffers[c].data(), 1, &avgVal, mWindowSize);
322 }
323 else
324 {
325 vDSP_meanv(mBuffers[c].data(), 1, &avgVal, mWindowSize);
326 }
327#else
328 for (auto i=0; i<mWindowSize; i++)
329 {
330 auto absVal = std::fabs(mBuffers[c][i]);
331
332 if (absVal > peakVal)
333 {
334 peakVal = absVal;
335 }
336
337 if (mRMSMode)
338 {
339 absVal = absVal * absVal;
340 }
341
342 avgVal += absVal;
343 }
344
345 avgVal /= static_cast<float>(mWindowSize);
346
347 if (mRMSMode)
348 {
349 avgVal = std::sqrt(avgVal);
350 }
351#endif
352
353 // set peak-hold value
354 if (mPeakHoldCounters[c] <= 0)
355 {
356 mHeldPeaks[c] = 0.0f;
357 }
358
359 if (mHeldPeaks[c] < peakVal)
360 {
361 mHeldPeaks[c] = peakVal;
362 mPeakHoldCounters[c] = mPeakHoldTime;
363 }
364 else
365 {
366 if (mPeakHoldCounters[c] > 0)
367 {
368 mPeakHoldCounters[c] -= mWindowSize;
369 }
370 }
371
372 std::get<0>(d.vals[c]) = mHeldPeaks[c];
373
374 // set avg value
375 auto smoothedAvg = mEnvFollowers[c].Process(avgVal, mAttackTimeSamples, mDecayTimeSamples);
376 std::get<1>(d.vals[c]) = smoothedAvg;
377
378 avgSum += smoothedAvg;
379 }
380
381 if (mPreviousSum > mThreshold)
382 {
384 }
385 else
386 {
387 // This makes sure that the data is still pushed if
388 // peakholds are still active
389 bool counterActive = false;
390
391 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
392 {
393 counterActive &= mPeakHoldCounters[c] > 0;
394 std::get<0>(d.vals[c]) = 0.0f;
395 std::get<1>(d.vals[c]) = 0.0f;
396 }
397
398 if (counterActive)
399 {
401 }
402 }
403
404 mPreviousSum = avgSum;
405 }
406
407 mCount++;
408 mCount %= mWindowSize;
409 }
410 }
411private:
412 float mThreshold = 0.01f;
413 bool mRMSMode = false;
414 float mPreviousSum = 1.0f;
415 int mWindowSize = 32;
416 int mPeakHoldTime = 1 << 16;
417 int mCount = 0;
418 float mWindowSizeMs = 5.f;
419 float mAttackTimeMs = 1.f;
420 float mDecayTimeMs = 100.f;
421 float mPeakHoldTimeMs = 100.f;
422 float mAttackTimeSamples = 1.0f;
423 float mDecayTimeSamples = DEFAULT_SAMPLE_RATE/10.0f;
424 std::array<float, MAXNC> mHeldPeaks = {0};
425 std::array<std::vector<float>, MAXNC> mBuffers;
426 std::array<int, MAXNC> mPeakHoldCounters;
427 std::array<EnvelopeFollower, MAXNC> mEnvFollowers;
428};
429
431template <int MAXNC = 1, int QUEUE_SIZE = 64, int MAXBUF = 128>
432class IBufferSender : public ISender<MAXNC, QUEUE_SIZE, std::array<float, MAXBUF>>
433{
434public:
435 using TDataPacket = std::array<float, MAXBUF>;
437 const double kNoThresholdDb = -100;
438
439 IBufferSender(double minThresholdDb = -90., int bufferSize = MAXBUF)
440 : TSender()
441 {
442 if (minThresholdDb <= kNoThresholdDb)
443 mThreshold = -1.0f;
444 else
445 mThreshold = DBToAmp(minThresholdDb);
446
447 SetBufferSize(bufferSize);
448 }
449
456 void ProcessBlock(sample** inputs, int nFrames, int ctrlTag = kNoTag, int nChans = MAXNC, int chanOffset = 0)
457 {
458 for (auto s = 0; s < nFrames; s++)
459 {
460 if (mBufCount == mBufferSize)
461 {
462 float sum = 0.0f;
463 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
464 {
465 sum += mRunningSum[c];
466 mRunningSum[c] = 0.0f;
467 }
468
469 if (sum > mThreshold || mPreviousSum > mThreshold)
470 {
471 mBuffer.ctrlTag = ctrlTag;
472 mBuffer.nChans = nChans;
473 mBuffer.chanOffset = chanOffset;
474 TSender::PushData(mBuffer);
475 }
476
477 mPreviousSum = sum;
478 mBufCount = 0;
479 }
480
481 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
482 {
483 const float inputSample = static_cast<float>(inputs[c][s]);
484 mBuffer.vals[c][mBufCount] = inputSample;
485 mRunningSum[c] += std::fabs(inputSample);
486 }
487
488 mBufCount++;
489 }
490 }
491
492 void SetBufferSize(int bufferSize)
493 {
494 assert(bufferSize > 0);
495 assert(bufferSize <= MAXBUF);
496
497 mBufferSize = bufferSize;
498 mBufCount = 0;
499 }
500
501 int GetBufferSize() const { return mBufferSize; }
502
503private:
505 int mBufCount = 0;
506 int mBufferSize = MAXBUF;
507 std::array<float, MAXNC> mRunningSum {0.};
508 float mPreviousSum = 1.f;
509 float mThreshold = 0.01f;
510};
511
513template <int MAXNC = 1, int QUEUE_SIZE = 64, int MAX_FFT_SIZE = 4096>
514class ISpectrumSender : public IBufferSender<MAXNC, QUEUE_SIZE, MAX_FFT_SIZE>
515{
516public:
517 using TDataPacket = std::array<float, MAX_FFT_SIZE>;
519
520 enum class EWindowType {
521 Hann = 0,
522 BlackmanHarris,
523 Hamming,
524 Flattop,
525 Rectangular
526 };
527
528 enum class EOutputType {
529 Complex = 0,
530 MagPhase,
531 };
532
533 ISpectrumSender(int fftSize = 1024, int overlap = 2, EWindowType window = EWindowType::Hann, EOutputType outputType = EOutputType::MagPhase, double minThresholdDb = -100.0)
534 : TBufferSender(minThresholdDb, fftSize / overlap)
535 , mWindowType(window)
536 , mOutputType(outputType)
537 {
538 WDL_fft_init();
539 SetFFTSizeAndOverlap(fftSize, overlap);
540 }
541
542 void SetFFTSize(int fftSize)
543 {
544 SetFFTSizeAndOverlap(fftSize, mOverlap);
545 }
546
547 void SetFFTSizeAndOverlap(int fftSize, int overlap)
548 {
549 mFFTSize = fftSize;
550 mOverlap = overlap;
551 int hopSize = fftSize / overlap;
552 TBufferSender::SetBufferSize(hopSize);
553 InitSTFTFrames();
554 CalculateWindow();
555 CalculateScalingFactors();
556 }
557
558 void SetWindowType(EWindowType windowType)
559 {
560 mWindowType = windowType;
561 CalculateWindow();
562 }
563
564 void SetOutputType(EOutputType outputType)
565 {
566 mOutputType = outputType;
567 }
568
569 void PrepareDataForUI(ISenderData<MAXNC, TDataPacket>& d) override
570 {
571 int hopSize = TBufferSender::GetBufferSize();
572
573 for (auto s = 0; s < hopSize; s++)
574 {
575 for (auto stftFrameIdx = 0; stftFrameIdx < mOverlap; stftFrameIdx++)
576 {
577 auto& stftFrame = mSTFTFrames[stftFrameIdx];
578
579 for (auto ch = 0; ch < MAXNC; ch++)
580 {
581 auto windowedValue = (float) d.vals[ch][s] * mWindow[stftFrame.pos];
582 stftFrame.bins[ch][stftFrame.pos].re = windowedValue;
583 stftFrame.bins[ch][stftFrame.pos].im = 0.0f;
584 }
585
586 stftFrame.pos++;
587
588 if (stftFrame.pos >= mFFTSize)
589 {
590 stftFrame.pos = 0;
591
592 for (auto ch = 0; ch < MAXNC; ch++)
593 {
594 Permute(ch, stftFrameIdx);
595 memcpy(d.vals[ch].data(), mSTFTOutput[ch].data(), mFFTSize * sizeof(float));
596 }
597 }
598 }
599 }
600 }
601
602 int GetFFTSize() const
603 {
604 return mFFTSize;
605 }
606
607 int GetOverlap() const
608 {
609 return mOverlap;
610 }
611
612
613private:
614 void InitSTFTFrames()
615 {
616 if (mSTFTFrames.size() != mOverlap)
617 {
618 mSTFTFrames.resize(mOverlap);
619 }
620
621 int hopSize = mFFTSize / mOverlap;
622
623 for (int i = 0; i < mOverlap; i++)
624 {
625 auto& frame = mSTFTFrames[i];
626 for (auto ch = 0; ch < MAXNC; ch++)
627 {
628 std::fill(frame.bins[ch].begin(), frame.bins[ch].end(), WDL_FFT_COMPLEX{0.0f, 0.0f});
629 }
630 // Stagger frame positions so FFTs are computed at different times
631 frame.pos = i * hopSize;
632 }
633
634 for (auto ch = 0; ch < MAXNC; ch++)
635 {
636 std::fill(mSTFTOutput[ch].begin(), mSTFTOutput[ch].end(), 0.0f);
637 }
638 }
639
640 void CalculateWindow()
641 {
642 const float M = static_cast<float>(mFFTSize - 1);
643
644 switch (mWindowType)
645 {
646 case EWindowType::Hann:
647 for (auto i = 0; i < mFFTSize; i++) { mWindow[i] = 0.5f * (1.0f - std::cos(PI * 2.0f * i / M)); }
648 break;
649 case EWindowType::BlackmanHarris:
650 for (auto i = 0; i < mFFTSize; i++) {
651 mWindow[i] = 0.35875 - (0.48829f * std::cos(2.0f * PI * i / M)) +
652 (0.14128f * std::cos(4.0f * PI * i / M)) -
653 (0.01168f * std::cos(6.0f * PI * i / M));
654 }
655 break;
656 case EWindowType::Hamming:
657 for (auto i = 0; i < mFFTSize; i++) { mWindow[i] = 0.54f - 0.46f * std::cos(2.0f * PI * i / M); }
658 break;
659 case EWindowType::Flattop:
660 for (auto i = 0; i < mFFTSize; i++) {
661 mWindow[i] = 0.21557895f - 0.41663158f * std::cos(2.0f * PI * i / M) +
662 0.277263158f * std::cos(4.0f * PI * i / M) -
663 0.083578947f * std::cos(6.0f * PI * i / M) +
664 0.006947368f * std::cos(8.0f * PI * i / M);
665 }
666 break;
667 case EWindowType::Rectangular:
668 std::fill(mWindow.begin(), mWindow.end(), 1.0f);
669 break;
670 default:
671 break;
672 }
673 }
674
675 void CalculateScalingFactors()
676 {
677 const float M = static_cast<float>(mFFTSize - 1);
678
679 auto scaling = 0.0f;
680
681 for (auto i = 0; i < mFFTSize; i++)
682 {
683 auto v = 0.5f * (1.0f - std::cos(2.0f * PI * i / M));
684 scaling += v;
685 }
686
687 mScalingFactor = scaling * scaling;
688 }
689
690 void Permute(int ch, int frameIdx)
691 {
692 WDL_fft(mSTFTFrames[frameIdx].bins[ch].data(), mFFTSize, false);
693
694 if (mOutputType == EOutputType::Complex)
695 {
696 auto nBins = mFFTSize / 2;
697 for (auto i = 0; i < nBins; ++i)
698 {
699 int sortIdx = WDL_fft_permute(mFFTSize, i);
700 mSTFTOutput[ch][i] = mSTFTFrames[frameIdx].bins[ch][sortIdx].re;
701 mSTFTOutput[ch][i + nBins] = mSTFTFrames[frameIdx].bins[ch][sortIdx].im;
702 }
703 }
704 else // magPhase
705 {
706 auto nBins = mFFTSize / 2;
707 for (auto i = 0; i < nBins; ++i)
708 {
709 int sortIdx = WDL_fft_permute(mFFTSize, i);
710 auto re = mSTFTFrames[frameIdx].bins[ch][sortIdx].re;
711 auto im = mSTFTFrames[frameIdx].bins[ch][sortIdx].im;
712 mSTFTOutput[ch][i] = std::sqrt(2.0f * (re * re + im * im) / mScalingFactor);
713 mSTFTOutput[ch][i + nBins] = std::atan2(im, re);
714 }
715 }
716 }
717
718 struct STFTFrame
719 {
720 int pos;
721 std::array<std::array<WDL_FFT_COMPLEX, MAX_FFT_SIZE>, MAXNC> bins;
722 };
723
724 int mFFTSize = 1024;
725 int mOverlap = 2;
726 EWindowType mWindowType;
727 EOutputType mOutputType;
728 std::array<float, MAX_FFT_SIZE> mWindow;
729 std::vector<STFTFrame> mSTFTFrames;
730 std::array<std::array<float, MAX_FFT_SIZE>, MAXNC> mSTFTOutput;
731 float mScalingFactor = 0.0f;
732};
733
734END_IPLUG_NAMESPACE
Include to get consistently named preprocessor macros for different platforms and logging functionali...
IBufferSender is a utility class which can be used to defer buffer data for sending to the GUI.
Definition: ISender.h:433
void ProcessBlock(sample **inputs, int nFrames, int ctrlTag=kNoTag, int nChans=MAXNC, int chanOffset=0)
Queue sample buffers into the sender, checking the data is over the required threshold.
Definition: ISender.h:456
This pure virtual interface delegates communication in both directions between a UI editor and someth...
virtual void SendControlMsgFromDelegate(int ctrlTag, int msgTag, int dataSize=0, const void *pData=nullptr)
SendControlMsgFromDelegate (Abbreviation: SCMFD) WARNING: should not be called on the realtime audio ...
IPeakAvgSender is a utility class which can be used to defer peak & avg/RMS data from sample buffers ...
Definition: ISender.h:198
void ProcessBlock(sample **inputs, int nFrames, int ctrlTag=kNoTag, int nChans=MAXNC, int chanOffset=0)
Queue peaks from sample buffers into the sender This can be called on the realtime audio thread.
Definition: ISender.h:294
IPeakSender is a utility class which can be used to defer peak data from sample buffers for sending t...
Definition: ISender.h:125
void ProcessBlock(sample **inputs, int nFrames, int ctrlTag=kNoTag, int nChans=MAXNC, int chanOffset=0)
Queue peaks from sample buffers into the sender This can be called on the realtime audio thread.
Definition: ISender.h:152
A lock-free SPSC queue used to transfer data between threads based on MLQueue.h by Randy Jones based ...
Definition: IPlugQueue.h:32
ISender is a utility class which can be used to defer data from the realtime audio processing and sen...
Definition: ISender.h:66
ISenderData< MAXNC, T > GetLastData()
Gets the last data item sent to the UI
Definition: ISender.h:113
virtual void PrepareDataForUI(ISenderData< MAXNC, T > &d)
This is called on the main thread and can be used to transform the data, e.g.
Definition: ISender.h:77
void PushData(const ISenderData< MAXNC, T > &d)
Pushes a data element onto the queue.
Definition: ISender.h:71
void TransmitDataToControlsWithTags(IEditorDelegate &dlg, const std::initializer_list< int > &ctrlTags)
This variation can be used if you need to supply multiple controls with the same ISenderData,...
Definition: ISender.h:97
void TransmitData(IEditorDelegate &dlg)
Pops elements off the queue and sends messages to controls.
Definition: ISender.h:81
ISpectrumSender is designed for sending Spectral Data from the plug-in to the UI.
Definition: ISender.h:515
static double DBToAmp(double dB)
Calculates gain from a given dB value.
ISenderData is used to represent a typed data packet, that may contain values for multiple channels.
Definition: ISender.h:35