Integrate DigiRadioFinale DSP program: exclusive source mux, real

Bass Boost1/SPhat1 enhancement blocks

Replaces the 74-parameter ADAU1701 program with the new 224-parameter
SigmaStudio export. The new netlist has no mixer -- MX1 is an
exclusive selector (DC1) passing through exactly one of three stereo
pairs (radio/bluetooth/beep) instead of blending them with independent
gains. core::MixSource/MixerState are gone; core::ActiveSource plus
IDsp::selectSource() replace applyMixer()/setInputVolume() throughout
the driver/service/API stack. AudioProfile.mixer -> activeSource;
the HTTP "mixer" JSON object -> a single "active_source" string.

DC1 turned out to need a raw 32-bit integer (0/1/2), not the 5.23
fixpoint its compiled TYPE_DC1 macro implies -- confirmed live by
writing both encodings and listening for which one actually switched
sources. Documented in Adau1701Driver::selectSource() and
adau1701::paramSourceIndex().

Bass Boost1 and SPhat1 are real ADI algorithm blocks in this revision,
so core::applyEnhancementsToEq() (the old EQ-band-overwrite hack for
bass/stereo enhancement) is deleted; IDsp::setBassBoostLevel()/
setStereoSpreadLevel() scale each block's compiled coefficients toward
identity/unity instead. Confirmed live: Bass Boost audible on real
program content (not a static test tone -- the algorithm is dynamics-
based), Stereo Spread audible but subtle. The EQ "locked" flag from
2026-08-24 (enhancements silently overwriting manually-set bands) no
longer applies to bands 1-5.

