Skip to content

Commit 927224e

Browse files
authored
Merge branch 'pdpwopr' into audioupdates
2 parents 46cd909 + d98d838 commit 927224e

5 files changed

Lines changed: 86 additions & 38 deletions

File tree

include/effects/strip/faneffects.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,9 +412,7 @@ class FanBeatEffect : public LEDStripEffect
412412

413413
CRGB c = CHSV(random(0, 255), 255, 255);
414414
for (int i = NUM_FANS * FAN_SIZE; i < NUM_LEDS; i++)
415-
{
416415
g()->setPixel(i, c);
417-
}
418416
}
419417

420418
void DrawEffect()

include/globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
#define USE_M5 1
151151
#endif
152152

153+
153154
#if USE_M5
154155
#include "M5Unified.h"
155156
#endif

include/soundanalyzer.h

Lines changed: 72 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ class SoundAnalyzer : public ISoundAnalyzer
304304
// I'm old enough I can only hear up to about 12000Hz, but feel free to adjust. Remember from
305305
// school that you need to sample at double the frequency you want to process, so 24000 samples is 12000Hz
306306

307-
static constexpr size_t SAMPLING_FREQUENCY = 20000;
307+
static constexpr size_t SAMPLING_FREQUENCY = 24000;
308308
static constexpr size_t LOWEST_FREQ = 100;
309309
static constexpr size_t HIGHEST_FREQ = SAMPLING_FREQUENCY / 2;
310310

@@ -324,6 +324,7 @@ class SoundAnalyzer : public ISoundAnalyzer
324324
float _oldPeakVU; // Old peak VU value for damping
325325
float _oldMinVU; // Old min VU value for damping
326326
float * _vPeaks; // Normalized band energies 0..1
327+
float * _livePeaks; // Attack-limited display peaks per band
327328
int _bandBinStart[NUM_BANDS];
328329
int _bandBinEnd[NUM_BANDS];
329330
float _energyMaxEnv = 1.0f; // adaptive envelope for autoscaling
@@ -346,6 +347,7 @@ class SoundAnalyzer : public ISoundAnalyzer
346347
//
347348

