Skip to content

Commit c347cc9

Browse files
committed
Revert "FrameGate"
This reverts commit e0e93f8.
1 parent e0e93f8 commit c347cc9

3 files changed

Lines changed: 113 additions & 114 deletions

File tree

include/globals.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@
7979
// Jul-24-2023 v038 Davepl NTP clock fix
8080
// Jul-26-2023 v039 Davepl NTP every minute, stack sizes
8181
// Jul-26-2023 v040 Davepl NTP every 5 minutes, Wifi delay code
82-
// Aig-09-2025 v041 Davepl New Audio Code
8382
//
8483
//---------------------------------------------------------------------------
8584

@@ -110,7 +109,7 @@
110109
//
111110
// BUGBUG (davepl): If you know a cleaner way, please improve this!
112111

113-
#define FLASH_VERSION 41 // Update ONLY this to increment the version number
112+
#define FLASH_VERSION 40 // Update ONLY this to increment the version number
114113

115114
#ifndef USE_HUB75 // We support strips by default unless specifically defined out
116115
#ifndef USE_WS281X
@@ -384,7 +383,7 @@ extern RemoteDebug Debug; // Let everyone in the project know about it
384383
#define AUDIO_PEAK_REMOTE_TIMEOUT 1000.0f // How long after remote PeakData before local microphone is used again
385384
#endif
386385
#ifndef ENABLE_AUDIO_SMOOTHING
387-
#define ENABLE_AUDIO_SMOOTHING 0
386+
#define ENABLE_AUDIO_SMOOTHING 1
388387
#endif
389388
#ifndef BARBEAT_ENHANCE
390389
#define BARBEAT_ENHANCE 0.3 // How much the SpectrumAnalyzer "pulses" with the music

include/soundanalyzer.h

Lines changed: 110 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ struct AudioInputParams {
8686
float energyMinEnv; // Min envelope to avoid div-by-zero
8787
float bandCompLow; // Low-band compensation scalar
8888
float bandCompHigh; // High-band compensation scalar
89+
float frameSilenceGate; // Gate entire frame if below this after norm
90+
float normNoiseGate; // Gate individual bands below this after norm
91+
float envFloorFromNoise; // Multiplier on mean noise floor to cap normalization
92+
float frameSNRGate; // Gate frame if raw SNR (max/noiseMax) is below this
8993
};
9094

9195
// Mesmerizer (default) tuning
@@ -97,33 +101,17 @@ static constexpr AudioInputParams kParamsMesmerizer{
97101
0.90f, // energyEnvDecay
98102
0.000001f, // energyMinEnv
99103
0.25f, // bandCompLow
100-
10.0f // bandCompHigh
104+
10.0f, // bandCompHigh
105+
0.00f, // frameSilenceGate
106+
0.00f, // normNoiseGate
107+
3.0f, // envFloorFromNoise (cap auto-gain at ~1/4 of pure-noise)
108+
0.0f // frameSNRGate (require ~3:1 SNR to show frame)
101109
};
102110

103111
// Copies for now; adjust per device as needed
104112
static constexpr AudioInputParams kParamsPCRemote = kParamsMesmerizer;
105-
106-
static constexpr AudioInputParams kParamsM5 {
107-
4.0f, // windowPowerCorrection
108-
0.02f, // energyNoiseAdapt
109-
0.98f, // energyNoiseDecay
110-
0.25f, // energySmoothAlpha
111-
0.90f, // energyEnvDecay
112-
0.000001f, // energyMinEnv
113-
0.25f, // bandCompLow
114-
10.0f // bandCompHigh
115-
};
116-
117-
static constexpr AudioInputParams kParamsM5Plus2 {
118-
4.0f, // windowPowerCorrection
119-
0.02f, // energyNoiseAdapt
120-
0.98f, // energyNoiseDecay
121-
0.25f, // energySmoothAlpha
122-
0.90f, // energyEnvDecay
123-
0.000001f, // energyMinEnv
124-
0.25f, // bandCompLow
125-
10.0f // bandCompHigh
126-
};
113+
static constexpr AudioInputParams kParamsM5 = kParamsMesmerizer;
114+
static constexpr AudioInputParams kParamsM5Plus2 = kParamsMesmerizer;
127115

128116
// PeakData
129117
//
@@ -334,8 +322,8 @@ class SoundAnalyzer : public ISoundAnalyzer
334322
size_t _clipCount = 0; // number of clipped samples seen
335323
float _dcOffsetEMA = 0.0f; // exponential moving average of DC offset (abs)
336324
float _envEMA = 0.0f; // EMA of _energyMaxEnv
337-
size_t _frameGateHits = 0; // frames gated by gate
338-
size_t _bandGateHits = 0; // total band gate hits (kept for telemetry)
325+
size_t _frameGateHits = 0; // frames gated by params.frameSilenceGate
326+
size_t _bandGateHits = 0; // total band gate hits
339327
size_t _framesProcessed = 0; // total processed frames
340328