A full sweep of all 223 non-DC1 named parameters (writing each back to
its own compiled default) completed with zero failures, confirming the
whole new address space is reachable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 21:53:00 +02:00
co-authored by Claude Sonnet 5
parent b55f2dac9d
commit 3e5cfe814d
32 changed files with 5013 additions and 3351 deletions
@@ -12,16 +12,15 @@
*/
#pragma once
#include "core/ActiveSource.hpp"
#include "core/AudioProfile.hpp"
#include "core/DspError.hpp"
#include "core/EnhanceLevel.hpp"
#include "core/EnhancementsDesign.hpp"
#include "core/EqBandIndex.hpp"
#include "core/FrequencyHz.hpp"
#include "core/GainDb.hpp"
#include "core/IAudioProfileStore.hpp"
#include "core/IDsp.hpp"
#include "core/MixSource.hpp"
#include "core/StoreError.hpp"
#include <expected>
@@ -106,22 +105,19 @@ public:
const core::AudioProfile& profile, bool persist);
/**
* @brief setInputVolume — update one input path and apply live.
* @brief selectSource — switch MX1's active input pair and apply live.
*
* @dname setInputVolume
* @param source Si4684 or ESP32 path.
* @param left Left gain.
* @param right Right gain.
* @dname selectSource
* @param source Which stereo pair MX1 should pass through.
* @param persist When true and store is set, write NVS.
* @return Ok on success, or StoreError wrapping DspError/IoFailed.
* @pubstate updates profile_.mixer and safeloads.
* @pubstate updates profile_.activeSource and safeloads.
*
* @author Michele Bigi
* @date 2026-07-06
* @date 2026-08-25
*/
[[nodiscard]] std::expected<void, core::StoreError> setInputVolume(
core::MixSource source, core::GainDb left, core::GainDb right,
bool persist);
[[nodiscard]] std::expected<void, core::StoreError> selectSource(
core::ActiveSource source, bool persist);
/**
* @brief setMasterVolume — update master output and apply live.
@@ -239,9 +235,6 @@ private:
[[nodiscard]] std::expected<void, core::StoreError> applyProfileToDsp(
const core::AudioProfile& profile);
[[nodiscard]] std::expected<void, core::StoreError> applyEffectiveEq(
bool persist);
core::IDsp& dsp_;
core::IAudioProfileStore* store_;
core::AudioProfile profile_;
@@ -21,15 +21,6 @@ namespace {
constexpr char kTag[] = "AudioService";
[[nodiscard]] core::AudioProfile profileForHardware(
const core::AudioProfile& profile) noexcept
{
core::AudioProfile hardware = profile;
hardware.eq =
core::applyEnhancementsToEq(profile.eq, profile.enhancements);
return hardware;
}
} // namespace
AudioService::AudioService(core::IDsp& dsp, core::IAudioProfileStore* store)
@@ -81,28 +72,13 @@ std::expected<void, core::StoreError> AudioService::persistProfile() const
std::expected<void, core::StoreError> AudioService::applyProfileToDsp(
const core::AudioProfile& profile)
{
const core::AudioProfile hardware = profileForHardware(profile);
if (auto applied = dsp_.applyProfile(hardware); !applied) {
if (auto applied = dsp_.applyProfile(profile); !applied) {
ESP_LOGW(kTag, "applyProfileToDsp: DSP safeload failed");
return std::unexpected(core::StoreError::IoFailed);
}
return {};
}
std::expected<void, core::StoreError> AudioService::applyEffectiveEq(
bool persist)
{
const core::EqProfile effective = core::applyEnhancementsToEq(
profile_.eq, profile_.enhancements);
if (auto applied = dsp_.applyEq(effective); !applied) {
return std::unexpected(core::StoreError::IoFailed);
}
if (persist) {
return persistProfile();
}
return {};
}
std::expected<void, core::StoreError> AudioService::applyProfile(
const core::AudioProfile& profile, bool persist)
{
@@ -116,21 +92,13 @@ std::expected<void, core::StoreError> AudioService::applyProfile(
return {};
}
std::expected<void, core::StoreError> AudioService::setInputVolume(
core::MixSource source, core::GainDb left, core::GainDb right, bool persist)
std::expected<void, core::StoreError> AudioService::selectSource(
core::ActiveSource source, bool persist)
{
if (auto applied = dsp_.setInputVolume(source, left, right); !applied) {
if (auto applied = dsp_.selectSource(source); !applied) {
return std::unexpected(core::StoreError::IoFailed);
}
if (source == core::MixSource::Si4684) {
profile_.mixer.si4684Left = left;
profile_.mixer.si4684Right = right;
} else {
profile_.mixer.esp32Left = left;
profile_.mixer.esp32Right = right;
}
profile_.activeSource = source;
if (persist) {
return persistProfile();
}
@@ -155,10 +123,10 @@ std::expected<void, core::StoreError> AudioService::applyRadioFirstMix(
bool persist)
{
const core::GainDb unity = core::GainDb::zero();
profile_.mixer = core::MixerState::radioFirst();
profile_.activeSource = core::ActiveSource::Radio;
profile_.masterLeft = unity;
profile_.masterRight = unity;
if (auto applied = dsp_.applyMixer(profile_.mixer); !applied) {
if (auto applied = dsp_.selectSource(profile_.activeSource); !applied) {
return std::unexpected(core::StoreError::IoFailed);
}
if (auto master = dsp_.setMasterVolume(unity, unity); !master) {
@@ -179,21 +147,39 @@ std::expected<void, core::StoreError> AudioService::setEqBand(
.center = center,
.q = q,
});
return applyEffectiveEq(persist);
if (auto applied = dsp_.applyEq(profile_.eq); !applied) {
return std::unexpected(core::StoreError::IoFailed);
}
if (persist) {
return persistProfile();
}
return {};
}
std::expected<void, core::StoreError> AudioService::setStereoEnhance(
core::EnhanceLevel level, bool persist)
{
if (auto applied = dsp_.setStereoSpreadLevel(level); !applied) {
return std::unexpected(core::StoreError::IoFailed);
}
profile_.enhancements.stereo = level;
return applyEffectiveEq(persist);
if (persist) {
return persistProfile();
}
return {};
}
std::expected<void, core::StoreError> AudioService::setBassEnhance(
core::EnhanceLevel level, bool persist)
{
if (auto applied = dsp_.setBassBoostLevel(level); !applied) {
return std::unexpected(core::StoreError::IoFailed);
}
profile_.enhancements.bass = level;
return applyEffectiveEq(persist);
if (persist) {
return persistProfile();
}
return {};
}
std::expected<void, core::DspError> AudioService::setBeepEnabled(bool enabled)