26#if defined OS_IOS || defined OS_MAC
27#include <Accelerate/Accelerate.h>
33template <
int MAXNC = 1,
typename T =
float>
39 std::array<T, MAXNC> vals;
46 ISenderData(
int ctrlTag,
int nChans,
int chanOffset)
49 , chanOffset(chanOffset)
54 ISenderData(
int ctrlTag,
const std::array<T, MAXNC>& vals,
int nChans = MAXNC,
int chanOffset = 0)
57 , chanOffset(chanOffset)
64template <
int MAXNC = 1,
int QUEUE_SIZE = 64,
typename T =
float>
68 static constexpr int kUpdateMessage = 0;
83 while (mQueue.ElementsAvailable())
87 assert(d.ctrlTag != kNoTag &&
"You must supply a control tag");
99 while(mQueue.ElementsAvailable())
104 for (
auto tag : ctrlTags)
123template <
int MAXNC = 1,
int QUEUE_SIZE = 64>
127 IPeakSender(
double minThresholdDb = -90.,
float windowSizeMs = 5.0f)
129 , mThreshold(
static_cast<float>(
DBToAmp(minThresholdDb)))
131 Reset(DEFAULT_SAMPLE_RATE);
134 void Reset(
double sampleRate)
136 SetWindowSizeMs(mWindowSizeMs, sampleRate);
137 std::fill(mPeaks.begin(), mPeaks.end(), 0.0f);
140 void SetWindowSizeMs(
double timeMs,
double sampleRate)
142 mWindowSizeMs =
static_cast<float>(timeMs);
143 mWindowSize =
static_cast<int>(timeMs * 0.001 * sampleRate);
152 void ProcessBlock(sample** inputs,
int nFrames,
int ctrlTag = kNoTag,
int nChans = MAXNC,
int chanOffset = 0)
154 for (
auto s = 0; s < nFrames; s++)
162 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
164 d.vals[c] = mPeaks[c] / mWindowSize;
169 if (sum > mThreshold || mPreviousSum > mThreshold)
175 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
177 mPeaks[c] += std::fabs(
static_cast<float>(inputs[c][s]));
181 mCount %= mWindowSize;
185 float mPreviousSum = 1.f;
186 float mThreshold = 0.01f;
187 float mWindowSizeMs = 5.0f;
188 int mWindowSize = 32;
190 std::array<float, MAXNC> mPeaks = {0.0};
196template <
int MAXNC = 1,
int QUEUE_SIZE = 64>
203 inline float Process(
float& input,
float& attackTimeSamples,
float& decayTimeSamples)
205 if (input > mPreviousOutput)
207 if (attackTimeSamples > 1.0f)
209 mPreviousOutput += (input - mPreviousOutput) / attackTimeSamples;
213 mPreviousOutput = input;
216 else if (input < mPreviousOutput)
218 if (decayTimeSamples > 1.0f)
220 mPreviousOutput += (input - mPreviousOutput) / decayTimeSamples;
224 mPreviousOutput = input;
228 denormal_fix(&mPreviousOutput);
229 return mPreviousOutput;
233 float mPreviousOutput = 0.0f;
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)))
240 , mWindowSizeMs(windowSizeMs)
241 , mAttackTimeMs(attackTimeMs)
242 , mDecayTimeMs(decayTimeMs)
243 , mPeakHoldTimeMs(peakHoldTimeMs)
245 Reset(DEFAULT_SAMPLE_RATE);
248 void Reset(
double sampleRate)
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);
257 void SetAttackTimeMs(
double timeMs,
double sampleRate)
259 mAttackTimeMs =
static_cast<float>(timeMs);
260 mAttackTimeSamples =
static_cast<float>(timeMs * 0.001 * (sampleRate / double(mWindowSize)));
263 void SetDecayTimeMs(
double timeMs,
double sampleRate)
265 mDecayTimeMs =
static_cast<float>(timeMs);
266 mDecayTimeSamples =
static_cast<float>(timeMs * 0.001 * (sampleRate / mWindowSize));
269 void SetWindowSizeMs(
double timeMs,
double sampleRate)
271 mWindowSizeMs =
static_cast<float>(timeMs);
272 mWindowSize =
static_cast<int>(timeMs * 0.001 * sampleRate);
274 for (
auto i=0; i<MAXNC; i++)
276 mBuffers[i].resize(mWindowSize);
277 std::fill(mBuffers[i].begin(), mBuffers[i].end(), 0.0f);
281 void SetPeakHoldTimeMs(
double timeMs,
double sampleRate)
283 mPeakHoldTimeMs =
static_cast<float>(timeMs);
284 mPeakHoldTime =
static_cast<int>(timeMs * 0.001 * sampleRate);
285 std::fill(mPeakHoldCounters.begin(), mPeakHoldCounters.end(), mPeakHoldTime);
294 void ProcessBlock(sample** inputs,
int nFrames,
int ctrlTag = kNoTag,
int nChans = MAXNC,
int chanOffset = 0)
296 for (
auto s = 0; s < nFrames; s++)
298 int windowPos = s % mWindowSize;
300 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
302 mBuffers[c][windowPos] =
static_cast<float>(inputs[c][s]);
311 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
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);
321 vDSP_rmsqv(mBuffers[c].data(), 1, &avgVal, mWindowSize);
325 vDSP_meanv(mBuffers[c].data(), 1, &avgVal, mWindowSize);
328 for (
auto i=0; i<mWindowSize; i++)
330 auto absVal = std::fabs(mBuffers[c][i]);
332 if (absVal > peakVal)
339 absVal = absVal * absVal;
345 avgVal /=
static_cast<float>(mWindowSize);
349 avgVal = std::sqrt(avgVal);
354 if (mPeakHoldCounters[c] <= 0)
356 mHeldPeaks[c] = 0.0f;
359 if (mHeldPeaks[c] < peakVal)
361 mHeldPeaks[c] = peakVal;
362 mPeakHoldCounters[c] = mPeakHoldTime;
366 if (mPeakHoldCounters[c] > 0)
368 mPeakHoldCounters[c] -= mWindowSize;
372 std::get<0>(d.vals[c]) = mHeldPeaks[c];
375 auto smoothedAvg = mEnvFollowers[c].Process(avgVal, mAttackTimeSamples, mDecayTimeSamples);
376 std::get<1>(d.vals[c]) = smoothedAvg;
378 avgSum += smoothedAvg;
381 if (mPreviousSum > mThreshold)
389 bool counterActive =
false;
391 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
393 counterActive &= mPeakHoldCounters[c] > 0;
394 std::get<0>(d.vals[c]) = 0.0f;
395 std::get<1>(d.vals[c]) = 0.0f;
404 mPreviousSum = avgSum;
408 mCount %= mWindowSize;
412 float mThreshold = 0.01f;
413 bool mRMSMode =
false;
414 float mPreviousSum = 1.0f;
415 int mWindowSize = 32;
416 int mPeakHoldTime = 1 << 16;
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;
431template <
int MAXNC = 1,
int QUEUE_SIZE = 64,
int MAXBUF = 128>
435 using TDataPacket = std::array<float, MAXBUF>;
437 const double kNoThresholdDb = -100;
439 IBufferSender(
double minThresholdDb = -90.,
int bufferSize = MAXBUF)
442 if (minThresholdDb <= kNoThresholdDb)
445 mThreshold =
DBToAmp(minThresholdDb);
447 SetBufferSize(bufferSize);
456 void ProcessBlock(sample** inputs,
int nFrames,
int ctrlTag = kNoTag,
int nChans = MAXNC,
int chanOffset = 0)
458 for (
auto s = 0; s < nFrames; s++)
460 if (mBufCount == mBufferSize)
463 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
465 sum += mRunningSum[c];
466 mRunningSum[c] = 0.0f;
469 if (sum > mThreshold || mPreviousSum > mThreshold)
471 mBuffer.ctrlTag = ctrlTag;
472 mBuffer.nChans = nChans;
473 mBuffer.chanOffset = chanOffset;
481 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
483 const float inputSample =
static_cast<float>(inputs[c][s]);
484 mBuffer.vals[c][mBufCount] = inputSample;
485 mRunningSum[c] += std::fabs(inputSample);
492 void SetBufferSize(
int bufferSize)
494 assert(bufferSize > 0);
495 assert(bufferSize <= MAXBUF);
497 mBufferSize = bufferSize;
501 int GetBufferSize()
const {
return mBufferSize; }
506 int mBufferSize = MAXBUF;
507 std::array<float, MAXNC> mRunningSum {0.};
508 float mPreviousSum = 1.f;
509 float mThreshold = 0.01f;
513template <
int MAXNC = 1,
int QUEUE_SIZE = 64,
int MAX_FFT_SIZE = 4096>
517 using TDataPacket = std::array<float, MAX_FFT_SIZE>;
520 enum class EWindowType {
528 enum class EOutputType {
533 ISpectrumSender(
int fftSize = 1024,
int overlap = 2, EWindowType window = EWindowType::Hann, EOutputType outputType = EOutputType::MagPhase,
double minThresholdDb = -100.0)
535 , mWindowType(window)
536 , mOutputType(outputType)
539 SetFFTSizeAndOverlap(fftSize, overlap);
542 void SetFFTSize(
int fftSize)
544 SetFFTSizeAndOverlap(fftSize, mOverlap);
547 void SetFFTSizeAndOverlap(
int fftSize,
int overlap)
551 int hopSize = fftSize / overlap;
552 TBufferSender::SetBufferSize(hopSize);
555 CalculateScalingFactors();
558 void SetWindowType(EWindowType windowType)
560 mWindowType = windowType;
564 void SetOutputType(EOutputType outputType)
566 mOutputType = outputType;
571 int hopSize = TBufferSender::GetBufferSize();
573 for (
auto s = 0; s < hopSize; s++)
575 for (
auto stftFrameIdx = 0; stftFrameIdx < mOverlap; stftFrameIdx++)
577 auto& stftFrame = mSTFTFrames[stftFrameIdx];
579 for (
auto ch = 0; ch < MAXNC; ch++)
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;
588 if (stftFrame.pos >= mFFTSize)
592 for (
auto ch = 0; ch < MAXNC; ch++)
594 Permute(ch, stftFrameIdx);
595 memcpy(d.vals[ch].data(), mSTFTOutput[ch].data(), mFFTSize *
sizeof(
float));
602 int GetFFTSize()
const
607 int GetOverlap()
const
614 void InitSTFTFrames()
616 if (mSTFTFrames.size() != mOverlap)
618 mSTFTFrames.resize(mOverlap);
621 int hopSize = mFFTSize / mOverlap;
623 for (
int i = 0; i < mOverlap; i++)
625 auto& frame = mSTFTFrames[i];
626 for (
auto ch = 0; ch < MAXNC; ch++)
628 std::fill(frame.bins[ch].begin(), frame.bins[ch].end(), WDL_FFT_COMPLEX{0.0f, 0.0f});
631 frame.pos = i * hopSize;
634 for (
auto ch = 0; ch < MAXNC; ch++)
636 std::fill(mSTFTOutput[ch].begin(), mSTFTOutput[ch].end(), 0.0f);
640 void CalculateWindow()
642 const float M =
static_cast<float>(mFFTSize - 1);
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)); }
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));
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); }
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);
667 case EWindowType::Rectangular:
668 std::fill(mWindow.begin(), mWindow.end(), 1.0f);
675 void CalculateScalingFactors()
677 const float M =
static_cast<float>(mFFTSize - 1);
681 for (
auto i = 0; i < mFFTSize; i++)
683 auto v = 0.5f * (1.0f - std::cos(2.0f * PI * i / M));
687 mScalingFactor = scaling * scaling;
690 void Permute(
int ch,
int frameIdx)
692 WDL_fft(mSTFTFrames[frameIdx].bins[ch].data(), mFFTSize,
false);
694 if (mOutputType == EOutputType::Complex)
696 auto nBins = mFFTSize / 2;
697 for (
auto i = 0; i < nBins; ++i)
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;
706 auto nBins = mFFTSize / 2;
707 for (
auto i = 0; i < nBins; ++i)
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);
721 std::array<std::array<WDL_FFT_COMPLEX, MAX_FFT_SIZE>, MAXNC> bins;
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;
IBufferSender is a utility class which can be used to defer buffer data for sending to the GUI.
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.
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 ...
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.
IPeakSender is a utility class which can be used to defer peak data from sample buffers for sending t...
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.
A lock-free SPSC queue used to transfer data between threads based on MLQueue.h by Randy Jones based ...
ISender is a utility class which can be used to defer data from the realtime audio processing and sen...
ISenderData< MAXNC, T > GetLastData()
Gets the last data item sent to the UI
virtual void PrepareDataForUI(ISenderData< MAXNC, T > &d)
This is called on the main thread and can be used to transform the data, e.g.
void PushData(const ISenderData< MAXNC, T > &d)
Pushes a data element onto the queue.
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,...
void TransmitData(IEditorDelegate &dlg)
Pops elements off the queue and sends messages to controls.
ISpectrumSender is designed for sending Spectral Data from the plug-in to the UI.
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.