348349
private:
350+
static constexpr int kBandOffset = 1; // number of lowest source bands to skip in layout
349351
float *_vReal = nullptr;
350352
float *_vImaginary = nullptr;
351353
std::unique_ptr<int16_t[]> ptrSampleBuffer; // sample buffer storage
@@ -455,7 +457,9 @@ class SoundAnalyzer : public ISoundAnalyzer
455457
#endif
456458
for (int b = 0; b < NUM_BANDS; b++)
457459
{
458-
float fracHi = (float)(b + 1) / (float)NUM_BANDS;
460+
// Shift the effective band index by kBandOffset so logical band 0 starts higher
461+
const int logicalIdx = b + kBandOffset;
462+
const float fracHi = (float)(logicalIdx + 1) / (float)(NUM_BANDS + kBandOffset);
459463
#if SPECTRUM_BAND_SCALE_MEL
460464
float edgeMel = melMin + (melMax - melMin) * fracHi;
461465
float edgeHiFreq = melToHz(edgeMel);
@@ -474,10 +478,31 @@ class SoundAnalyzer : public ISoundAnalyzer
474478

475479
PeakData ProcessPeaksEnergy()
476480
{
477-
float frameMax = 0.0f;
478-
float frameRawMax = 0.0f; // max pre-subtraction band power (with compensation)
479-
float noiseSum = 0.0f; // accumulate noise floor for mean
480-
float noiseMaxAll = 0.0f; // track max noise floor across bands
481+
// Band offset handled in ComputeBandLayout so index 0 is the lowest VISIBLE band
482+
float frameMax = 0.0f;
483+
float frameSumRaw = 0.0f;
484+
float noiseSum = 0.0f;
485+
float noiseMaxAll = 0.0f; // Tracks max noise floor across bands
486+
// Display gain and band floor constants
487+
constexpr float kDisplayGain = 1.5f;
488+
constexpr float kBandFloorMin = 0.01f;
489+
constexpr float kBandFloorScale = 0.05f;
490+
491+
// Remember that a slower attack will yield a smoother display but a lower one, as it's always
492+
// below the actual for some amount of time
493+
494+
constexpr float kLiveAttackPerSec = 5.0f;
495+
496+
// Envelope gating factor and max relative EMA
497+
float _gateEnv = 1.0f;
498+
float _vMaxRelEMA = 1.0f;
499+
// Delta time for attack limiting (replace with actual frame time if available)
500+
float dt = 0.016f;
501+
// Allocate _livePeaks if not already done
502+
if (!_livePeaks) {
503+
_livePeaks = (float *)PreferPSRAMAlloc(NUM_BANDS * sizeof(_livePeaks[0]));
504+
for (int i = 0; i < NUM_BANDS; ++i) _livePeaks[i] = 0.0f;
505+
}
481506
_framesProcessed++;
482507
const float binWidth = (float)SAMPLING_FREQUENCY / (MAX_SAMPLES / 2.0f);
483508
for (int b = 0; b < NUM_BANDS; b++)
@@ -504,8 +529,8 @@ class SoundAnalyzer : public ISoundAnalyzer
504529
avgPower *= compensation;
505530

506531
// Track pre-subtraction max for SNR gating
507-
if (avgPower > frameRawMax)
508-
frameRawMax = avgPower;
532+
if (avgPower > frameSumRaw)
533+
frameSumRaw = avgPower;
509534

510535
if (avgPower > _noiseFloor[b])
511536
_noiseFloor[b] = _noiseFloor[b] * (1.0f - _params.energyNoiseAdapt) + (float)avgPower * _params.energyNoiseAdapt;
@@ -520,11 +545,20 @@ class SoundAnalyzer : public ISoundAnalyzer
520545
float signal = (float)avgPower - _noiseFloor[b];
521546
if (signal < 0.0f)
522547
signal = 0.0f;
523-
float smoothed = _rawPrev[b] * (1.0f - _params.energySmoothAlpha) + signal * _params.energySmoothAlpha;
548+
#if ENABLE_AUDIO_SMOOTHING
549+
// Weighted average: 0.25 * (2 * current + left + right)
550+
float left = (b > 0) ? _rawPrev[b - 1] : signal;
551+
float right = (b < NUM_BANDS - 1) ? _rawPrev[b + 1] : signal;
552+
float smoothed = 0.125f * (6.0f * signal + left + right);
524553
_rawPrev[b] = smoothed;
525554
_vPeaks[b] = smoothed;
526555
if (smoothed > frameMax)
527556
frameMax = smoothed;
557+
#else
558+
_vPeaks[b] = signal;
559+
if (signal > frameMax)
560+
frameMax = signal;
561+
#endif
528562
}
529563
if (frameMax > _energyMaxEnv)
530564
_energyMaxEnv = frameMax;
@@ -535,7 +569,7 @@ class SoundAnalyzer : public ISoundAnalyzer
535569

536570
// Raw SNR-based frame gate (pre-normalization) to suppress steady HVAC and similar backgrounfd noises
537571

538-
float snrRaw = frameRawMax / (noiseMaxAll + 1e-9f);
572+
float snrRaw = frameSumRaw / (noiseMaxAll + 1e-9f);
539573
if (snrRaw < _params.frameSNRGate)
540574
{
541575
_frameGateHits++;
@@ -599,22 +633,8 @@ class SoundAnalyzer : public ISoundAnalyzer
599633
}
600634
#endif
601635

636+
const float invEnv = 1.0f / normDen;
602637
float sumNorm = 0.0f;
603-
for (int b = 0; b < NUM_BANDS; b++)
604-
{
605-
float v = _vPeaks[b] * invEnv;
606-
607-
// Per-band normalized noise gate
608-
if (v < _params.normNoiseGate)
609-
{
610-
v = 0.0f;
611-
_bandGateHits++;
612-
}
613-
else
614-
{
615-
// Cube root compression for smooth flattening without spikes
616-
v = sqrt(cbrtf(std::max(0.0f, v)));
617-
}
618638

619639
// Apply post-scale to improve visible band intensity and clamp to [0,1]
620640
v *= _params.postScale;
@@ -628,15 +648,25 @@ class SoundAnalyzer : public ISoundAnalyzer
628648
#if ENABLE_AUDIO_SMOOTHING
629649
// Spatially smooth live spectrum peaks by blending with neighbors.
630650
// This affects displayed bars (and VU after we recompute below).
651+
// Now that layout skips the lowest bins, emit all NUM_BANDS directly with no reindexing
652+
for (int b = 0; b < NUM_BANDS; b++)
631653
{
632-
float tmp[NUM_BANDS];
633-
for (int b = 0; b < NUM_BANDS; ++b)
654+
float vTarget = _vPeaks[b] * invEnv;
655+
vTarget = cbrtf(std::max(0.0f, vTarget));
656+
if (vTarget > 1.0f) vTarget = 1.0f;
657+
vTarget *= _gateEnv;
658+
vTarget *= kDisplayGain;
659+
if (vTarget > 1.0f) vTarget = 1.0f;
660+
const float bandFloor = std::max(kBandFloorMin, kBandFloorScale * _vMaxRelEMA);
661+
if (vTarget < bandFloor) { vTarget = 0.0f; _bandGateHits++; }
662+
float vCurr = _livePeaks[b];
663+
float vNew = vTarget;
664+
if (vTarget > vCurr)
634665
{
635-
float v = _Peaks._Level[b];
636-
float l = (b > 0) ? _Peaks._Level[b - 1] : v;
637-
float r = (b < NUM_BANDS - 1) ? _Peaks._Level[b + 1] : v;
638-
tmp[b] = (v * 2.0f + l + r) * 0.25f; // (2*center + left + right) / 4
666+
const float maxRise = kLiveAttackPerSec * dt;
667+
vNew = vCurr + std::min(vTarget - vCurr, maxRise);
639668
}
669+
640670
for (int b = 0; b < NUM_BANDS; ++b)
641671
{
642672
float v = tmp[b];
@@ -646,9 +676,14 @@ class SoundAnalyzer : public ISoundAnalyzer
646676
}
647677
// Recompute VU from smoothed values
648678
sumNorm = std::accumulate(&_Peaks._Level[0], &_Peaks._Level[0] + NUM_BANDS, 0.0f);
679+
if (vNew > 1.0f) vNew = 1.0f;
680+
if (vNew < 0.0f) vNew = 0.0f;
681+
_livePeaks[b] = vNew;
682+
_vPeaks[b] = vNew;
683+
_Peaks._Level[b] = vNew;
684+
sumNorm += vNew;
649685
}
650-
#endif
651-
UpdateVU((float)(sumNorm / NUM_BANDS));
686+
UpdateVU(sumNorm / (float)NUM_BANDS);
652687
return _Peaks;
653688
}
654689

@@ -794,8 +829,10 @@ class SoundAnalyzer : public ISoundAnalyzer
794829
_vReal = (float *)PreferPSRAMAlloc(MAX_SAMPLES * sizeof(_vReal[0]));
795830
_vImaginary = (float *)PreferPSRAMAlloc(MAX_SAMPLES * sizeof(_vImaginary[0]));
796831
_vPeaks = (float *)PreferPSRAMAlloc(NUM_BANDS * sizeof(_vPeaks[0]));
797-
if (!_vReal || !_vImaginary || !_vPeaks)
832+
_livePeaks = (float *)PreferPSRAMAlloc(NUM_BANDS * sizeof(_livePeaks[0]));
833+
if (!_vReal || !_vImaginary || !_vPeaks || !_livePeaks)
798834
throw std::runtime_error("Failed to allocate FFT buffers");
835+
for (int i = 0; i < NUM_BANDS; ++i) _livePeaks[i] = 0.0f;
799836
_oldVU = _oldPeakVU = _oldMinVU = 0.0f;
800837
_MicMode = PeakData::MESMERIZERMIC;
801838
_params = ParamsFor(_MicMode);
@@ -812,6 +849,7 @@ class SoundAnalyzer : public ISoundAnalyzer
812849
free(_vReal);
813850
free(_vImaginary);
814851
free(_vPeaks);
852+
free(_livePeaks);
815853
}
816854

