21#include "IPlugStructs.h"
24BEGIN_IGRAPHICS_NAMESPACE
35 : lowRangeDB(low), highRangeDB(high), nSegs(segs), color(c) {}
39const static IColor SPEC_LED1 = {255, 36, 157, 16};
40const static IColor SPEC_LED2 = {255, 153, 191, 28};
41const static IColor SPEC_LED3 = {255, 215, 222, 37};
42const static IColor SPEC_LED4 = {255, 247, 153, 33};
43const static IColor SPEC_LED5 = COLOR_RED;
46const static float kThirdOctaveCenterFreqs[] = {
47 20.f, 25.f, 31.5f, 40.f, 50.f, 63.f, 80.f, 100.f, 125.f, 160.f,
48 200.f, 250.f, 315.f, 400.f, 500.f, 630.f, 800.f, 1000.f, 1250.f, 1600.f,
49 2000.f, 2500.f, 3150.f, 4000.f, 5000.f, 6300.f, 8000.f, 10000.f, 12500.f, 16000.f, 20000.f
51const static int kNumThirdOctaveBands = 31;
55template <
int MAXNC = 2,
int MAX_FFT_SIZE = 4096>
60 using TDataPacket = std::array<float, MAX_FFT_SIZE>;
65 kMsgTagSampleRate = 1,
72 enum class EFrequencyScale { Linear, Log, ThirdOctave };
73 enum class EColorMode { Segments, Smooth };
74 enum class EChannelMode { Left, Right, Sum, SideBySide, Split };
76 static constexpr float kPeakDecayRate = 0.95f;
96 const char* label =
"",
97 const IVStyle& style = DEFAULT_STYLE,
100 EFrequencyScale freqScale = EFrequencyScale::Log,
101 EColorMode colorMode = EColorMode::Segments,
102 EChannelMode channelMode = EChannelMode::Sum,
104 const std::vector<SpectrumLEDRange>& ledRanges = {
105 {0.f, 6.f, 2, SPEC_LED5},
106 {-12.f, 0.f, 3, SPEC_LED4},
107 {-24.f, -12.f, 3, SPEC_LED3},
108 {-48.f, -24.f, 4, SPEC_LED2},
109 {-72.f, -48.f, 4, SPEC_LED1}
111 float gapRatio = 0.2f,
112 float segGapRatio = 0.1f,
113 float attackTimeMs = 5.0f,
114 float decayTimeMs = 50.0f,
115 float lowRangeDB = -72.f,
116 float highRangeDB = 6.f)
120 , mNSegsPerBar(nSegsPerBar)
121 , mFreqScale(freqScale)
122 , mColorMode(colorMode)
123 , mChannelMode(channelMode)
124 , mBarPattern(barPattern)
125 , mLEDRanges(ledRanges)
126 , mGapRatio(gapRatio)
127 , mSegGapRatio(segGapRatio)
128 , mAttackTimeMs(attackTimeMs)
129 , mDecayTimeMs(decayTimeMs)
130 , mLowRangeDB(lowRangeDB)
131 , mHighRangeDB(highRangeDB)
136 CalculateFrequencyBands();
153 ProcessSpectrumData(d);
155 else if (msgTag == kMsgTagSampleRate)
161 else if (msgTag == kMsgTagFFTSize)
164 stream.
Get(&fftSize, 0);
167 else if (msgTag == kMsgTagOverlap)
170 stream.
Get(&overlap, 0);
173 else if (msgTag == kMsgTagWindowType)
176 stream.
Get(&windowType, 0);
177 mWindowType = windowType;
179 else if (msgTag == kMsgTagOctaveGain)
182 stream.
Get(&octaveGain, 0);
183 SetOctaveGain(
static_cast<float>(octaveGain));
192 auto* pFftSizeMenu = mMenu.AddItem(
"FFT Size",
new IPopupMenu(
"FFT Size", {
"64",
"128",
"256",
"512",
"1024",
"2048",
"4096" }))->GetSubmenu();
193 auto* pChansMenu = mMenu.AddItem(
"Channels",
new IPopupMenu(
"Channels", {
"L",
"R",
"Sum",
"L + R",
"L | R" }))->GetSubmenu();
194 auto* pFreqScaleMenu = mMenu.AddItem(
"Freq Scaling",
new IPopupMenu(
"Freq Scaling", {
"Linear",
"Log",
"1/3 Octave" }))->GetSubmenu();
195 auto* pOverlapMenu = mMenu.AddItem(
"Overlap",
new IPopupMenu(
"Overlap", {
"1x",
"2x",
"4x",
"8x" }))->GetSubmenu();
196 auto* pWindowMenu = mMenu.AddItem(
"Window",
new IPopupMenu(
"Window", {
"Hann",
"Blackman Harris",
"Hamming",
"Flattop",
"Rectangular" }))->GetSubmenu();
198 pFftSizeMenu->CheckItem(0, mFFTSize == 64);
199 pFftSizeMenu->CheckItem(1, mFFTSize == 128);
200 pFftSizeMenu->CheckItem(2, mFFTSize == 256);
201 pFftSizeMenu->CheckItem(3, mFFTSize == 512);
202 pFftSizeMenu->CheckItem(4, mFFTSize == 1024);
203 pFftSizeMenu->CheckItem(5, mFFTSize == 2048);
204 pFftSizeMenu->CheckItem(6, mFFTSize == 4096);
206 pChansMenu->CheckItem(0, mChannelMode == EChannelMode::Left);
207 pChansMenu->CheckItem(1, mChannelMode == EChannelMode::Right);
208 pChansMenu->CheckItem(2, mChannelMode == EChannelMode::Sum);
209 pChansMenu->CheckItem(3, mChannelMode == EChannelMode::SideBySide);
210 pChansMenu->CheckItem(4, mChannelMode == EChannelMode::Split);
212 pFreqScaleMenu->CheckItem(0, mFreqScale == EFrequencyScale::Linear);
213 pFreqScaleMenu->CheckItem(1, mFreqScale == EFrequencyScale::Log);
214 pFreqScaleMenu->CheckItem(2, mFreqScale == EFrequencyScale::ThirdOctave);
216 pOverlapMenu->CheckItem(0, mOverlap == 1);
217 pOverlapMenu->CheckItem(1, mOverlap == 2);
218 pOverlapMenu->CheckItem(2, mOverlap == 4);
219 pOverlapMenu->CheckItem(3, mOverlap == 8);
221 pWindowMenu->CheckItem(0, mWindowType == 0);
222 pWindowMenu->CheckItem(1, mWindowType == 1);
223 pWindowMenu->CheckItem(2, mWindowType == 2);
224 pWindowMenu->CheckItem(3, mWindowType == 3);
225 pWindowMenu->CheckItem(4, mWindowType == 4);
235 const char* title = pSelectedMenu->GetRootTitle();
237 if (strcmp(title,
"FFT Size") == 0)
239 int fftSize = atoi(pSelectedMenu->GetChosenItem()->GetText());
243 else if (strcmp(title,
"Channels") == 0)
245 int idx = pSelectedMenu->GetChosenItemIdx();
248 case 0: SetChannelMode(EChannelMode::Left);
break;
249 case 1: SetChannelMode(EChannelMode::Right);
break;
250 case 2: SetChannelMode(EChannelMode::Sum);
break;
251 case 3: SetChannelMode(EChannelMode::SideBySide);
break;
252 case 4: SetChannelMode(EChannelMode::Split);
break;
255 else if (strcmp(title,
"Freq Scaling") == 0)
257 int idx = pSelectedMenu->GetChosenItemIdx();
258 EFrequencyScale scale = EFrequencyScale::Linear;
259 if (idx == 1) scale = EFrequencyScale::Log;
260 else if (idx == 2) scale = EFrequencyScale::ThirdOctave;
261 SetFrequencyScale(scale);
263 else if (strcmp(title,
"Overlap") == 0)
265 const char* txt = pSelectedMenu->GetChosenItem()->GetText();
266 int overlap = atoi(txt);
272 else if (strcmp(title,
"Window") == 0)
274 int idx = pSelectedMenu->GetChosenItemIdx();
287 if (mStyle.drawFrame)
289 if (mChannelMode == EChannelMode::Split)
292 const float halfWidth = mWidgetBounds.
W() * 0.5f;
293 IRECT leftBounds(mWidgetBounds.L, mWidgetBounds.T, mWidgetBounds.L + halfWidth, mWidgetBounds.B);
294 IRECT rightBounds(mWidgetBounds.L + halfWidth, mWidgetBounds.T, mWidgetBounds.R, mWidgetBounds.B);
305 void SetNumBars(
int nBars)
309 CalculateFrequencyBands();
313 void SetNumSegments(
int nSegs)
315 mNSegsPerBar = nSegs;
319 void SetColorMode(EColorMode mode)
325 void SetChannelMode(EChannelMode mode)
336 mBarPattern = pattern;
340 void SetFrequencyScale(EFrequencyScale scale)
343 if (mFreqScale != EFrequencyScale::ThirdOctave && scale == EFrequencyScale::ThirdOctave)
345 mStoredNBars = mNBars;
348 else if (mFreqScale == EFrequencyScale::ThirdOctave && scale != EFrequencyScale::ThirdOctave)
350 mNBars = mStoredNBars;
355 CalculateFrequencyBands();
359 void SetPeakHoldTime(
int timeMs)
361 mPeakHoldTimeMs = timeMs;
364 void SetShowPeaks(
bool show)
375 mClipThresholdDB = thresholdDB;
376 mClipColor = clipColor;
377 mShowClipIndicator =
true;
381 void SetShowClipIndicator(
bool show)
383 mShowClipIndicator = show;
387 void SetFFTSize(
int fftSize)
390 SetSmoothing(mAttackTimeMs, mDecayTimeMs);
391 CalculateFrequencyBands();
395 void SetSampleRate(
double sr)
398 SetSmoothing(mAttackTimeMs, mDecayTimeMs);
399 CalculateFrequencyBands();
403 void SetOctaveGain(
float octaveGain)
405 mOctaveGain = octaveGain;
410 void ResizeBarArrays()
412 const int numBars = (mChannelMode == EChannelMode::SideBySide || mChannelMode == EChannelMode::Split) ? mNBars * 2 : mNBars;
413 mBarValues.resize(numBars, 0.f);
414 mPeakValues.resize(numBars, 0.f);
415 mPeakHoldCounters.resize(numBars, 0);
420 const float totalWidth = mWidgetBounds.
W();
421 const float totalHeight = mWidgetBounds.
H();
422 const float segHeightWithGap = totalHeight / mNSegsPerBar;
423 const float segHeight = segHeightWithGap * (1.f - mSegGapRatio);
424 const float segGap = segHeightWithGap * mSegGapRatio;
426 if (mChannelMode == EChannelMode::SideBySide)
429 const float pairWidthWithGap = totalWidth / mNBars;
430 const float pairGap = pairWidthWithGap * mGapRatio;
431 const float pairWidth = pairWidthWithGap - pairGap;
432 const float singleBarWidth = pairWidth * 0.5f;
434 for (
int bar = 0; bar < mNBars; bar++)
436 const float pairX = mWidgetBounds.L + bar * pairWidthWithGap + pairGap * 0.5f;
439 const float leftBarX = pairX;
440 const float leftValue = mBarValues[bar * 2];
441 DrawBar(g, leftBarX, singleBarWidth, segHeight, segGap, leftValue, 0);
443 if (mShowPeaks && mPeakValues[bar * 2] > 0.001f)
445 DrawPeakIndicator(g, leftBarX, singleBarWidth, segHeightWithGap, mPeakValues[bar * 2], 0);
449 const float rightBarX = pairX + singleBarWidth;
450 const float rightValue = mBarValues[bar * 2 + 1];
451 DrawBar(g, rightBarX, singleBarWidth, segHeight, segGap, rightValue, 1);
453 if (mShowPeaks && mPeakValues[bar * 2 + 1] > 0.001f)
455 DrawPeakIndicator(g, rightBarX, singleBarWidth, segHeightWithGap, mPeakValues[bar * 2 + 1], 1);
459 else if (mChannelMode == EChannelMode::Split)
462 const float halfWidth = totalWidth * 0.5f;
463 const float barWidthWithGap = halfWidth / mNBars;
464 const float barWidth = barWidthWithGap * (1.f - mGapRatio);
465 const float gapWidth = barWidthWithGap * mGapRatio;
468 for (
int bar = 0; bar < mNBars; bar++)
470 const float barX = mWidgetBounds.L + bar * barWidthWithGap + gapWidth * 0.5f;
471 const float barValue = mBarValues[bar * 2];
473 DrawBar(g, barX, barWidth, segHeight, segGap, barValue, 0);
475 if (mShowPeaks && mPeakValues[bar * 2] > 0.001f)
477 DrawPeakIndicator(g, barX, barWidth, segHeightWithGap, mPeakValues[bar * 2], 0);
482 for (
int bar = 0; bar < mNBars; bar++)
484 const float barX = mWidgetBounds.L + halfWidth + bar * barWidthWithGap + gapWidth * 0.5f;
485 const float barValue = mBarValues[bar * 2 + 1];
487 DrawBar(g, barX, barWidth, segHeight, segGap, barValue, 1);
489 if (mShowPeaks && mPeakValues[bar * 2 + 1] > 0.001f)
491 DrawPeakIndicator(g, barX, barWidth, segHeightWithGap, mPeakValues[bar * 2 + 1], 1);
498 const float barWidthWithGap = totalWidth / mNBars;
499 const float barWidth = barWidthWithGap * (1.f - mGapRatio);
500 const float gapWidth = barWidthWithGap * mGapRatio;
502 for (
int bar = 0; bar < mNBars; bar++)
504 const float barX = mWidgetBounds.L + bar * barWidthWithGap + gapWidth * 0.5f;
505 const float barValue = mBarValues[bar];
507 DrawBar(g, barX, barWidth, segHeight, segGap, barValue);
509 if (mShowPeaks && mPeakValues[bar] > 0.001f)
511 DrawPeakIndicator(g, barX, barWidth, segHeightWithGap, mPeakValues[bar]);
517 void DrawBar(
IGraphics& g,
float barX,
float barWidth,
float segHeight,
float segGap,
float value,
int channelIdx = -1)
519 if (mColorMode == EColorMode::Smooth)
525 const float clipThresholdNorm = mShowClipIndicator ?
526 (mClipThresholdDB - mLowRangeDB) / (mHighRangeDB - mLowRangeDB) : 1.0f;
528 const bool isClipping = mShowClipIndicator && value > clipThresholdNorm;
531 const float gradientValue = isClipping ? clipThresholdNorm : value;
532 if (gradientValue > 0.001f)
534 const float barHeight = gradientValue * mWidgetBounds.
H();
535 const float barY = mWidgetBounds.B - barHeight;
536 IRECT barRect(barX, barY, barX + barWidth, mWidgetBounds.B);
542 for (
int i = 0; i < mBarPattern.
NStops(); i++)
545 pattern.
AddStop(stop.mColor, stop.mOffset);
555 const float clipStartY = mWidgetBounds.B - (clipThresholdNorm * mWidgetBounds.
H());
556 const float clipEndY = mWidgetBounds.T;
557 IRECT clipRect(barX, clipEndY, barX + barWidth, clipStartY);
559 IColor clipColor = mClipColor;
560 if (channelIdx == 1 && mChannelMode != EChannelMode::Split)
573 for (
const auto& range : mLEDRanges)
575 for (
int i = 0; i < range.nSegs; i++)
577 const int totalSeg = mNSegsPerBar - 1 - segIdx;
578 const float segNorm =
static_cast<float>(totalSeg + 1) / mNSegsPerBar;
580 if (value >= segNorm - 0.001f)
582 const float segY = mWidgetBounds.B - (totalSeg + 1) * (segHeight + segGap) + segGap * 0.5f;
583 IRECT segRect(barX, segY, barX + barWidth, segY + segHeight);
584 IColor segColor = range.color;
586 if (channelIdx == 1 && mChannelMode != EChannelMode::Split)
594 if (segIdx >= mNSegsPerBar)
break;
596 if (segIdx >= mNSegsPerBar)
break;
601 void DrawPeakIndicator(
IGraphics& g,
float barX,
float barWidth,
float segHeightWithGap,
float peakValue,
int channelIdx = -1)
603 if (peakValue <= 0.001f)
return;
606 if (mColorMode == EColorMode::Smooth && mShowClipIndicator)
608 const float clipThresholdNorm = (mClipThresholdDB - mLowRangeDB) / (mHighRangeDB - mLowRangeDB);
609 if (peakValue >= clipThresholdNorm)
614 if (mColorMode == EColorMode::Smooth)
617 peakY = mWidgetBounds.B - (peakValue * mWidgetBounds.
H());
622 const int peakSeg =
static_cast<int>(peakValue * mNSegsPerBar);
623 if (peakSeg <= 0)
return;
624 peakY = mWidgetBounds.B - peakSeg * segHeightWithGap;
627 IRECT peakRect(barX, peakY, barX + barWidth, peakY + 2.f);
630 if (mColorMode == EColorMode::Segments)
632 peakColor = GetColorForLevel(peakValue);
636 peakColor = GetPatternColorAtPosition(peakValue);
638 if (channelIdx == 1 && mChannelMode != EChannelMode::Split)
646 IColor GetColorForLevel(
float normalizedLevel)
648 const float dB = mLowRangeDB + normalizedLevel * (mHighRangeDB - mLowRangeDB);
650 for (
const auto& range : mLEDRanges)
652 if (dB >= range.lowRangeDB && dB <= range.highRangeDB)
658 return mLEDRanges.empty() ? COLOR_GREEN : mLEDRanges.back().color;
662 IColor GetPatternColorAtPosition(
float pos)
664 pos =
Clip(pos, 0.f, 1.f);
665 const int nStops = mBarPattern.
NStops();
670 return mBarPattern.
GetStop(0).mColor;
676 for (
int i = 0; i < nStops - 1; i++)
680 if (pos >= s1.mOffset && pos <= s2.mOffset)
689 const float range = nextStop->mOffset - prevStop->mOffset;
690 const float t = (range > 0.f) ? (pos - prevStop->mOffset) / range : 0.f;
697 const float rangeDB = mHighRangeDB - mLowRangeDB;
698 const float lowPointAbs = std::fabs(mLowRangeDB);
699 const int numBins = mFFTSize / 2;
701 if (mChannelMode == EChannelMode::SideBySide || mChannelMode == EChannelMode::Split)
704 for (
int bar = 0; bar < mNBars; bar++)
706 const int startBin = mBandStartBins[bar];
707 const int endBin = mBandEndBins[bar];
708 const float freqNorm =
static_cast<float>(startBin + endBin) / (2.f * numBins);
710 for (
int ch = 0; ch < 2; ch++)
712 if (d.nChans <= 0)
break;
713 const int channelIdx = d.chanOffset + std::min(ch, d.nChans - 1);
714 const int barIdx = bar * 2 + ch;
717 for (
int bin = startBin; bin < endBin; bin++)
719 float mag = d.vals[channelIdx][bin];
720 if (mag > maxMag) maxMag = mag;
723 UpdateBarValue(barIdx, maxMag, lowPointAbs, rangeDB, freqNorm);
730 for (
int bar = 0; bar < mNBars; bar++)
732 const int startBin = mBandStartBins[bar];
733 const int endBin = mBandEndBins[bar];
734 const float freqNorm =
static_cast<float>(startBin + endBin) / (2.f * numBins);
737 for (
int bin = startBin; bin < endBin; bin++)
741 if (mChannelMode == EChannelMode::Left)
743 mag = d.vals[d.chanOffset][bin];
745 else if (mChannelMode == EChannelMode::Right)
747 const int rightCh = d.chanOffset + std::min(1, d.nChans - 1);
748 mag = d.vals[rightCh][bin];
752 for (
int ch = d.chanOffset; ch < d.chanOffset + d.nChans; ch++)
754 mag += d.vals[ch][bin];
759 if (mag > maxMag) maxMag = mag;
762 UpdateBarValue(bar, maxMag, lowPointAbs, rangeDB, freqNorm);
769 void UpdateBarValue(
int barIdx,
float maxMag,
float lowPointAbs,
float rangeDB,
float freqNorm = 0.5f)
772 if (mOctaveGain > 0.f)
774 const float centerFreqNorm = 500.f / (mSampleRate * 0.5f);
775 if (centerFreqNorm > 1e-6f)
777 maxMag *= (freqNorm / centerFreqNorm) * mOctaveGain;
782 float dB =
AmpToDB(maxMag + 1e-30f);
783 float normalizedValue = (dB + lowPointAbs) / rangeDB;
784 normalizedValue =
Clip(normalizedValue, 0.f, 1.f);
787 float prevValue = mBarValues[barIdx];
789 if (normalizedValue > prevValue)
790 newValue = mAttackCoeff * prevValue + (1.f - mAttackCoeff) * normalizedValue;
792 newValue = mReleaseCoeff * prevValue + (1.f - mReleaseCoeff) * normalizedValue;
794 mBarValues[barIdx] = newValue;
799 if (newValue > mPeakValues[barIdx])
801 mPeakValues[barIdx] = newValue;
802 mPeakHoldCounters[barIdx] = mPeakHoldTimeMs;
804 else if (mPeakHoldCounters[barIdx] > 0)
806 const int hopSize = mFFTSize / std::max(1, mOverlap);
807 mPeakHoldCounters[barIdx] -=
static_cast<int>(1000.f * hopSize / mSampleRate);
811 mPeakValues[barIdx] *= kPeakDecayRate;
816 void CalculateFrequencyBands()
818 const int numBins = mFFTSize / 2;
819 const float nyquist =
static_cast<float>(mSampleRate) * 0.5f;
821 if (mFreqScale == EFrequencyScale::ThirdOctave)
824 const float bandwidthFactor = std::pow(2.f, 1.f / 6.f);
828 std::vector<int> validIndices;
829 std::vector<int> startBins;
830 std::vector<int> endBins;
833 for (
int i = 0; i < kNumThirdOctaveBands; i++)
835 const float centerFreq = kThirdOctaveCenterFreqs[i];
836 if (centerFreq >= nyquist)
839 const float lowFreq = centerFreq / bandwidthFactor;
840 const float highFreq = std::min(centerFreq * bandwidthFactor, nyquist);
842 int startBin = std::max(1,
static_cast<int>(lowFreq / nyquist * numBins));
843 int endBin =
static_cast<int>(highFreq / nyquist * numBins);
847 if (endBin > prevEndBin && endBin > startBin)
850 if (startBin <= prevEndBin)
851 startBin = prevEndBin + 1;
853 if (endBin > startBin)
855 validIndices.push_back(i);
856 startBins.push_back(startBin);
857 endBins.push_back(endBin);
863 mNBars =
static_cast<int>(validIndices.size());
865 mBandStartBins.resize(mNBars);
866 mBandEndBins.resize(mNBars);
867 mThirdOctaveIndices = validIndices;
869 for (
int bar = 0; bar < mNBars; bar++)
871 mBandStartBins[bar] = startBins[bar];
872 mBandEndBins[bar] = endBins[bar];
875 else if (mFreqScale == EFrequencyScale::Log)
877 mBandStartBins.resize(mNBars);
878 mBandEndBins.resize(mNBars);
880 const float minFreq = 20.f;
881 const float maxFreq = nyquist;
882 const float logMin = std::log(minFreq);
883 const float logMax = std::log(maxFreq);
884 const float logRange = logMax - logMin;
886 for (
int bar = 0; bar < mNBars; bar++)
888 const float logFreqStart = logMin + (
static_cast<float>(bar) / mNBars) * logRange;
889 const float logFreqEnd = logMin + (
static_cast<float>(bar + 1) / mNBars) * logRange;
890 const float freqStart = std::exp(logFreqStart);
891 const float freqEnd = std::exp(logFreqEnd);
893 mBandStartBins[bar] = std::max(1,
static_cast<int>(freqStart / nyquist * numBins));
894 mBandEndBins[bar] = std::max(mBandStartBins[bar] + 1,
static_cast<int>(freqEnd / nyquist * numBins));
899 mBandStartBins.resize(mNBars);
900 mBandEndBins.resize(mNBars);
902 const float binWidth =
static_cast<float>(numBins) / mNBars;
903 for (
int bar = 0; bar < mNBars; bar++)
905 mBandStartBins[bar] =
static_cast<int>(bar * binWidth);
906 mBandEndBins[bar] =
static_cast<int>((bar + 1) * binWidth);
911 void SetSmoothing(
float attackTimeMs,
float releaseTimeMs)
913 mAttackTimeMs = attackTimeMs;
914 mDecayTimeMs = releaseTimeMs;
915 float attackTimeSec = attackTimeMs * 0.001f;
916 float releaseTimeSec = releaseTimeMs * 0.001f;
917 float updatePeriod =
static_cast<float>(mFFTSize) /
static_cast<float>(mSampleRate);
918 mAttackCoeff = std::exp(-updatePeriod / attackTimeSec);
919 mReleaseCoeff = std::exp(-updatePeriod / releaseTimeSec);
923 int mStoredNBars = 32;
924 int mNSegsPerBar = 16;
928 double mSampleRate = 44100.0;
929 float mOctaveGain = 0.f;
930 EFrequencyScale mFreqScale = EFrequencyScale::Log;
931 EColorMode mColorMode = EColorMode::Segments;
932 EChannelMode mChannelMode = EChannelMode::Sum;
934 std::vector<SpectrumLEDRange> mLEDRanges;
936 float mGapRatio = 0.2f;
937 float mSegGapRatio = 0.1f;
938 float mLowRangeDB = -72.f;
939 float mHighRangeDB = 6.f;
940 float mAttackTimeMs = 5.f;
941 float mDecayTimeMs = 50.f;
942 float mAttackCoeff = 0.9f;
943 float mReleaseCoeff = 0.99f;
945 bool mShowPeaks =
true;
946 int mPeakHoldTimeMs = 1000;
948 bool mShowClipIndicator =
false;
949 float mClipThresholdDB = 0.f;
950 IColor mClipColor = COLOR_RED;
952 std::vector<float> mBarValues;
953 std::vector<float> mPeakValues;
954 std::vector<int> mPeakHoldCounters;
955 std::vector<int> mBandStartBins;
956 std::vector<int> mBandEndBins;
957 std::vector<int> mThirdOctaveIndices;
961END_IGRAPHICS_NAMESPACE
This file contains the base IControl implementation, along with some base classes for specific types ...
Manages a non-owned block of memory, for receiving arbitrary message byte streams.
int Get(T *pDst, int startPos) const
Get arbitary typed data from the stream.
The lowest level base class of an IGraphics control.
void SetTargetRECT(const IRECT &bounds)
Set the rectangular mouse tracking target area, within the graphics context for this control.
IGEditorDelegate * GetDelegate()
Gets a pointer to the class implementing the IEditorDelegate interface that handles parameter changes...
virtual void SetDirty(bool triggerAction=true, int valIdx=kNoValIdx)
Mark the control as dirty, i.e.
virtual void SendArbitraryMsgFromUI(int msgTag, int ctrlTag=kNoTag, int dataSize=0, const void *pData=nullptr)
SendArbitraryMsgFromUI (Abbreviation: SAMFUI)
The lowest level base class of an IGraphics context.
virtual void DrawRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0, float thickness=1.f)
Draw a rectangle to the graphics context.
virtual void PathFill(const IPattern &pattern, const IFillOptions &options=IFillOptions(), const IBlend *pBlend=0)=0
Fill the current current path.
void CreatePopupMenu(IControl &control, IPopupMenu &menu, const IRECT &bounds, int valIdx=0)
Shows a pop up/contextual menu in relation to a rectangular region of the graphics context.
void PathRect(const IRECT &bounds)
Add a rectangle to the current path.
virtual void FillRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0)
Fill a rectangular region of the graphics context with a color.
ISender is a utility class which can be used to defer data from the realtime audio processing and sen...
Vectorial bar graph spectrum analyzer control with segmented LEDs.
void SetBarPattern(const IPattern &pattern)
Set the bar pattern for Smooth color mode.
void OnResize() override
Called when IControl is constructed or resized using SetRect().
void SetClipIndicator(float thresholdDB, const IColor &clipColor=COLOR_RED)
Enable a clip indicator segment above a threshold.
void OnMsgFromDelegate(int msgTag, int dataSize, const void *pData) override
Implement to receive messages sent to the control, see IEditorDelegate:SendControlMsgFromDelegate()
void Draw(IGraphics &g) override
Draw the control to the graphics context.
MsgTags
Message tags for synchronization with ISpectrumSender.
void OnMouseDown(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse down event on this control.
IVBarGraphSpectrumAnalyzerControl(const IRECT &bounds, const char *label="", const IVStyle &style=DEFAULT_STYLE, int nBars=32, int nSegsPerBar=16, EFrequencyScale freqScale=EFrequencyScale::Log, EColorMode colorMode=EColorMode::Segments, EChannelMode channelMode=EChannelMode::Sum, const IPattern &barPattern=IPattern(COLOR_GREEN), const std::vector< SpectrumLEDRange > &ledRanges={ {0.f, 6.f, 2, SPEC_LED5}, {-12.f, 0.f, 3, SPEC_LED4}, {-24.f, -12.f, 3, SPEC_LED3}, {-48.f, -24.f, 4, SPEC_LED2}, {-72.f, -48.f, 4, SPEC_LED1} }, float gapRatio=0.2f, float segGapRatio=0.1f, float attackTimeMs=5.0f, float decayTimeMs=50.0f, float lowRangeDB=-72.f, float highRangeDB=6.f)
Create a bar graph spectrum analyzer.
void OnPopupMenuSelection(IPopupMenu *pSelectedMenu, int valIdx) override
Implement this method to handle popup menu selection after IGraphics::CreatePopupMenu/IControlPromptU...
A base interface to be combined with IControl for vectorial controls "IVControls",...
IRECT MakeRects(const IRECT &parent, bool hasHandle=false)
Calculate the rectangles for the various areas, depending on the style.
virtual void DrawBackground(IGraphics &g, const IRECT &rect)
Draw the IVControl background (usually transparent)
void AttachIControl(IControl *pControl, const char *label)
Call in the constructor of your IVControl to link the IVectorBase and IControl.
virtual void DrawLabel(IGraphics &g)
Draw the IVControl label text.
const IColor & GetColor(EVColor color) const
Get value of a specific EVColor in the IVControl.
BEGIN_IPLUG_NAMESPACE T Clip(T x, T lo, T hi)
Clips the value x between lo and hi.
static double AmpToDB(double amp)
Used to manage color data, independent of draw class/platform.
IColor WithContrast(float c) const
Returns a new contrasted IColor based on this one.
static IColor LinearInterpolateBetween(const IColor &start, const IColor &dest, float progress)
Helper function to linear interpolate between two IColors.
Used to represent a point/stop in a gradient.
Used to manage mouse modifiers i.e.
Used to store pattern information for gradients.
const IColorStop & GetStop(int idx) const
Get the IColorStop at a particular index (will crash if out of bounds)
void AddStop(IColor color, float offset)
Add an IColorStop to the IPattern.
static IPattern CreateLinearGradient(float x1, float y1, float x2, float y2, const std::initializer_list< IColorStop > &stops={})
Create a linear gradient IPattern.
Used to manage a rectangular area, independent of draw class/platform.
ISenderData is used to represent a typed data packet, that may contain values for multiple channels.
A struct encapsulating a set of properties used to configure IVControls.
LED Range for spectrum analyzer bar segments.