341329
float _oldVU; // Old VU value for damping
@@ -358,33 +346,6 @@ class SoundAnalyzer : public ISoundAnalyzer
358346
PeakData::MicrophoneType _MicMode;
359347
AudioInputParams _params; // Active tuning params for current mic mode
360348

361-
// Gate envelope (absolute-level, hysteresis, with hold)
362-
float _gateEnv = 1.0f; // 0..1 applied to bands and VU
363-
uint32_t _gateHoldUntil = 0; // ms timestamp until which gate stays closed
364-
uint32_t _gateOpenHoldUntil = 0; // ms timestamp until which gate stays open
365-
float _frameSumEMA = 0.0f; // smoothed frame energy sum
366-
float _vMaxRelEMA = 0.0f; // smoothed normalized max-band level
367-
368-
// Gate tuning constants
369-
static constexpr float kGateEnterK = 0.60f; // kept (unused by new gate but retained for debug)
370-
static constexpr float kGateExitK = 1.20f; // kept (unused by new gate but retained for debug)
371-
static constexpr float kGateAttackPerSec = 20.0f; // ~50ms full open
372-
static constexpr float kGateReleasePerSec = 2.0f; // ~500ms full close
373-
static constexpr uint32_t kGateHoldMs = 100; // minimum close hold time
374-
static constexpr uint32_t kGateOpenHoldMs = 150; // minimum open hold time (reduced to avoid lingering on noise)
375-
static constexpr float kGateEnterVMax = 0.035f; // close if vMaxRelEMA <= this (raise to suppress ambient)
376-
static constexpr float kGateExitVMax = 0.090f; // open if vMaxRelEMA >= this (raise to require stronger music)
377-
378-
// Limit auto-gain in silence: anchor normalization to a multiple of mean noise
379-
static constexpr float kEnvNoiseFloorK = 37.0f; // denominator >= noiseMean * K
380-
381-
// Post-gate display floor to suppress shimmer from tiny values
382-
static constexpr float kBandFloorMin = 0.015f; // absolute min floor after gating
383-
static constexpr float kBandFloorScale = 0.15f; // floor scales with vMaxRelEMA (higher during music)
384-
385-
// Display gain (post-normalization, post-compression). Increase to use more vertical range.
386-
static constexpr float kDisplayGain = 1.5f;
387-
388349
// Energy spectrum processing (implemented inline below)
389350
//
390351
// Calculate a logarithmic scale for the bands like you would find on a graphic equalizer display
@@ -408,7 +369,6 @@ class SoundAnalyzer : public ISoundAnalyzer
408369
if (_vImaginary)
409370
_vImaginary[i] = 0.0f;
410371
}
411-
_cSamples = 0;
412372
for (int i = 0; i < NUM_BANDS; i++)
413373
_vPeaks[i] = 0.0f;
414374
}
@@ -521,8 +481,9 @@ class SoundAnalyzer : public ISoundAnalyzer
521481
PeakData ProcessPeaksEnergy()
522482
{
523483
float frameMax = 0.0f;
524-
float frameSumRaw = 0.0f; // sum of noise-subtracted, smoothed band power (pre-normalization)
484+
float frameRawMax = 0.0f; // max pre-subtraction band power (with compensation)
525485
float noiseSum = 0.0f; // accumulate noise floor for mean
486+
float noiseMaxAll = 0.0f; // track max noise floor across bands
526487
_framesProcessed++;
527488
const float binWidth = (float)SAMPLING_FREQUENCY / (MAX_SAMPLES / 2.0f);
528489
for (int b = 0; b < NUM_BANDS; b++)
@@ -544,102 +505,141 @@ class SoundAnalyzer : public ISoundAnalyzer
544505
float avgPower = (widthBins > 0) ? (sumPower / (float)widthBins) : 0.0f;
545506
avgPower *= _params.windowPowerCorrection;
546507

547-
// Frequency-dependent compensation
508+
// Apply frequency-dependent compensation
548509
float compensation = _params.bandCompLow + (_params.bandCompHigh - _params.bandCompLow) * ((float)b / (NUM_BANDS - 1));
549510
avgPower *= compensation;
550511

551-
// Adaptive noise floor
512+
// Track pre-subtraction max for SNR gating
513+
if (avgPower > frameRawMax)
514+
frameRawMax = avgPower;
515+
552516
if (avgPower > _noiseFloor[b])
553517
_noiseFloor[b] = _noiseFloor[b] * (1.0f - _params.energyNoiseAdapt) + (float)avgPower * _params.energyNoiseAdapt;
554518
else
555519
_noiseFloor[b] *= _params.energyNoiseDecay;
556520

521+
// Accumulate noise stats
557522
noiseSum += _noiseFloor[b];
523+
if (_noiseFloor[b] > noiseMaxAll)
524+
noiseMaxAll = _noiseFloor[b];
558525

559526
float signal = (float)avgPower - _noiseFloor[b];
560-
if (signal < 0.0f) signal = 0.0f;
527+
if (signal < 0.0f)
528+
signal = 0.0f;
561529
float smoothed = _rawPrev[b] * (1.0f - _params.energySmoothAlpha) + signal * _params.energySmoothAlpha;
562530
_rawPrev[b] = smoothed;
563-
_vPeaks[b] = smoothed; // store pre-normalized, smoothed signal
564-
frameSumRaw += smoothed;
531+
_vPeaks[b] = smoothed;
565532
if (smoothed > frameMax)
566533
frameMax = smoothed;
567534
}
568-
569-
// Update autoscale envelope
570535
if (frameMax > _energyMaxEnv)
571536
_energyMaxEnv = frameMax;
572537
else
573538
_energyMaxEnv = std::max(_params.energyMinEnv, _energyMaxEnv * _params.energyEnvDecay);
574539

