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");
98 while(mQueue.ElementsAvailable())
103 for (
auto tag : ctrlTags)
118template <
int MAXNC = 1,
int QUEUE_SIZE = 64>
122 IPeakSender(
double minThresholdDb = -90.,
float windowSizeMs = 5.0f)
124 , mThreshold(
static_cast<float>(
DBToAmp(minThresholdDb)))
126 Reset(DEFAULT_SAMPLE_RATE);
129 void Reset(
double sampleRate)
131 SetWindowSizeMs(mWindowSizeMs, sampleRate);
132 std::fill(mPeaks.begin(), mPeaks.end(), 0.0f);
135 void SetWindowSizeMs(
double timeMs,
double sampleRate)
137 mWindowSizeMs =
static_cast<float>(timeMs);
138 mWindowSize =
static_cast<int>(timeMs * 0.001 * sampleRate);
147 void ProcessBlock(sample** inputs,
int nFrames,
int ctrlTag = kNoTag,
int nChans = MAXNC,
int chanOffset = 0)
149 for (
auto s = 0; s < nFrames; s++)
157 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
159 d.vals[c] = mPeaks[c] / mWindowSize;
164 if (sum > mThreshold || mPreviousSum > mThreshold)
170 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
172 mPeaks[c] += std::fabs(
static_cast<float>(inputs[c][s]));
176 mCount %= mWindowSize;
180 float mPreviousSum = 1.f;
181 float mThreshold = 0.01f;
182 float mWindowSizeMs = 5.0f;
183 int mWindowSize = 32;
185 std::array<float, MAXNC> mPeaks = {0.0};
191template <
int MAXNC = 1,
int QUEUE_SIZE = 64>
198 inline float Process(
float& input,
float& attackTimeSamples,
float& decayTimeSamples)
200 if (input > mPreviousOutput)
202 if (attackTimeSamples > 1.0f)
204 mPreviousOutput += (input - mPreviousOutput) / attackTimeSamples;
208 mPreviousOutput = input;
211 else if (input < mPreviousOutput)
213 if (decayTimeSamples > 1.0f)
215 mPreviousOutput += (input - mPreviousOutput) / decayTimeSamples;
219 mPreviousOutput = input;
223 denormal_fix(&mPreviousOutput);
224 return mPreviousOutput;
228 float mPreviousOutput = 0.0f;
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)))
235 , mWindowSizeMs(windowSizeMs)
236 , mAttackTimeMs(attackTimeMs)
237 , mDecayTimeMs(decayTimeMs)
238 , mPeakHoldTimeMs(peakHoldTimeMs)
240 Reset(DEFAULT_SAMPLE_RATE);
243 void Reset(
double sampleRate)
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);
252 void SetAttackTimeMs(
double timeMs,
double sampleRate)
254 mAttackTimeMs =
static_cast<float>(timeMs);
255 mAttackTimeSamples =
static_cast<float>(timeMs * 0.001 * (sampleRate / double(mWindowSize)));
258 void SetDecayTimeMs(
double timeMs,
double sampleRate)
260 mDecayTimeMs =
static_cast<float>(timeMs);
261 mDecayTimeSamples =
static_cast<float>(timeMs * 0.001 * (sampleRate / mWindowSize));
264 void SetWindowSizeMs(
double timeMs,
double sampleRate)
266 mWindowSizeMs =
static_cast<float>(timeMs);
267 mWindowSize =
static_cast<int>(timeMs * 0.001 * sampleRate);
269 for (
auto i=0; i<MAXNC; i++)
271 mBuffers[i].resize(mWindowSize);
272 std::fill(mBuffers[i].begin(), mBuffers[i].end(), 0.0f);
276 void SetPeakHoldTimeMs(
double timeMs,
double sampleRate)
278 mPeakHoldTimeMs =
static_cast<float>(timeMs);
279 mPeakHoldTime =
static_cast<int>(timeMs * 0.001 * sampleRate);
280 std::fill(mPeakHoldCounters.begin(), mPeakHoldCounters.end(), mPeakHoldTime);
289 void ProcessBlock(sample** inputs,
int nFrames,
int ctrlTag = kNoTag,
int nChans = MAXNC,
int chanOffset = 0)
291 for (
auto s = 0; s < nFrames; s++)
293 int windowPos = s % mWindowSize;
295 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
297 mBuffers[c][windowPos] =
static_cast<float>(inputs[c][s]);
306 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
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);
316 vDSP_rmsqv(mBuffers[c].data(), 1, &avgVal, mWindowSize);
320 vDSP_meanv(mBuffers[c].data(), 1, &avgVal, mWindowSize);
323 for (
auto i=0; i<mWindowSize; i++)
325 auto absVal = std::fabs(mBuffers[c][i]);
327 if (absVal > peakVal)
334 absVal = absVal * absVal;
340 avgVal /=
static_cast<float>(mWindowSize);
344 avgVal = std::sqrt(avgVal);
349 if (mPeakHoldCounters[c] <= 0)
351 mHeldPeaks[c] = 0.0f;
354 if (mHeldPeaks[c] < peakVal)
356 mHeldPeaks[c] = peakVal;
357 mPeakHoldCounters[c] = mPeakHoldTime;
361 if (mPeakHoldCounters[c] > 0)
363 mPeakHoldCounters[c] -= mWindowSize;
367 std::get<0>(d.vals[c]) = mHeldPeaks[c];
370 auto smoothedAvg = mEnvFollowers[c].Process(avgVal, mAttackTimeSamples, mDecayTimeSamples);
371 std::get<1>(d.vals[c]) = smoothedAvg;
373 avgSum += smoothedAvg;
376 if (mPreviousSum > mThreshold)
384 bool counterActive =
false;
386 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
388 counterActive &= mPeakHoldCounters[c] > 0;
389 std::get<0>(d.vals[c]) = 0.0f;
390 std::get<1>(d.vals[c]) = 0.0f;
399 mPreviousSum = avgSum;
403 mCount %= mWindowSize;
407 float mThreshold = 0.01f;
408 bool mRMSMode =
false;
409 float mPreviousSum = 1.0f;
410 int mWindowSize = 32;
411 int mPeakHoldTime = 1 << 16;
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;
426template <
int MAXNC = 1,
int QUEUE_SIZE = 64,
int MAXBUF = 128>
430 using TDataPacket = std::array<float, MAXBUF>;
432 const double kNoThresholdDb = -100;
434 IBufferSender(
double minThresholdDb = -90.,
int bufferSize = MAXBUF)
437 if (minThresholdDb <= kNoThresholdDb)
440 mThreshold =
DBToAmp(minThresholdDb);
442 SetBufferSize(bufferSize);
451 void ProcessBlock(sample** inputs,
int nFrames,
int ctrlTag = kNoTag,
int nChans = MAXNC,
int chanOffset = 0)
453 for (
auto s = 0; s < nFrames; s++)
455 if (mBufCount == mBufferSize)
458 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
460 sum += mRunningSum[c];
461 mRunningSum[c] = 0.0f;
464 if (sum > mThreshold || mPreviousSum > mThreshold)
466 mBuffer.ctrlTag = ctrlTag;
467 mBuffer.nChans = nChans;
468 mBuffer.chanOffset = chanOffset;
476 for (
auto c = chanOffset; c < (chanOffset + nChans); c++)
478 const float inputSample =
static_cast<float>(inputs[c][s]);
479 mBuffer.vals[c][mBufCount] = inputSample;
480 mRunningSum[c] += std::fabs(inputSample);
487 void SetBufferSize(
int bufferSize)
489 assert(bufferSize > 0);
490 assert(bufferSize <= MAXBUF);
492 mBufferSize = bufferSize;
496 int GetBufferSize()
const {
return mBufferSize; }
501 int mBufferSize = MAXBUF;
502 std::array<float, MAXNC> mRunningSum {0.};
503 float mPreviousSum = 1.f;
504 float mThreshold = 0.01f;
508template <
int MAXNC = 1,
int QUEUE_SIZE = 64,
int MAX_FFT_SIZE = 4096>
512 using TDataPacket = std::array<float, MAX_FFT_SIZE>;
515 enum class EWindowType {
523 enum class EOutputType {
528 ISpectrumSender(
int fftSize = 1024,
int overlap = 2, EWindowType window = EWindowType::Hann, EOutputType outputType = EOutputType::MagPhase,
double minThresholdDb = -100.0)
530 , mWindowType(window)
531 , mOutputType(outputType)
534 SetFFTSizeAndOverlap(fftSize, overlap);
537 void SetFFTSize(
int fftSize)
539 TBufferSender::SetBufferSize(fftSize);
542 CalculateScalingFactors();
545 void SetFFTSizeAndOverlap(
int fftSize,
int overlap)
548 TBufferSender::SetBufferSize(fftSize);
551 CalculateScalingFactors();
554 void SetWindowType(EWindowType windowType)
556 mWindowType = windowType;
560 void SetOutputType(EOutputType outputType)
562 mOutputType = outputType;
567 auto fftSize = TBufferSender::GetBufferSize();
569 for (
auto s = 0; s < fftSize; s++)
571 for (
auto stftFrameIdx = 0; stftFrameIdx < mOverlap; stftFrameIdx++)
573 auto& stftFrame = mSTFTFrames[stftFrameIdx];
575 for (
auto ch = 0; ch < MAXNC; ch++)
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;
584 if (stftFrame.pos >= fftSize)
588 for (
auto ch = 0; ch < MAXNC; ch++)
590 Permute(ch, stftFrameIdx);
591 memcpy(d.vals[ch].data(), mSTFTOutput[ch].data(), fftSize *
sizeof(
float));
598 int GetFFTSize()
const
600 return TBufferSender::GetBufferSize();
603 int GetOverlap()
const
611 if (mSTFTFrames.size() != mOverlap)
613 mSTFTFrames.resize(mOverlap);
616 for (
auto&& frame : mSTFTFrames)
618 for (
auto ch = 0; ch < MAXNC; ch++)
620 std::fill(frame.bins[ch].begin(), frame.bins[ch].end(), WDL_FFT_COMPLEX{0.0f, 0.0f});
626 for (
auto ch = 0; ch < MAXNC; ch++)
628 std::fill(mSTFTOutput[ch].begin(), mSTFTOutput[ch].end(), 0.0f);
632 void CalculateWindow()
634 const auto fftSize = TBufferSender::GetBufferSize();
636 const float M =
static_cast<float>(fftSize - 1);
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)); }
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));
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); }
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);
661 case EWindowType::Rectangular:
662 std::fill(mWindow.begin(), mWindow.end(), 1.0f);
669 void CalculateScalingFactors()
671 const auto fftSize = TBufferSender::GetBufferSize();
672 const float M =
static_cast<float>(fftSize - 1);
676 for (
auto i = 0; i < fftSize; i++)
678 auto v = 0.5f * (1.0f - std::cos(2.0f * PI * i / M));
682 mScalingFactor = scaling * scaling;
685 void Permute(
int ch,
int frameIdx)
687 const auto fftSize = TBufferSender::GetBufferSize();
688 WDL_fft(mSTFTFrames[frameIdx].bins[ch].data(), fftSize,
false);
690 if (mOutputType == EOutputType::Complex)
692 auto nBins = fftSize/2;
693 for (
auto i = 0; i < nBins; ++i)
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;
702 auto nBins = fftSize/2;
703 for (
auto i = 0; i < nBins; ++i)
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);
717 std::array<std::array<WDL_FFT_COMPLEX, MAX_FFT_SIZE>, MAXNC> bins;
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;
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...
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.