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 }
91 }
92
96 void TransmitDataToControlsWithTags(IEditorDelegate& dlg, const std::initializer_list<int>& ctrlTags)
97 {
98 while(mQueue.ElementsAvailable())
99 {
101 mQueue.Pop(d);
102
103 for (auto tag : ctrlTags)
104 {
105 d.ctrlTag = tag;
106 dlg.SendControlMsgFromDelegate(tag, kUpdateMessage, sizeof(ISenderData<MAXNC, T>), (void*) &d);
107 }
108 }
109 }
110
111private:
112 IPlugQueue<ISenderData<MAXNC, T>> mQueue {QUEUE_SIZE};
113};
114
118template <int MAXNC = 1, int QUEUE_SIZE = 64>
119class IPeakSender : public ISender<MAXNC, QUEUE_SIZE, float>
120{
121public:
122 IPeakSender(double minThresholdDb = -90., float windowSizeMs = 5.0f)
124 , mThreshold(static_cast<float>(DBToAmp(minThresholdDb)))
125 {
126 Reset(DEFAULT_SAMPLE_RATE);
127 }
128
129 void Reset(double sampleRate)
130 {
131 SetWindowSizeMs(mWindowSizeMs, sampleRate);
132 std::fill(mPeaks.begin(), mPeaks.end(), 0.0f);
133 }
134
135 void SetWindowSizeMs(double timeMs, double sampleRate)
136 {
137 mWindowSizeMs = static_cast<float>(timeMs);
138 mWindowSize = static_cast<int>(timeMs * 0.001 * sampleRate);
139 }
140
147 void ProcessBlock(sample** inputs, int nFrames, int ctrlTag = kNoTag, int nChans = MAXNC, int chanOffset = 0)
148 {
149 for (auto s = 0; s < nFrames; s++)
150 {
151 if (mCount == 0)
152 {
153 ISenderData<MAXNC, float> d {ctrlTag, nChans, chanOffset};
154
155 float sum = 0.0f;
156
157 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
158 {
159 d.vals[c] = mPeaks[c] / mWindowSize;
160 mPeaks[c] = 0.0f;
161 sum += d.vals[c];
162 }
163
164 if (sum > mThreshold || mPreviousSum > mThreshold)
166
167 mPreviousSum = sum;
168 }
169
170 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
171 {
172 mPeaks[c] += std::fabs(static_cast<float>(inputs[c][s]));
173 }
174
175 mCount++;
176 mCount %= mWindowSize;
177 }
178 }
179private:
180 float mPreviousSum = 1.f;
181 float mThreshold = 0.01f;
182 float mWindowSizeMs = 5.0f;
183 int mWindowSize = 32;
184 int mCount = 0;
185 std::array<float, MAXNC> mPeaks = {0.0};
186};
187
191template <int MAXNC = 1, int QUEUE_SIZE = 64>
192class IPeakAvgSender : public ISender<MAXNC, QUEUE_SIZE, std::pair<float, float>>
193{
194public:
196 {
197 public:
198 inline float Process(float& input, float& attackTimeSamples, float& decayTimeSamples)
199 {
200 if (input > mPreviousOutput)
201 {
202 if (attackTimeSamples > 1.0f)
203 {
204 mPreviousOutput += (input - mPreviousOutput) / attackTimeSamples;
205 }
206 else
207 {
208 mPreviousOutput = input;
209 }
210 }
211 else if (input < mPreviousOutput)
212 {
213 if (decayTimeSamples > 1.0f)
214 {
215 mPreviousOutput += (input - mPreviousOutput) / decayTimeSamples;
216 }
217 else
218 {
219 mPreviousOutput = input;
220 }
221 }
222
223 denormal_fix(&mPreviousOutput);
224 return mPreviousOutput;
225 }
226
227 private:
228 float mPreviousOutput = 0.0f;
229 };
230
231 IPeakAvgSender(double minThresholdDb = -90.0, bool rmsMode = true, float windowSizeMs = 5.0f, float attackTimeMs = 1.0f, float decayTimeMs = 100.0f, float peakHoldTimeMs = 500.0f)
232 : ISender<MAXNC, QUEUE_SIZE, std::pair<float, float>>()
233 , mThreshold(static_cast<float>(DBToAmp(minThresholdDb)))
234 , mRMSMode(rmsMode)
235 , mWindowSizeMs(windowSizeMs)
236 , mAttackTimeMs(attackTimeMs)
237 , mDecayTimeMs(decayTimeMs)
238 , mPeakHoldTimeMs(peakHoldTimeMs)
239 {
240 Reset(DEFAULT_SAMPLE_RATE);
241 }
242
243 void Reset(double sampleRate)
244 {
245 SetWindowSizeMs(mWindowSizeMs, sampleRate);
246 SetAttackTimeMs(mAttackTimeMs, sampleRate);
247 SetDecayTimeMs(mDecayTimeMs, sampleRate);
248 SetPeakHoldTimeMs(mPeakHoldTimeMs, sampleRate);
249 std::fill(mHeldPeaks.begin(), mHeldPeaks.end(), 0.0f);
250 }
251
252 void SetAttackTimeMs(double timeMs, double sampleRate)
253 {
254 mAttackTimeMs = static_cast<float>(timeMs);
255 mAttackTimeSamples = static_cast<float>(timeMs * 0.001 * (sampleRate / double(mWindowSize)));
256 }
257
258 void SetDecayTimeMs(double timeMs, double sampleRate)
259 {
260 mDecayTimeMs = static_cast<float>(timeMs);
261 mDecayTimeSamples = static_cast<float>(timeMs * 0.001 * (sampleRate / mWindowSize));
262 }
263
264 void SetWindowSizeMs(double timeMs, double sampleRate)
265 {
266 mWindowSizeMs = static_cast<float>(timeMs);
267 mWindowSize = static_cast<int>(timeMs * 0.001 * sampleRate);
268
269 for (auto i=0; i<MAXNC; i++)
270 {
271 mBuffers[i].resize(mWindowSize);
272 std::fill(mBuffers[i].begin(), mBuffers[i].end(), 0.0f);
273 }
274 }
275
276 void SetPeakHoldTimeMs(double timeMs, double sampleRate)
277 {
278 mPeakHoldTimeMs = static_cast<float>(timeMs);
279 mPeakHoldTime = static_cast<int>(timeMs * 0.001 * sampleRate);
280 std::fill(mPeakHoldCounters.begin(), mPeakHoldCounters.end(), mPeakHoldTime);
281 }
282
289 void ProcessBlock(sample** inputs, int nFrames, int ctrlTag = kNoTag, int nChans = MAXNC, int chanOffset = 0)
290 {
291 for (auto s = 0; s < nFrames; s++)
292 {
293 int windowPos = s % mWindowSize;
294
295 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
296 {
297 mBuffers[c][windowPos] = static_cast<float>(inputs[c][s]);
298 }
299
300 if (mCount == 0)
301 {
302 ISenderData<MAXNC, std::pair<float, float>> d {ctrlTag, nChans, chanOffset};
303
304 auto avgSum = 0.0f;
305
306 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
307 {
308 auto peakVal = 0.0f;
309 auto avgVal = 0.0f;
310#if defined OS_IOS || defined OS_MAC
311 vDSP_vabs(mBuffers[c].data(), 1, mBuffers[c].data(), 1, mWindowSize);
312 vDSP_maxv(mBuffers[c].data(), 1, &peakVal, mWindowSize);
313
314 if (mRMSMode)
315 {
316 vDSP_rmsqv(mBuffers[c].data(), 1, &avgVal, mWindowSize);
317 }
318 else
319 {
320 vDSP_meanv(mBuffers[c].data(), 1, &avgVal, mWindowSize);
321 }
322#else
323 for (auto i=0; i<mWindowSize; i++)
324 {
325 auto absVal = std::fabs(mBuffers[c][i]);
326
327 if (absVal > peakVal)
328 {
329 peakVal = absVal;
330 }
331
332 if (mRMSMode)
333 {
334 absVal = absVal * absVal;
335 }
336
337 avgVal += absVal;
338 }
339
340 avgVal /= static_cast<float>(mWindowSize);
341
342 if (mRMSMode)
343 {
344 avgVal = std::sqrt(avgVal);
345 }
346#endif
347
348 // set peak-hold value
349 if (mPeakHoldCounters[c] <= 0)
350 {
351 mHeldPeaks[c] = 0.0f;
352 }
353
354 if (mHeldPeaks[c] < peakVal)
355 {
356 mHeldPeaks[c] = peakVal;
357 mPeakHoldCounters[c] = mPeakHoldTime;
358 }
359 else
360 {
361 if (mPeakHoldCounters[c] > 0)
362 {
363 mPeakHoldCounters[c] -= mWindowSize;
364 }
365 }
366
367 std::get<0>(d.vals[c]) = mHeldPeaks[c];
368
369 // set avg value
370 auto smoothedAvg = mEnvFollowers[c].Process(avgVal, mAttackTimeSamples, mDecayTimeSamples);
371 std::get<1>(d.vals[c]) = smoothedAvg;
372
373 avgSum += smoothedAvg;
374 }
375
376 if (mPreviousSum > mThreshold)
377 {
379 }
380 else
381 {
382 // This makes sure that the data is still pushed if
383 // peakholds are still active
384 bool counterActive = false;
385
386 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
387 {
388 counterActive &= mPeakHoldCounters[c] > 0;
389 std::get<0>(d.vals[c]) = 0.0f;
390 std::get<1>(d.vals[c]) = 0.0f;
391 }
392
393 if (counterActive)
394 {
396 }
397 }
398
399 mPreviousSum = avgSum;
400 }
401
402 mCount++;
403 mCount %= mWindowSize;
404 }
405 }
406private:
407 float mThreshold = 0.01f;
408 bool mRMSMode = false;
409 float mPreviousSum = 1.0f;
410 int mWindowSize = 32;
411 int mPeakHoldTime = 1 << 16;
412 int mCount = 0;
413 float mWindowSizeMs = 5.f;
414 float mAttackTimeMs = 1.f;
415 float mDecayTimeMs = 100.f;
416 float mPeakHoldTimeMs = 100.f;
417 float mAttackTimeSamples = 1.0f;
418 float mDecayTimeSamples = DEFAULT_SAMPLE_RATE/10.0f;
419 std::array<float, MAXNC> mHeldPeaks = {0};
420 std::array<std::vector<float>, MAXNC> mBuffers;
421 std::array<int, MAXNC> mPeakHoldCounters;
422 std::array<EnvelopeFollower, MAXNC> mEnvFollowers;
423};
424
426template <int MAXNC = 1, int QUEUE_SIZE = 64, int MAXBUF = 128>
427class IBufferSender : public ISender<MAXNC, QUEUE_SIZE, std::array<float, MAXBUF>>
428{
429public:
430 using TDataPacket = std::array<float, MAXBUF>;
432 const double kNoThresholdDb = -100;
433
434 IBufferSender(double minThresholdDb = -90., int bufferSize = MAXBUF)
435 : TSender()
436 {
437 if (minThresholdDb <= kNoThresholdDb)
438 mThreshold = -1.0f;
439 else
440 mThreshold = DBToAmp(minThresholdDb);
441
442 SetBufferSize(bufferSize);
443 }
444
451 void ProcessBlock(sample** inputs, int nFrames, int ctrlTag = kNoTag, int nChans = MAXNC, int chanOffset = 0)
452 {
453 for (auto s = 0; s < nFrames; s++)
454 {
455 if (mBufCount == mBufferSize)
456 {
457 float sum = 0.0f;
458 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
459 {
460 sum += mRunningSum[c];
461 mRunningSum[c] = 0.0f;
462 }
463
464 if (sum > mThreshold || mPreviousSum > mThreshold)
465 {
466 mBuffer.ctrlTag = ctrlTag;
467 mBuffer.nChans = nChans;
468 mBuffer.chanOffset = chanOffset;
469 TSender::PushData(mBuffer);
470 }
471
472 mPreviousSum = sum;
473 mBufCount = 0;
474 }
475
476 for (auto c = chanOffset; c < (chanOffset + nChans); c++)
477 {
478 const float inputSample = static_cast<float>(inputs[c][s]);
479 mBuffer.vals[c][mBufCount] = inputSample;
480 mRunningSum[c] += std::fabs(inputSample);
481 }
482
483 mBufCount++;
484 }
485 }
486
487 void SetBufferSize(int bufferSize)
488 {
489 assert(bufferSize > 0);
490 assert(bufferSize <= MAXBUF);
491
492 mBufferSize = bufferSize;
493 mBufCount = 0;
494 }
495
496 int GetBufferSize() const { return mBufferSize; }
497
498private:
500 int mBufCount = 0;
501 int mBufferSize = MAXBUF;
502 std::array<float, MAXNC> mRunningSum {0.};
503 float mPreviousSum = 1.f;
504 float mThreshold = 0.01f;
505};
506
508template <int MAXNC = 1, int QUEUE_SIZE = 64, int MAX_FFT_SIZE = 4096>
509class ISpectrumSender : public IBufferSender<MAXNC, QUEUE_SIZE, MAX_FFT_SIZE>
510{
511public:
512 using TDataPacket = std::array<float, MAX_FFT_SIZE>;
514
515 enum class EWindowType {
516 Hann = 0,
517 BlackmanHarris,
518 Hamming,
519 Flattop,
520 Rectangular
521 };
522
523 enum class EOutputType {
524 Complex = 0,
525 MagPhase,
526 };
527
528 ISpectrumSender(int fftSize = 1024, int overlap = 2, EWindowType window = EWindowType::Hann, EOutputType outputType = EOutputType::MagPhase, double minThresholdDb = -100.0)
529 : TBufferSender(minThresholdDb, fftSize)
530 , mWindowType(window)
531 , mOutputType(outputType)
532 {
533 WDL_fft_init();
534 SetFFTSizeAndOverlap(fftSize, overlap);
535 }
536
537 void SetFFTSize(int fftSize)
538 {
539 TBufferSender::SetBufferSize(fftSize);
540 SetFFTSize();
541 CalculateWindow();
542 CalculateScalingFactors();
543 }
544
545 void SetFFTSizeAndOverlap(int fftSize, int overlap)
546 {
547 mOverlap = overlap;
548 TBufferSender::SetBufferSize(fftSize);
549 SetFFTSize();
550 CalculateWindow();
551 CalculateScalingFactors();
552 }
553
554 void SetWindowType(EWindowType windowType)
555 {
556 mWindowType = windowType;
557 CalculateWindow();
558 }
559
560 void SetOutputType(EOutputType outputType)
561 {
562 mOutputType = outputType;
563 }
564
565 void PrepareDataForUI(ISenderData<MAXNC, TDataPacket>& d) override
566 {
567 auto fftSize = TBufferSender::GetBufferSize();
568
569 for (auto s = 0; s < fftSize; s++)
570 {
571 for (auto stftFrameIdx = 0; stftFrameIdx < mOverlap; stftFrameIdx++)
572 {
573 auto& stftFrame = mSTFTFrames[stftFrameIdx];
574
575 for (auto ch = 0; ch < MAXNC; ch++)
576 {
577 auto windowedValue = (float) d.vals[ch][s] * mWindow[stftFrame.pos];
578 stftFrame.bins[ch][stftFrame.pos].re = windowedValue;
579 stftFrame.bins[ch][stftFrame.pos].im = 0.0f;
580 }
581
582 stftFrame.pos++;
583
584 if (stftFrame.pos >= fftSize)
585 {
586 stftFrame.pos = 0;
587
588 for (auto ch = 0; ch < MAXNC; ch++)
589 {
590 Permute(ch, stftFrameIdx);
591 memcpy(d.vals[ch].data(), mSTFTOutput[ch].data(), fftSize * sizeof(float));
592 }
593 }
594 }
595 }
596 }
597
598 int GetFFTSize() const
599 {
600 return TBufferSender::GetBufferSize();
601 }
602
603 int GetOverlap() const
604 {
605 return mOverlap;
606 }
607
608private:
609 void SetFFTSize()
610 {
611 if (mSTFTFrames.size() != mOverlap)
612 {
613 mSTFTFrames.resize(mOverlap);
614 }
615
616 for (auto&& frame : mSTFTFrames)
617 {
618 for (auto ch = 0; ch < MAXNC; ch++)
619 {
620 std::fill(frame.bins[ch].begin(), frame.bins[ch].end(), WDL_FFT_COMPLEX{0.0f, 0.0f});
621 }
622
623 frame.pos = 0;
624 }
625
626 for (auto ch = 0; ch < MAXNC; ch++)
627 {
628 std::fill(mSTFTOutput[ch].begin(), mSTFTOutput[ch].end(), 0.0f);
629 }
630 }
631
632 void CalculateWindow()
633 {
634 const auto fftSize = TBufferSender::GetBufferSize();
635
636 const float M = static_cast<float>(fftSize - 1);
637
638 switch (mWindowType)
639 {
640 case EWindowType::Hann:
641 for (auto i = 0; i < fftSize; i++) { mWindow[i] = 0.5f * (1.0f - std::cos(PI * 2.0f * i / M)); }
642 break;
643 case EWindowType::BlackmanHarris:
644 for (auto i = 0; i < fftSize; i++) {
645 mWindow[i] = 0.35875 - (0.48829f * std::cos(2.0f * PI * i / M)) +
646 (0.14128f * std::cos(4.0f * PI * i / M)) -
647 (0.01168f * std::cos(6.0f * PI * i / M));
648 }
649 break;
650 case EWindowType::Hamming:
651 for (auto i = 0; i < fftSize; i++) { mWindow[i] = 0.54f - 0.46f * std::cos(2.0f * PI * i / M); }
652 break;
653 case EWindowType::Flattop:
654 for (auto i = 0; i < fftSize; i++) {
655 mWindow[i] = 0.21557895f - 0.41663158f * std::cos(2.0f * PI * i / M) +
656 0.277263158f * std::cos(4.0f * PI * i / M) -
657 0.083578947f * std::cos(6.0f * PI * i / M) +
658 0.006947368f * std::cos(8.0f * PI * i / M);
659 }
660 break;
661 case EWindowType::Rectangular:
662 std::fill(mWindow.begin(), mWindow.end(), 1.0f);
663 break;
664 default:
665 break;
666 }
667 }
668
669 void CalculateScalingFactors()
670 {
671 const auto fftSize = TBufferSender::GetBufferSize();
672 const float M = static_cast<float>(fftSize - 1);
673
674 auto scaling = 0.0f;
675
676 for (auto i = 0; i < fftSize; i++)
677 {
678 auto v = 0.5f * (1.0f - std::cos(2.0f * PI * i / M));
679 scaling += v;
680 }
681
682 mScalingFactor = scaling * scaling;
683 }
684
685 void Permute(int ch, int frameIdx)
686 {
687 const auto fftSize = TBufferSender::GetBufferSize();
688 WDL_fft(mSTFTFrames[frameIdx].bins[ch].data(), fftSize, false);
689
690 if (mOutputType == EOutputType::Complex)
691 {
692 auto nBins = fftSize/2;
693 for (auto i = 0; i < nBins; ++i)
694 {
695 int sortIdx = WDL_fft_permute(fftSize, i);
696 mSTFTOutput[ch][i] = mSTFTFrames[frameIdx].bins[ch][sortIdx].re;
697 mSTFTOutput[ch][i + nBins] = mSTFTFrames[frameIdx].bins[ch][sortIdx].im;
698 }
699 }
700 else // magPhase
701 {
702 auto nBins = fftSize/2;
703 for (auto i = 0; i < nBins; ++i)
704 {
705 int sortIdx = WDL_fft_permute(fftSize, i);
706 auto re = mSTFTFrames[frameIdx].bins[ch][sortIdx].re;
707 auto im = mSTFTFrames[frameIdx].bins[ch][sortIdx].im;
708 mSTFTOutput[ch][i] = std::sqrt(2.0f * (re * re + im * im) / mScalingFactor);
709 mSTFTOutput[ch][i + nBins] = std::atan2(im, re);
710 }
711 }
712 }
713
714 struct STFTFrame
715 {
716 int pos;
717 std::array<std::array<WDL_FFT_COMPLEX, MAX_FFT_SIZE>, MAXNC> bins;
718 };
719
720 int mOverlap = 2;
721 EWindowType mWindowType;
722 EOutputType mOutputType;
723 std::array<float, MAX_FFT_SIZE> mWindow;
724 std::vector<STFTFrame> mSTFTFrames;
725 std::array<std::array<float, MAX_FFT_SIZE>, MAXNC> mSTFTOutput;
726 float mScalingFactor = 0.0f;
727};
728
729END_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:428
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:451
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:193
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:289
IPeakSender is a utility class which can be used to defer peak data from sample buffers for sending t...
Definition: ISender.h:120
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:147
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
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:96
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:510
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