817855
// These functions allow access to the last-acquired sample buffer and its size so that

site/vite.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default defineConfig({
2727
}),
2828
viteCompression({
2929
deleteOriginFile: true,
30-
filter: '/\.(js|mjs|json|css|html|ico)$/i'
30+
filter: /\.(js|mjs|json|css|html|ico)$/i
3131
})
3232
],
3333
server: {

src/effects.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,20 @@ void LoadEffectFactories()
179179
// Include custom effects header if available - it overrides whatever the effect set flags
180180
// would otherwise include.
181181
//
182-
// ONLY one of these may be defined at a time
182+
// REVIEW (davepl) This allows the user to define custom effects
183+
// in a separate file, which is included here. This way, the user can add custom effects
184+
// without having to modify the main effect factories code, which is useful for projects that
185+
// are shared across multiple boards and/or projects. But it replaces ALL effects. If we
186+
// want to allow custom effects to be added to the existing set, we need to change this
187+
// not to have an else case after but rather an endif.
188+
189+
// REVIEW (davepl) If someone were to define multiple effects sets, it would overwrite the
190+
// value of EFFECT_SET_VERSION differently for each set, which is not what we want.
191+
//
192+
183193

184194
#if __has_include ("custom_effects.h")
195+
185196
#include "custom_effects.h"
186197

187198
// Fill effect factories using new effect set flags

0 commit comments

Comments
 (0)