575540
_envEMA = _envEMA * 0.95f + _energyMaxEnv * 0.05f;
576541

577-
// Timing and smoothing factors
578-
const float dt = std::max(0.0f, (float)g_Values.AppTime.LastFrameTime());
579-
580-
// Optional: keep frame-sum EMA for diagnostics (not used for gating now)
581-
const float emaAlphaFrame = std::clamp(dt * 5.0f, 0.0f, 1.0f); // ~200ms time constant
582-
_frameSumEMA = _frameSumEMA * (1.0f - emaAlphaFrame) + frameSumRaw * emaAlphaFrame;
583-
584-
// Gate based on normalized max band value relative to adaptive envelope,
585-
// with auto-gain capped by noise-anchored floor
586-
const float noiseMean = noiseSum / (float)NUM_BANDS;
587-
const float envFloor = std::max(_params.energyMinEnv, noiseMean * kEnvNoiseFloorK);
588-
const float normDen = std::max(_energyMaxEnv, envFloor);
589-
const float vMaxRel = frameMax / normDen;
590-
const float emaAlphaV = std::clamp(dt * 6.0f, 0.0f, 1.0f); // ~167ms time constant
591-
_vMaxRelEMA = _vMaxRelEMA * (1.0f - emaAlphaV) + vMaxRel * emaAlphaV;
592-
593-
const uint32_t now = millis();
594-
bool targetOpen = _gateEnv > 0.0f;
595-
if (_vMaxRelEMA <= kGateEnterVMax) {
596-
targetOpen = false;
542+
// Raw SNR-based frame gate (pre-normalization) to suppress steady HVAC and similar backgrounfd noises
543+
544+
float snrRaw = frameRawMax / (noiseMaxAll + 1e-9f);
545+
if (snrRaw < _params.frameSNRGate)
546+
{
597547
_frameGateHits++;
598-
if (_gateHoldUntil < now + kGateHoldMs)
599-
_gateHoldUntil = now + kGateHoldMs;
600-
} else if (_vMaxRelEMA >= kGateExitVMax) {
601-
targetOpen = true;
602-
if (_gateOpenHoldUntil < now + kGateOpenHoldMs)
603-
_gateOpenHoldUntil = now + kGateOpenHoldMs;
548+
for (int b = 0; b < NUM_BANDS; ++b)
549+
{
550+
_vPeaks[b] = 0.0f;
551+
_Peaks._Level[b] = 0.0f;
552+
}
553+
UpdateVU(0.0f);
554+
return _Peaks;
604555
}
605556

606-
// Apply holds (closed-hold has priority to prevent flicker right after closing)
607-
if (now < _gateHoldUntil)
608-
targetOpen = false;
609-
else if (now < _gateOpenHoldUntil)
610-
targetOpen = true;
557+
// Anchor normalization to noise-derived floor to avoid auto-gain blow-up
558+
float noiseMean = noiseSum / (float)NUM_BANDS;
559+
float envFloor = std::max(_params.energyMinEnv, noiseMean * _params.envFloorFromNoise);
560+
float normDen = std::max(_energyMaxEnv, envFloor);
561+
float invEnv = 1.0f / normDen;
611562

612-
// Attack/release envelope on gate
613-
if (targetOpen)
614-
_gateEnv = std::min(1.0f, _gateEnv + dt * kGateAttackPerSec);
615-
else
616-
_gateEnv = std::max(0.0f, _gateEnv - dt * kGateReleasePerSec);
563+
// Frame-level silence gate on normalized values
564+
float vMaxNorm = 0.0f;
565+
for (int b = 0; b < NUM_BANDS; ++b)
566+
{
567+
float vNorm = _vPeaks[b] * invEnv;
568+
if (vNorm > vMaxNorm)
569+
vMaxNorm = vNorm;
570+
}
571+
if (vMaxNorm < _params.frameSilenceGate)
572+
{
573+
_frameGateHits++;
574+
for (int b = 0; b < NUM_BANDS; ++b)
575+
{
576+
_vPeaks[b] = 0.0f;
577+
_Peaks._Level[b] = 0.0f;
578+
}
579+
UpdateVU(0.0f);
580+
return _Peaks;
581+
}
617582

618-
// Simple normalization using autoscale envelope with noise-anchored floor
619-
const float invEnv = 1.0f / normDen;
583+
#if ENABLE_AUDIO_DEBUG
584+
// Debug HVAC issues: log when frame gate should trigger but doesn't
585+
static uint32_t lastDebugMs = 0;
586+
if (millis() - lastDebugMs > 1000) // once per second
587+
{
588+
Serial.printf("FrameGate: vMaxNorm=%.3f, gate=%.3f, envelope=%.1f, hits=%zu\n",
589+
vMaxNorm, _params.frameSilenceGate, _energyMaxEnv, _frameGateHits);
590+
lastDebugMs = millis();
591+
}
592+
#endif
620593

621594
float sumNorm = 0.0f;
622595
for (int b = 0; b < NUM_BANDS; b++)
623596
{
624597
float v = _vPeaks[b] * invEnv;
625-
// Soft compression for smooth visuals
626-
v = cbrtf(std::max(0.0f, v));
627-
if (v > 1.0f) v = 1.0f;
628-
v *= _gateEnv; // apply gate envelope
629598

630-
// Apply additional display gain and clamp
631-
v *= kDisplayGain;
632-
if (v > 1.0f) v = 1.0f;
633-
634-
// Apply display floor to remove tiny ambient shimmer
635-
const float bandFloor = std::max(kBandFloorMin, kBandFloorScale * _vMaxRelEMA);
636-
if (v < bandFloor) { v = 0.0f; _bandGateHits++; }
599+
// Per-band normalized noise gate
600+
if (v < _params.normNoiseGate)
601+
{
602+
v = 0.0f;
603+
_bandGateHits++;
604+
}
605+
else
606+
{
607+
// Cube root compression for smooth flattening without spikes
608+
v = sqrt(cbrtf(std::max(0.0f, v)));
609+
}
637610

611+
if (v > 1.0f)
612+
v = 1.0f;
638613
_vPeaks[b] = v;
639614
_Peaks._Level[b] = v;
640615
sumNorm += v;
641616
}
642617

618+
#if ENABLE_AUDIO_SMOOTHING
619+
// Spatially smooth live spectrum peaks by blending with neighbors.
620+
// This affects displayed bars (and VU after we recompute below).
621+
{
622+
float tmp[NUM_BANDS];
623+
for (int b = 0; b < NUM_BANDS; ++b)
624+
{
625+
float v = _Peaks._Level[b];
626+
float l = (b > 0) ? _Peaks._Level[b - 1] : v;
627+
float r = (b < NUM_BANDS - 1) ? _Peaks._Level[b + 1] : v;
628+
tmp[b] = (v * 2.0f + l + r) * 0.25f; // (2*center + left + right) / 4
629+
}
630+
for (int b = 0; b < NUM_BANDS; ++b)
631+
{
632+
float v = tmp[b];
633+
if (v > 1.0f) v = 1.0f;
634+
_vPeaks[b] = v;
635+
_Peaks._Level[b] = v;
636+
}
637+
// Recompute VU from smoothed values
638+
sumNorm = 0.0f;
639+
for (int b = 0; b < NUM_BANDS; ++b)
640+
sumNorm += _Peaks._Level[b];
641+
}
642+
#endif
643643
UpdateVU((float)(sumNorm / NUM_BANDS));
644644
return _Peaks;
645645
}

platformio.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
; mesmerizer HUB75 info panel with audio effects, weather, info, etc,
2020

2121
[platformio]
22-
default_envs =
22+
default_envs =
2323
build_cache_dir = .pio/build_cache
2424
extra_configs =
2525
custom_*.ini ; This file can be created in the root directory to store user defined devices and environments.

0 commit comments

Comments
 (0)