Fix root cause of audio profile never persisting; document ADAU1701 registers

Root cause (found via SigmaStudio firmware analysis requested this
session): NVS was initialized AFTER HardwareBootstrap::boot(), which
internally calls AudioService::loadAndApply() to restore the saved
mixer/EQ/master-volume profile. Every nvs_open() inside
NvsAudioProfileStore::hasProfile()/loadProfile() failed with
ESP_ERR_NVS_NOT_INITIALIZED (0x1101), silently swallowed as "no saved
profile" -- the audio profile was never actually restored on any boot,
regardless of how many times it was saved via PUT /api/audio/profile.
Fixed by moving secure_store::initEncryptedStorage() to the top of
app_main(), before HardwareBootstrap::boot() (nvs_flash_init() has no
hardware dependency, so this is safe).

Second, related bug: HardwareBootstrap::boot() called
AudioService::applyRadioFirstMix() unconditionally right after
loadAndApply(), discarding any just-restored mixer/master values on
every boot. loadAndApply() now returns whether it actually restored a
profile from NVS; the radio-first fallback only applies when nothing
was saved.

Verified live: a distinct mixer+master+5-EQ-band test pattern now
survives a full reboot exactly as saved (previously always reset to
factory default). DAB/FM/BT unaffected.

Also: added a "locked" flag per EQ band in the audio profile JSON --
band 0 is always locked (fixed high-pass, Adau1701Driver::applyEq()
never safeloads it) and bands 1-2/3-5 are locked whenever
bass_level/stereo_level is active (core::applyEnhancementsToEq()
overwrites them with formula-derived values). This was previously
undiscoverable from the API -- GET echoed back the stored, inert value
with no indication it wasn't what was actually playing.

Full register-by-register analysis of the compiled SigmaStudio program
(signal chain, all 74 Parameter RAM addresses grouped by function, HTTP
API mapping, every endpoint tested live) in
docs/adau1701-sigmastudio-analysis.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 15:52:38 +02:00
co-authored by Claude Sonnet 5
parent a7a5311c2c
commit af51948912
7 changed files with 287 additions and 18 deletions
+15 -6
View File
@@ -253,13 +253,22 @@ std::expected<void, HardwareBootError> HardwareBootstrap::boot()
"auto-tune");
}
if (auto audioResult = gAudioService.loadAndApply(); !audioResult) {
// Only fall back to the hardcoded radio-first mix (Si4684 open, ESP32
// muted) when NO saved profile was restored (2026-08-24 fix): this used
// to run unconditionally, silently discarding the user's saved
// mixer/master-volume settings on every single boot regardless of
// whether loadAndApply() actually found something in NVS.
const auto audioResult = gAudioService.loadAndApply();
if (!audioResult) {
ESP_LOGW(kTag, "ADAU1701 profile apply failed");
}
if (auto radioMix = gAudioService.applyRadioFirstMix(false); !radioMix) {
ESP_LOGW(kTag, "ADAU1701 radio-first mix failed");
} else {
ESP_LOGI(kTag, "ADAU1701 Si4684 input routed (radio-first mix)");
} else if (!*audioResult) {
if (auto radioMix = gAudioService.applyRadioFirstMix(false);
!radioMix) {
ESP_LOGW(kTag, "ADAU1701 radio-first mix failed");
} else {
ESP_LOGI(kTag, "ADAU1701 Si4684 input routed (radio-first mix, "
"no saved profile)");
}
}
if (auto btResult = gBt1035.boot(); !btResult) {
+15 -5
View File
@@ -160,6 +160,21 @@ extern "C" void app_main()
{
ESP_LOGI(kTag, "DigiRadio firmware boot");
// NVS must be initialized before HardwareBootstrap::boot(), which
// internally calls AudioService::loadAndApply() to restore the saved
// mixer/EQ/master-volume profile (2026-08-24 root-cause fix): this used
// to run in the other order, so every nvs_open() inside
// NvsAudioProfileStore::hasProfile()/loadProfile() failed with
// ESP_ERR_NVS_NOT_INITIALIZED (0x1101, silently swallowed as "no saved
// profile") on every single boot -- the audio profile was never
// actually restored, no matter how many times it was saved via
// PUT /api/audio/profile. nvs_flash_init() itself has no hardware
// dependency (pure flash-partition access), so moving it first is safe.
if (auto nvsResult = secure_store::initEncryptedStorage(); !nvsResult) {
ESP_LOGE(kTag, "NVS init failed — halting");
return;
}
auto hwResult = hardware::HardwareBootstrap::boot();
if (!hwResult) {
ESP_LOGE(kTag, "companion chip boot failed — halting");
@@ -183,11 +198,6 @@ extern "C" void app_main()
return;
#endif
if (auto nvsResult = secure_store::initEncryptedStorage(); !nvsResult) {
ESP_LOGE(kTag, "NVS init failed — halting");
return;
}
static secure_store::NvsSecureStore store;
static tuner::TunerService tunerService(