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:
@@ -19,11 +19,9 @@ idf_component_register(
|
||||
"src/EqBandIndex.cpp"
|
||||
"src/BiquadCoefficients.cpp"
|
||||
"src/BiquadDesign.cpp"
|
||||
"src/MixerState.cpp"
|
||||
"src/EqProfile.cpp"
|
||||
"src/EnhanceLevel.cpp"
|
||||
"src/AudioEnhancements.cpp"
|
||||
"src/EnhancementsDesign.cpp"
|
||||
"src/AudioProfile.cpp"
|
||||
"src/AudioProfileJson.cpp"
|
||||
"src/Bt1035At.cpp"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @file ActiveSource.hpp
|
||||
* @brief ADAU1701 MX1 input source selector.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief ActiveSource — the one stereo pair MX1 currently passes through.
|
||||
*
|
||||
* @dname ActiveSource
|
||||
* @return n/a (type)
|
||||
* @pubstate n/a
|
||||
*
|
||||
* The DigiRadioFinale SigmaStudio revision (2026-08-25) replaced St Mixer1
|
||||
* (which combined Si4684 + ESP32 + Beep simultaneously, each at its own
|
||||
* gain) with MX1, a DC-controlled exclusive mux: only one of the three
|
||||
* stereo pairs reaches Param EQ1 at a time, selected by the DC1 cell.
|
||||
* Per-source gain trims (the old Si4674/ESP32 cells) no longer exist —
|
||||
* level is controlled solely by Master Volume and the EQ, downstream of
|
||||
* the selected source.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
enum class ActiveSource {
|
||||
Radio, ///< Si4684 tuner (FM/DAB), MX1 pair index 0.
|
||||
Bluetooth, ///< ESP32 I2S input, MX1 pair index 1.
|
||||
Beep, ///< Internal test tone, MX1 pair index 2.
|
||||
};
|
||||
|
||||
// DC1 is a raw 32-bit integer index (0/1/2), not the 5.23 fixpoint its
|
||||
// compiled TYPE_DC1/VALUE_DC1 macros would suggest -- confirmed live
|
||||
// 2026-08-25 (see adau1701::paramSourceIndex() and Adau1701Driver::
|
||||
// selectSource()).
|
||||
|
||||
} // namespace core
|
||||
@@ -17,19 +17,20 @@
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief AudioEnhancements — psychoacoustic EQ overlays (0–100 each).
|
||||
* @brief AudioEnhancements — dedicated-block enhancement levels (0–100).
|
||||
*
|
||||
* @dname AudioEnhancements
|
||||
* @return n/a (type)
|
||||
* @pubstate Mapped to PEQ bands at runtime (manual chapter sigmastudio).
|
||||
* No dedicated SigmaStudio blocks; see \c applyEnhancementsToEq.
|
||||
* @pubstate Applied via IDsp::setStereoSpreadLevel/setBassBoostLevel, which
|
||||
* scale the SPhat1/Bass Boost1 SigmaStudio blocks toward their
|
||||
* compiled response; does not touch EqProfile.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
struct AudioEnhancements {
|
||||
EnhanceLevel stereo; ///< Stereo depth / presence (PEQ bands 3–5).
|
||||
EnhanceLevel bass; ///< Bass emphasis (PEQ bands 1–2).
|
||||
EnhanceLevel stereo; ///< Stereo spread (SPhat1 spatializer).
|
||||
EnhanceLevel bass; ///< Bass boost intensity (Bass Boost1).
|
||||
|
||||
/**
|
||||
* @brief factoryDefault — both enhancements off.
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/ActiveSource.hpp"
|
||||
#include "core/AudioEnhancements.hpp"
|
||||
#include "core/EqProfile.hpp"
|
||||
#include "core/GainDb.hpp"
|
||||
#include "core/MixerState.hpp"
|
||||
|
||||
namespace core {
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace core {
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
struct AudioProfile {
|
||||
MixerState mixer; ///< Input and stereo-mixer gains.
|
||||
ActiveSource activeSource; ///< Which MX1 pair reaches Param EQ1.
|
||||
EqProfile eq; ///< Six-band parametric EQ.
|
||||
GainDb masterLeft; ///< Multiple 1 master volume, left.
|
||||
GainDb masterRight; ///< Multiple 1 master volume, right.
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* @file EnhancementsDesign.hpp
|
||||
* @brief Map enhancement levels onto Param EQ1 bands (host-testable).
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/AudioEnhancements.hpp"
|
||||
#include "core/EqProfile.hpp"
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief applyEnhancementsToEq — merge enhancement overlays into an EQ profile.
|
||||
*
|
||||
* @dname applyEnhancementsToEq
|
||||
* @param base User EQ settings (bands not touched stay unchanged).
|
||||
* @param enhancements Stereo and bass levels (0 = use base band only).
|
||||
* @return EqProfile with affected PEQ bands updated for safeload.
|
||||
* @pubstate none
|
||||
*
|
||||
* Stereo (bands 3–5): slight 1\,kHz cut plus 3/8\,kHz lift for depth.
|
||||
* Bass (bands 1–2): 100\,Hz and 400\,Hz peaking boost.
|
||||
* Band 0 (high-pass) is never modified.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] EqProfile applyEnhancementsToEq(
|
||||
const EqProfile& base, const AudioEnhancements& enhancements) noexcept;
|
||||
|
||||
} // namespace core
|
||||
@@ -12,13 +12,13 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/ActiveSource.hpp"
|
||||
#include "core/AudioProfile.hpp"
|
||||
#include "core/DspError.hpp"
|
||||
#include "core/EnhanceLevel.hpp"
|
||||
#include "core/EqBandIndex.hpp"
|
||||
#include "core/FrequencyHz.hpp"
|
||||
#include "core/GainDb.hpp"
|
||||
#include "core/MixSource.hpp"
|
||||
#include "core/MixerState.hpp"
|
||||
#include "core/EqProfile.hpp"
|
||||
|
||||
#include <expected>
|
||||
@@ -56,18 +56,18 @@ public:
|
||||
const AudioProfile& profile) = 0;
|
||||
|
||||
/**
|
||||
* @brief applyMixer — safeload input and stereo-mixer gains.
|
||||
* @brief selectSource — safeload MX1's DC1 control to pick one source.
|
||||
*
|
||||
* @dname applyMixer
|
||||
* @param mixer Per-source and St Mixer1 levels.
|
||||
* @dname selectSource
|
||||
* @param source Which stereo pair MX1 passes through to Param EQ1.
|
||||
* @return Ok on success, or DspError.
|
||||
* @pubstate writes ADAU1701 parameter RAM via safeload.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<void, DspError> applyMixer(
|
||||
const MixerState& mixer) = 0;
|
||||
[[nodiscard]] virtual std::expected<void, DspError> selectSource(
|
||||
ActiveSource source) = 0;
|
||||
|
||||
/**
|
||||
* @brief applyEq — safeload all six PEQ bands.
|
||||
@@ -83,22 +83,6 @@ public:
|
||||
[[nodiscard]] virtual std::expected<void, DspError> applyEq(
|
||||
const EqProfile& eq) = 0;
|
||||
|
||||
/**
|
||||
* @brief setInputVolume — safeload one input path (Si4684 or ESP32).
|
||||
*
|
||||
* @dname setInputVolume
|
||||
* @param source Tuner or ESP32 I2S path.
|
||||
* @param left Left channel gain.
|
||||
* @param right Right channel gain.
|
||||
* @return Ok on success, or DspError.
|
||||
* @pubstate writes ADAU1701 parameter RAM via safeload.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<void, DspError> setInputVolume(
|
||||
MixSource source, GainDb left, GainDb right) = 0;
|
||||
|
||||
/**
|
||||
* @brief setMasterVolume — safeload Multiple 1 master output gain.
|
||||
*
|
||||
@@ -146,6 +130,42 @@ public:
|
||||
[[nodiscard]] virtual std::expected<void, DspError> setBeepEnabled(
|
||||
bool enabled) = 0;
|
||||
|
||||
/**
|
||||
* @brief setBassBoostLevel — scale Bass Boost1 toward its compiled
|
||||
* curve.
|
||||
*
|
||||
* @dname setBassBoostLevel
|
||||
* @param level 0 = flat bypass, 100 = the full SigmaStudio-tuned
|
||||
* Dynamic Bass Boost response.
|
||||
* @return Ok on success, or DspError.
|
||||
* @pubstate writes ADAU1701 parameter RAM via safeload. Linearly
|
||||
* interpolates the compiled crossover filter and 33-point
|
||||
* compander curve toward identity/unity; BASSFREQUENCY and
|
||||
* the time constant are left at their tuned defaults.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<void, DspError> setBassBoostLevel(
|
||||
EnhanceLevel level) = 0;
|
||||
|
||||
/**
|
||||
* @brief setStereoSpreadLevel — scale SPhat1's stereo spread amount.
|
||||
*
|
||||
* @dname setStereoSpreadLevel
|
||||
* @param level 0 = no added spread, 100 = the full SigmaStudio-tuned
|
||||
* SuperPhat spread.
|
||||
* @return Ok on success, or DspError.
|
||||
* @pubstate writes ADAU1701 parameter RAM via safeload. Scales
|
||||
* SPREAD1/SPREAD2 linearly; the crossover filter and
|
||||
* compander curve are left at their tuned defaults.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<void, DspError> setStereoSpreadLevel(
|
||||
EnhanceLevel level) = 0;
|
||||
|
||||
/**
|
||||
* @brief writeRawParam — safeload any named Parameter RAM cell.
|
||||
*
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* @file MixSource.hpp
|
||||
* @brief ADAU1701 input path selector for per-source volume control.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief MixSource — Si4684 tuner or ESP32 I2S input on the ADAU1701.
|
||||
*
|
||||
* @dname MixSource
|
||||
* @return n/a (type)
|
||||
* @pubstate n/a
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
enum class MixSource {
|
||||
Si4684,
|
||||
Esp32,
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* @file MixerState.hpp
|
||||
* @brief ADAU1701 input and stereo-mixer gain snapshot.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/GainDb.hpp"
|
||||
|
||||
namespace core {
|
||||
|
||||
/**
|
||||
* @brief MixerState — per-source and St Mixer1 levels for the ADAU1701.
|
||||
*
|
||||
* @dname MixerState
|
||||
* @return n/a (type)
|
||||
* @pubstate Immutable snapshot applied by AudioService via IDsp.
|
||||
*
|
||||
* Maps to SigmaStudio modules Si4674, ESP32, and St Mixer1.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
struct MixerState {
|
||||
GainDb si4684Left; ///< Si4674 volume control, left channel.
|
||||
GainDb si4684Right; ///< Si4674 volume control, right channel.
|
||||
GainDb esp32Left; ///< ESP32 volume control, left channel.
|
||||
GainDb esp32Right; ///< ESP32 volume control, right channel.
|
||||
GainDb mixLeft; ///< St Mixer1 ST0 (Si4684) input level.
|
||||
GainDb mixRight; ///< St Mixer1 ST1 (ESP32) input level.
|
||||
|
||||
/**
|
||||
* @brief factoryDefault — unity gains on all paths.
|
||||
*
|
||||
* @dname factoryDefault
|
||||
* @return MixerState with 0 dB on every control.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] static MixerState factoryDefault() noexcept;
|
||||
|
||||
/**
|
||||
* @brief radioFirst — Si4684 path open, ESP32 path muted.
|
||||
*
|
||||
* @dname radioFirst
|
||||
* @return MixerState for FM/DAB tuner audio to the Bose I2S chain.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-05
|
||||
*/
|
||||
[[nodiscard]] static MixerState radioFirst() noexcept;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -19,7 +19,7 @@ AudioProfile AudioProfile::factoryDefault() noexcept
|
||||
{
|
||||
const GainDb unity = GainDb::zero();
|
||||
return AudioProfile{
|
||||
.mixer = MixerState::factoryDefault(),
|
||||
.activeSource = ActiveSource::Radio,
|
||||
.eq = EqProfile::factoryDefault(),
|
||||
.masterLeft = unity,
|
||||
.masterRight = unity,
|
||||
|
||||
@@ -92,35 +92,48 @@ namespace {
|
||||
return GainDb::tryFromDb(db);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<MixerState, ParseError> parseMixerJson(
|
||||
[[nodiscard]] std::string_view activeSourceName(ActiveSource source) noexcept
|
||||
{
|
||||
switch (source) {
|
||||
case ActiveSource::Radio:
|
||||
return "radio";
|
||||
case ActiveSource::Bluetooth:
|
||||
return "bluetooth";
|
||||
case ActiveSource::Beep:
|
||||
return "beep";
|
||||
}
|
||||
return "radio";
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<ActiveSource, ParseError> parseActiveSourceJson(
|
||||
std::string_view json)
|
||||
{
|
||||
const std::size_t mixerStart = json.find("\"mixer\"");
|
||||
if (mixerStart == std::string_view::npos) {
|
||||
constexpr std::string_view kKey = "\"active_source\"";
|
||||
const std::size_t needlePos = json.find(kKey);
|
||||
if (needlePos == std::string_view::npos) {
|
||||
return std::unexpected(ParseError::MissingField);
|
||||
}
|
||||
const std::string_view mixer = json.substr(mixerStart);
|
||||
|
||||
const auto si4684Left = parseGainField(mixer, "si4684_left_db");
|
||||
const auto si4684Right = parseGainField(mixer, "si4684_right_db");
|
||||
const auto esp32Left = parseGainField(mixer, "esp32_left_db");
|
||||
const auto esp32Right = parseGainField(mixer, "esp32_right_db");
|
||||
const auto mixLeft = parseGainField(mixer, "mix_left_db");
|
||||
const auto mixRight = parseGainField(mixer, "mix_right_db");
|
||||
|
||||
if (!si4684Left || !si4684Right || !esp32Left || !esp32Right || !mixLeft
|
||||
|| !mixRight) {
|
||||
const std::size_t quoteStart = json.find('"', needlePos + kKey.size());
|
||||
if (quoteStart == std::string_view::npos) {
|
||||
return std::unexpected(ParseError::MissingField);
|
||||
}
|
||||
const std::size_t quoteEnd = json.find('"', quoteStart + 1U);
|
||||
if (quoteEnd == std::string_view::npos) {
|
||||
return std::unexpected(ParseError::MissingField);
|
||||
}
|
||||
const std::string_view value =
|
||||
json.substr(quoteStart + 1U, quoteEnd - quoteStart - 1U);
|
||||
|
||||
return MixerState{
|
||||
.si4684Left = *si4684Left,
|
||||
.si4684Right = *si4684Right,
|
||||
.esp32Left = *esp32Left,
|
||||
.esp32Right = *esp32Right,
|
||||
.mixLeft = *mixLeft,
|
||||
.mixRight = *mixRight,
|
||||
};
|
||||
if (value == "radio") {
|
||||
return ActiveSource::Radio;
|
||||
}
|
||||
if (value == "bluetooth") {
|
||||
return ActiveSource::Bluetooth;
|
||||
}
|
||||
if (value == "beep") {
|
||||
return ActiveSource::Beep;
|
||||
}
|
||||
return std::unexpected(ParseError::MissingField);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<EqProfile, ParseError> parseEqJson(
|
||||
@@ -207,14 +220,8 @@ namespace {
|
||||
std::string serializeAudioProfileJson(const AudioProfile& profile)
|
||||
{
|
||||
std::ostringstream out;
|
||||
const auto& m = profile.mixer;
|
||||
out << "{\"mixer\":{"
|
||||
<< "\"si4684_left_db\":" << m.si4684Left.value() << ','
|
||||
<< "\"si4684_right_db\":" << m.si4684Right.value() << ','
|
||||
<< "\"esp32_left_db\":" << m.esp32Left.value() << ','
|
||||
<< "\"esp32_right_db\":" << m.esp32Right.value() << ','
|
||||
<< "\"mix_left_db\":" << m.mixLeft.value() << ','
|
||||
<< "\"mix_right_db\":" << m.mixRight.value() << "},"
|
||||
out << "{\"active_source\":\"" << activeSourceName(profile.activeSource)
|
||||
<< "\","
|
||||
<< "\"master\":{"
|
||||
<< "\"left_db\":" << profile.masterLeft.value() << ','
|
||||
<< "\"right_db\":" << profile.masterRight.value() << "},"
|
||||
@@ -223,19 +230,13 @@ std::string serializeAudioProfileJson(const AudioProfile& profile)
|
||||
// Per-band "locked" flag, added 2026-08-24: band 0 is a fixed high-pass
|
||||
// Adau1701Driver::applyEq() never safeloads (SigmaStudio band 1, ST0 in
|
||||
// the compiled program -- whatever gain_db/center_hz/q is stored/sent
|
||||
// for it has zero audible effect, always). Bands 1-2 are overwritten by
|
||||
// core::applyEnhancementsToEq() with formula-derived values whenever
|
||||
// bass_level > 0; bands 3-5 likewise whenever stereo_level > 0 -- a
|
||||
// manual edit to those bands is silently inaudible while the
|
||||
// corresponding enhancement is active. This was previously
|
||||
// undiscoverable from the API response (GET echoed back the stored,
|
||||
// inert value with no indication it wasn't what was actually playing);
|
||||
// "locked":true now tells a client to grey out that slider instead of
|
||||
// letting the user "fix" a value that can't take effect.
|
||||
const bool bassLocks = profile.enhancements.bass.value() > 0U;
|
||||
const bool stereoLocks = profile.enhancements.stereo.value() > 0U;
|
||||
// for it has zero audible effect, always). Since the 2026-08-25
|
||||
// DigiRadioFinale revision, bass/stereo enhancement drive the dedicated
|
||||
// Bass Boost1/SPhat1 blocks directly instead of overwriting EQ bands,
|
||||
// so bands 1-5 are no longer locked by enhancement levels -- only
|
||||
// band 0 remains permanently inert.
|
||||
const bool locked[EqBandIndex::kBandCount] = {
|
||||
true, bassLocks, bassLocks, stereoLocks, stereoLocks, stereoLocks,
|
||||
true, false, false, false, false, false,
|
||||
};
|
||||
|
||||
const auto& bands = profile.eq.bands();
|
||||
@@ -263,9 +264,9 @@ std::expected<AudioProfile, ParseError> parseAudioProfileJson(
|
||||
return std::unexpected(ParseError::InvalidJson);
|
||||
}
|
||||
|
||||
const auto mixer = parseMixerJson(json);
|
||||
if (!mixer) {
|
||||
return std::unexpected(mixer.error());
|
||||
const auto activeSource = parseActiveSourceJson(json);
|
||||
if (!activeSource) {
|
||||
return std::unexpected(activeSource.error());
|
||||
}
|
||||
|
||||
const auto eq = parseEqJson(json);
|
||||
@@ -290,7 +291,7 @@ std::expected<AudioProfile, ParseError> parseAudioProfileJson(
|
||||
}
|
||||
|
||||
return AudioProfile{
|
||||
.mixer = *mixer,
|
||||
.activeSource = *activeSource,
|
||||
.eq = *eq,
|
||||
.masterLeft = *masterLeft,
|
||||
.masterRight = *masterRight,
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* @file EnhancementsDesign.cpp
|
||||
* @brief Enhancement-to-EQ mapping implementation.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
#include "core/EnhancementsDesign.hpp"
|
||||
|
||||
#include "core/EqBandIndex.hpp"
|
||||
#include "core/FrequencyHz.hpp"
|
||||
#include "core/GainDb.hpp"
|
||||
|
||||
namespace core {
|
||||
|
||||
namespace {
|
||||
|
||||
void setBand(EqProfile& profile, std::uint8_t index, GainDb gain,
|
||||
FrequencyHz center, float q) noexcept
|
||||
{
|
||||
const auto band = EqBandIndex::tryFromIndex(index);
|
||||
if (!band) {
|
||||
return;
|
||||
}
|
||||
profile.setBand(*band, EqBandSettings{
|
||||
.gain = gain,
|
||||
.center = center,
|
||||
.q = q,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] GainDb gainFromDb(float db) noexcept
|
||||
{
|
||||
return *GainDb::tryFromDb(db);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EqProfile applyEnhancementsToEq(const EqProfile& base,
|
||||
const AudioEnhancements& enhancements) noexcept
|
||||
{
|
||||
EqProfile profile = base;
|
||||
|
||||
if (enhancements.bass.value() > 0U) {
|
||||
const float t = enhancements.bass.fraction();
|
||||
setBand(profile, 1U, gainFromDb(9.0F * t),
|
||||
*FrequencyHz::tryFromHz(100U), 0.9F);
|
||||
setBand(profile, 2U, gainFromDb(3.0F * t),
|
||||
*FrequencyHz::tryFromHz(400U), 1.0F);
|
||||
}
|
||||
|
||||
if (enhancements.stereo.value() > 0U) {
|
||||
const float t = enhancements.stereo.fraction();
|
||||
setBand(profile, 3U, gainFromDb(-1.5F * t),
|
||||
*FrequencyHz::tryFromHz(1000U), 1.0F);
|
||||
setBand(profile, 4U, gainFromDb(2.0F * t),
|
||||
*FrequencyHz::tryFromHz(3000U), 1.0F);
|
||||
setBand(profile, 5U, gainFromDb(4.0F * t),
|
||||
*FrequencyHz::tryFromHz(8000U), 1.0F);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @file MixerState.cpp
|
||||
* @brief MixerState factory defaults.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
|
||||
#include "core/MixerState.hpp"
|
||||
|
||||
namespace core {
|
||||
|
||||
MixerState MixerState::factoryDefault() noexcept
|
||||
{
|
||||
const GainDb unity = GainDb::zero();
|
||||
return MixerState{
|
||||
.si4684Left = unity,
|
||||
.si4684Right = unity,
|
||||
.esp32Left = unity,
|
||||
.esp32Right = unity,
|
||||
.mixLeft = unity,
|
||||
.mixRight = unity,
|
||||
};
|
||||
}
|
||||
|
||||
MixerState MixerState::radioFirst() noexcept
|
||||
{
|
||||
const GainDb unity = GainDb::zero();
|
||||
const GainDb muted = *GainDb::tryFromDb(GainDb::kMinDb);
|
||||
return MixerState{
|
||||
.si4684Left = unity,
|
||||
.si4684Right = unity,
|
||||
.esp32Left = muted,
|
||||
.esp32Right = muted,
|
||||
.mixLeft = unity,
|
||||
.mixRight = muted,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
@@ -31,11 +31,9 @@ add_library(digiradio_core STATIC
|
||||
"${CORE_SRC_DIR}/EqBandIndex.cpp"
|
||||
"${CORE_SRC_DIR}/BiquadCoefficients.cpp"
|
||||
"${CORE_SRC_DIR}/BiquadDesign.cpp"
|
||||
"${CORE_SRC_DIR}/MixerState.cpp"
|
||||
"${CORE_SRC_DIR}/EqProfile.cpp"
|
||||
"${CORE_SRC_DIR}/EnhanceLevel.cpp"
|
||||
"${CORE_SRC_DIR}/AudioEnhancements.cpp"
|
||||
"${CORE_SRC_DIR}/EnhancementsDesign.cpp"
|
||||
"${CORE_SRC_DIR}/AudioProfile.cpp"
|
||||
"${CORE_SRC_DIR}/AudioProfileJson.cpp"
|
||||
"${CORE_SRC_DIR}/Bt1035At.cpp"
|
||||
@@ -110,10 +108,6 @@ add_executable(audio_profile_json_test audio_profile_json_test.cpp)
|
||||
target_link_libraries(audio_profile_json_test PRIVATE digiradio_core)
|
||||
add_test(NAME audio_profile_json_test COMMAND audio_profile_json_test)
|
||||
|
||||
add_executable(enhancements_design_test enhancements_design_test.cpp)
|
||||
target_link_libraries(enhancements_design_test PRIVATE digiradio_core)
|
||||
add_test(NAME enhancements_design_test COMMAND enhancements_design_test)
|
||||
|
||||
add_executable(station_service_test station_service_test.cpp)
|
||||
target_include_directories(station_service_test PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/host_stubs"
|
||||
|
||||
@@ -69,6 +69,23 @@ namespace {
|
||||
std::cerr << "enhancements round-trip mismatch\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (parsed->activeSource != core::ActiveSource::Radio) {
|
||||
std::cerr << "default active_source mismatch\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
profile.activeSource = core::ActiveSource::Bluetooth;
|
||||
const std::string json3 = core::serializeAudioProfileJson(profile);
|
||||
if (json3.find(R"("active_source":"bluetooth")") == std::string::npos) {
|
||||
std::cerr << "active_source serialization mismatch: " << json3 << '\n';
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const auto parsed3 = core::parseAudioProfileJson(json3);
|
||||
if (!parsed3 || parsed3->activeSource != core::ActiveSource::Bluetooth) {
|
||||
std::cerr << "active_source round-trip mismatch\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
/**
|
||||
* @file enhancements_design_test.cpp
|
||||
* @brief Host tests for enhancement-to-EQ mapping.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
|
||||
#include "core/AudioEnhancements.hpp"
|
||||
#include "core/EnhanceLevel.hpp"
|
||||
#include "core/EnhancementsDesign.hpp"
|
||||
#include "core/EqBandIndex.hpp"
|
||||
#include "core/EqProfile.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool nearlyEqual(float a, float b) noexcept
|
||||
{
|
||||
return std::fabs(a - b) < 0.01F;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runBassMappingTest()
|
||||
{
|
||||
const auto level = core::EnhanceLevel::tryFromLevel(100U);
|
||||
if (!level) {
|
||||
std::cerr << "level setup failed\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
core::AudioEnhancements enhancements = core::AudioEnhancements::factoryDefault();
|
||||
enhancements.bass = *level;
|
||||
|
||||
const core::EqProfile base = core::EqProfile::factoryDefault();
|
||||
const core::EqProfile effective =
|
||||
core::applyEnhancementsToEq(base, enhancements);
|
||||
|
||||
const auto band1 = core::EqBandIndex::tryFromIndex(1U);
|
||||
const auto band2 = core::EqBandIndex::tryFromIndex(2U);
|
||||
if (!band1 || !band2) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!nearlyEqual(effective.band(*band1).gain.value(), 9.0F)
|
||||
|| !nearlyEqual(effective.band(*band2).gain.value(), 3.0F)) {
|
||||
std::cerr << "bass mapping mismatch\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runStereoMappingTest()
|
||||
{
|
||||
const auto level = core::EnhanceLevel::tryFromLevel(50U);
|
||||
if (!level) {
|
||||
std::cerr << "level setup failed\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
core::AudioEnhancements enhancements = core::AudioEnhancements::factoryDefault();
|
||||
enhancements.stereo = *level;
|
||||
|
||||
const core::EqProfile base = core::EqProfile::factoryDefault();
|
||||
const core::EqProfile effective =
|
||||
core::applyEnhancementsToEq(base, enhancements);
|
||||
|
||||
const auto band3 = core::EqBandIndex::tryFromIndex(3U);
|
||||
const auto band4 = core::EqBandIndex::tryFromIndex(4U);
|
||||
const auto band5 = core::EqBandIndex::tryFromIndex(5U);
|
||||
if (!band3 || !band4 || !band5) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!nearlyEqual(effective.band(*band3).gain.value(), -0.75F)
|
||||
|| !nearlyEqual(effective.band(*band4).gain.value(), 1.0F)
|
||||
|| !nearlyEqual(effective.band(*band5).gain.value(), 2.0F)) {
|
||||
std::cerr << "stereo mapping mismatch\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runOffLeavesBaseTest()
|
||||
{
|
||||
const core::EqProfile base = core::EqProfile::factoryDefault();
|
||||
const core::AudioEnhancements off = core::AudioEnhancements::factoryDefault();
|
||||
const core::EqProfile effective = core::applyEnhancementsToEq(base, off);
|
||||
|
||||
const auto band1 = core::EqBandIndex::tryFromIndex(1U);
|
||||
if (!band1) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!nearlyEqual(effective.band(*band1).gain.value(),
|
||||
base.band(*band1).gain.value())) {
|
||||
std::cerr << "off should leave base unchanged\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
if (runBassMappingTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runStereoMappingTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runOffLeavesBaseTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -40,8 +40,8 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> applyMixer(
|
||||
const core::MixerState&) override
|
||||
[[nodiscard]] std::expected<void, core::DspError> selectSource(
|
||||
core::ActiveSource) override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
@@ -52,12 +52,6 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> setInputVolume(
|
||||
core::MixSource, core::GainDb, core::GainDb) override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> setMasterVolume(
|
||||
core::GainDb, core::GainDb) override
|
||||
{
|
||||
@@ -76,6 +70,18 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> setBassBoostLevel(
|
||||
core::EnhanceLevel) override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> setStereoSpreadLevel(
|
||||
core::EnhanceLevel) override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> writeRawParam(
|
||||
unsigned, float) override
|
||||
{
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
#include "adau1701/Adau1701Error.hpp"
|
||||
|
||||
#include "core/AudioProfile.hpp"
|
||||
#include "core/EnhanceLevel.hpp"
|
||||
#include "core/EqBandIndex.hpp"
|
||||
#include "core/EqProfile.hpp"
|
||||
#include "core/FrequencyHz.hpp"
|
||||
#include "core/GainDb.hpp"
|
||||
#include "core/IDspProgramSource.hpp"
|
||||
#include "core/MixSource.hpp"
|
||||
#include "core/MixerState.hpp"
|
||||
#include "core/ActiveSource.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
@@ -144,18 +144,18 @@ public:
|
||||
const core::AudioProfile& profile);
|
||||
|
||||
/**
|
||||
* @brief applyMixer — safeload input and stereo-mixer gains.
|
||||
* @brief selectSource — safeload DC1 to pick MX1's active input pair.
|
||||
*
|
||||
* @dname applyMixer
|
||||
* @param mixer Per-source and St Mixer1 levels.
|
||||
* @dname selectSource
|
||||
* @param source Which stereo pair MX1 passes through.
|
||||
* @return Ok on success, or Adau1701Error.
|
||||
* @pubstate writes parameter RAM via safeload.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
[[nodiscard]] std::expected<void, Adau1701Error> applyMixer(
|
||||
const core::MixerState& mixer);
|
||||
[[nodiscard]] std::expected<void, Adau1701Error> selectSource(
|
||||
core::ActiveSource source);
|
||||
|
||||
/**
|
||||
* @brief applyEq — safeload all six PEQ bands.
|
||||
@@ -171,22 +171,6 @@ public:
|
||||
[[nodiscard]] std::expected<void, Adau1701Error> applyEq(
|
||||
const core::EqProfile& eq);
|
||||
|
||||
/**
|
||||
* @brief setInputVolume — safeload one input path gain.
|
||||
*
|
||||
* @dname setInputVolume
|
||||
* @param source Si4684 or ESP32 I2S path.
|
||||
* @param left Left channel gain.
|
||||
* @param right Right channel gain.
|
||||
* @return Ok on success, or Adau1701Error.
|
||||
* @pubstate writes parameter RAM via safeload.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] std::expected<void, Adau1701Error> setInputVolume(
|
||||
core::MixSource source, core::GainDb left, core::GainDb right);
|
||||
|
||||
/**
|
||||
* @brief setMasterVolume — safeload Multiple 1 master output gain.
|
||||
*
|
||||
@@ -234,6 +218,35 @@ public:
|
||||
[[nodiscard]] std::expected<void, Adau1701Error> setBeepEnabled(
|
||||
bool enabled);
|
||||
|
||||
/**
|
||||
* @brief setBassBoostLevel — scale Bass Boost1 toward its compiled
|
||||
* curve.
|
||||
*
|
||||
* @dname setBassBoostLevel
|
||||
* @param level 0 = flat bypass, 100 = full compiled response.
|
||||
* @return Ok on success, or Adau1701Error.
|
||||
* @pubstate writes parameter RAM via safeload.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
[[nodiscard]] std::expected<void, Adau1701Error> setBassBoostLevel(
|
||||
core::EnhanceLevel level);
|
||||
|
||||
/**
|
||||
* @brief setStereoSpreadLevel — scale SPhat1's stereo spread amount.
|
||||
*
|
||||
* @dname setStereoSpreadLevel
|
||||
* @param level 0 = no spread, 100 = full compiled spread.
|
||||
* @return Ok on success, or Adau1701Error.
|
||||
* @pubstate writes parameter RAM via safeload.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
[[nodiscard]] std::expected<void, Adau1701Error> setStereoSpreadLevel(
|
||||
core::EnhanceLevel level);
|
||||
|
||||
/**
|
||||
* @brief writeRawParam — safeload any named Parameter RAM cell.
|
||||
*
|
||||
|
||||
@@ -45,15 +45,12 @@ public:
|
||||
[[nodiscard]] std::expected<void, core::DspError> applyProfile(
|
||||
const core::AudioProfile& profile) override;
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> applyMixer(
|
||||
const core::MixerState& mixer) override;
|
||||
[[nodiscard]] std::expected<void, core::DspError> selectSource(
|
||||
core::ActiveSource source) override;
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> applyEq(
|
||||
const core::EqProfile& eq) override;
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> setInputVolume(
|
||||
core::MixSource source, core::GainDb left, core::GainDb right) override;
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> setMasterVolume(
|
||||
core::GainDb left, core::GainDb right) override;
|
||||
|
||||
@@ -64,6 +61,12 @@ public:
|
||||
[[nodiscard]] std::expected<void, core::DspError> setBeepEnabled(
|
||||
bool enabled) override;
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> setBassBoostLevel(
|
||||
core::EnhanceLevel level) override;
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> setStereoSpreadLevel(
|
||||
core::EnhanceLevel level) override;
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> writeRawParam(
|
||||
unsigned address, float value) override;
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
#include "DigiRadio_IC_1_PARAM.h"
|
||||
|
||||
#include "core/ActiveSource.hpp"
|
||||
#include "core/EqBandIndex.hpp"
|
||||
#include "core/MixSource.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -39,39 +39,30 @@ namespace adau1701 {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief paramAddrInputLeft — left volume address for an input source.
|
||||
* @brief paramSourceIndex — MX1 pair index written to DC1 for a source.
|
||||
*
|
||||
* @dname paramAddrInputLeft
|
||||
* @param source Si4684 or ESP32 path.
|
||||
* @return Parameter RAM address for the left channel gain cell.
|
||||
* @dname paramSourceIndex
|
||||
* @param source Which stereo pair MX1 should pass through.
|
||||
* @return 0 (Radio), 1 (Bluetooth), or 2 (Beep) -- confirmed live
|
||||
* 2026-08-25 against real hardware (see
|
||||
* docs/adau1701-sigmastudio-analysis.md).
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
[[nodiscard]] inline unsigned paramAddrInputLeft(core::MixSource source) noexcept
|
||||
[[nodiscard]] inline unsigned paramSourceIndex(
|
||||
core::ActiveSource source) noexcept
|
||||
{
|
||||
return source == core::MixSource::Si4684
|
||||
? static_cast<unsigned>(ADDR_SI4674)
|
||||
: static_cast<unsigned>(ADDR_ESP32);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief paramAddrInputRight — right volume address for an input source.
|
||||
*
|
||||
* @dname paramAddrInputRight
|
||||
* @param source Si4684 or ESP32 path.
|
||||
* @return Parameter RAM address for the right channel gain cell.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
*/
|
||||
[[nodiscard]] inline unsigned paramAddrInputRight(core::MixSource source) noexcept
|
||||
{
|
||||
return source == core::MixSource::Si4684
|
||||
? static_cast<unsigned>(ADDR_SI4674_1)
|
||||
: static_cast<unsigned>(ADDR_ESP32_1);
|
||||
switch (source) {
|
||||
case core::ActiveSource::Radio:
|
||||
return 0U;
|
||||
case core::ActiveSource::Bluetooth:
|
||||
return 1U;
|
||||
case core::ActiveSource::Beep:
|
||||
return 2U;
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
} // namespace adau1701
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Generated from Firmware/ADAU1701-Firmware/DigiRadio_IC_1_PARAM.h (the
|
||||
* SigmaStudio export). Regenerate by re-running the extraction over that
|
||||
* file if the SigmaStudio project's cell list changes; entries here must
|
||||
* stay in sync with the ADDR_* constants used by
|
||||
* SigmaStudio export, "DigiRadioFinale" revision, captured 2026-08-25).
|
||||
* Regenerate by re-running the extraction over that file if the SigmaStudio
|
||||
* project's cell list changes; entries here must stay in sync with the
|
||||
* ADDR_* constants used by
|
||||
* components/drivers/adau1701/src/Adau1701Driver.cpp's replayProgram().
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-18
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
@@ -39,83 +40,239 @@ struct Adau1701ParamEntry {
|
||||
* @pubstate Read-only; see file header for regeneration instructions.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-18
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
inline constexpr std::array<Adau1701ParamEntry, 74> kAdau1701ParamTable = {{
|
||||
inline constexpr std::array<Adau1701ParamEntry, 230> kAdau1701ParamTable = {{
|
||||
{"BEEP1_ENABLE", 0},
|
||||
{"BEEP1_KICK", 1},
|
||||
{"BEEP1_BEEP_FREQ", 2},
|
||||
{"SI4674", 3},
|
||||
{"SI4674_1", 4},
|
||||
{"ESP32", 5},
|
||||
{"ESP32_1", 6},
|
||||
{"SINGLE1", 7},
|
||||
{"DC1", 3},
|
||||
{"DCB1_POLE", 4},
|
||||
{"DCB2_POLE", 5},
|
||||
{"DCB4_POLE", 6},
|
||||
{"DCB3_POLE", 7},
|
||||
{"SSPLITTER1", 8},
|
||||
{"STMIXER1_ST0_VOLUME", 9},
|
||||
{"STMIXER1_ST1_VOLUME", 10},
|
||||
{"STMIXER1_ST2_VOLUME", 11},
|
||||
{"PARAMEQ1_ST0_B0", 12},
|
||||
{"PARAMEQ1_ST0_B1", 13},
|
||||
{"PARAMEQ1_ST0_B2", 14},
|
||||
{"PARAMEQ1_ST0_A0", 15},
|
||||
{"PARAMEQ1_ST0_A1", 16},
|
||||
{"PARAMEQ1_ST1_B0", 17},
|
||||
{"PARAMEQ1_ST1_B1", 18},
|
||||
{"PARAMEQ1_ST1_B2", 19},
|
||||
{"PARAMEQ1_ST1_A0", 20},
|
||||
{"PARAMEQ1_ST1_A1", 21},
|
||||
{"PARAMEQ1_ST2_B0", 22},
|
||||
{"PARAMEQ1_ST2_B1", 23},
|
||||
{"PARAMEQ1_ST2_B2", 24},
|
||||
{"PARAMEQ1_ST2_A0", 25},
|
||||
{"PARAMEQ1_ST2_A1", 26},
|
||||
{"PARAMEQ1_ST3_B0", 27},
|
||||
{"PARAMEQ1_ST3_B1", 28},
|
||||
{"PARAMEQ1_ST3_B2", 29},
|
||||
{"PARAMEQ1_ST3_A0", 30},
|
||||
{"PARAMEQ1_ST3_A1", 31},
|
||||
{"PARAMEQ1_ST4_B0", 32},
|
||||
{"PARAMEQ1_ST4_B1", 33},
|
||||
{"PARAMEQ1_ST4_B2", 34},
|
||||
{"PARAMEQ1_ST4_A0", 35},
|
||||
{"PARAMEQ1_ST4_A1", 36},
|
||||
{"PARAMEQ1_ST5_B0", 37},
|
||||
{"PARAMEQ1_ST5_B1", 38},
|
||||
{"PARAMEQ1_ST5_B2", 39},
|
||||
{"PARAMEQ1_ST5_A0", 40},
|
||||
{"PARAMEQ1_ST5_A1", 41},
|
||||
{"MULTIPLE1", 42},
|
||||
{"MULTIPLE1_1", 43},
|
||||
{"LIMITER1_S1", 44},
|
||||
{"LIMITER1_INT1", 45},
|
||||
{"LIMITER1_S2", 46},
|
||||
{"LIMITER1_INT2", 47},
|
||||
{"LIMITER1_S3", 48},
|
||||
{"LIMITER1_INT3", 49},
|
||||
{"LIMITER1_S4", 50},
|
||||
{"LIMITER1_INT4", 51},
|
||||
{"LIMITER1_C1", 52},
|
||||
{"LIMITER1_C2", 53},
|
||||
{"LIMITER1_C3", 54},
|
||||
{"LIMITER1_THRESHOLD", 55},
|
||||
{"LIMITER1_RMS", 56},
|
||||
{"LIMITER1_DECAY", 57},
|
||||
{"LIMITER1_DECAYCOMPLEMENT", 58},
|
||||
{"LIMITER2_S1", 59},
|
||||
{"LIMITER2_INT1", 60},
|
||||
{"LIMITER2_S2", 61},
|
||||
{"LIMITER2_INT2", 62},
|
||||
{"LIMITER2_S3", 63},
|
||||
{"LIMITER2_INT3", 64},
|
||||
{"LIMITER2_S4", 65},
|
||||
{"LIMITER2_INT4", 66},
|
||||
{"LIMITER2_C1", 67},
|
||||
{"LIMITER2_C2", 68},
|
||||
{"LIMITER2_C3", 69},
|
||||
{"LIMITER2_THRESHOLD", 70},
|
||||
{"LIMITER2_RMS", 71},
|
||||
{"LIMITER2_DECAY", 72},
|
||||
{"LIMITER2_DECAYCOMPLEMENT", 73},
|
||||
{"COMPRESSOR1_0", 9},
|
||||
{"COMPRESSOR1_1", 10},
|
||||
{"COMPRESSOR1_2", 11},
|
||||
{"COMPRESSOR1_3", 12},
|
||||
{"COMPRESSOR1_4", 13},
|
||||
{"COMPRESSOR1_5", 14},
|
||||
{"COMPRESSOR1_6", 15},
|
||||
{"COMPRESSOR1_7", 16},
|
||||
{"COMPRESSOR1_8", 17},
|
||||
{"COMPRESSOR1_9", 18},
|
||||
{"COMPRESSOR1_10", 19},
|
||||
{"COMPRESSOR1_11", 20},
|
||||
{"COMPRESSOR1_12", 21},
|
||||
{"COMPRESSOR1_13", 22},
|
||||
{"COMPRESSOR1_14", 23},
|
||||
{"COMPRESSOR1_15", 24},
|
||||
{"COMPRESSOR1_16", 25},
|
||||
{"COMPRESSOR1_17", 26},
|
||||
{"COMPRESSOR1_18", 27},
|
||||
{"COMPRESSOR1_19", 28},
|
||||
{"COMPRESSOR1_20", 29},
|
||||
{"COMPRESSOR1_21", 30},
|
||||
{"COMPRESSOR1_22", 31},
|
||||
{"COMPRESSOR1_23", 32},
|
||||
{"COMPRESSOR1_24", 33},
|
||||
{"COMPRESSOR1_25", 34},
|
||||
{"COMPRESSOR1_26", 35},
|
||||
{"COMPRESSOR1_27", 36},
|
||||
{"COMPRESSOR1_28", 37},
|
||||
{"COMPRESSOR1_29", 38},
|
||||
{"COMPRESSOR1_30", 39},
|
||||
{"COMPRESSOR1_31", 40},
|
||||
{"COMPRESSOR1_32", 41},
|
||||
{"COMPRESSOR1_33", 42},
|
||||
{"COMPRESSOR1_RMS", 43},
|
||||
{"COMPRESSOR1_POST_GAIN", 44},
|
||||
{"COMPRESSOR1_HOLD", 45},
|
||||
{"COMPRESSOR1_DECAY", 46},
|
||||
{"1XRTA3_TCONST", 47},
|
||||
{"1XRTA3_HOLD", 48},
|
||||
{"1XRTA3_DECAY", 49},
|
||||
{"1XRTA3", 2074},
|
||||
{"1XRTA4_TCONST", 50},
|
||||
{"1XRTA4_HOLD", 51},
|
||||
{"1XRTA4_DECAY", 52},
|
||||
{"1XRTA4", 2074},
|
||||
{"1XRTA1_TCONST", 53},
|
||||
{"1XRTA1_HOLD", 54},
|
||||
{"1XRTA1_DECAY", 55},
|
||||
{"1XRTA1", 2074},
|
||||
{"1XRTA2_TCONST", 56},
|
||||
{"1XRTA2_HOLD", 57},
|
||||
{"1XRTA2_DECAY", 58},
|
||||
{"1XRTA2", 2074},
|
||||
{"PARAMEQ1_ST0_B0", 59},
|
||||
{"PARAMEQ1_ST0_B1", 60},
|
||||
{"PARAMEQ1_ST0_B2", 61},
|
||||
{"PARAMEQ1_ST0_A0", 62},
|
||||
{"PARAMEQ1_ST0_A1", 63},
|
||||
{"PARAMEQ1_ST1_B0", 64},
|
||||
{"PARAMEQ1_ST1_B1", 65},
|
||||
{"PARAMEQ1_ST1_B2", 66},
|
||||
{"PARAMEQ1_ST1_A0", 67},
|
||||
{"PARAMEQ1_ST1_A1", 68},
|
||||
{"PARAMEQ1_ST2_B0", 69},
|
||||
{"PARAMEQ1_ST2_B1", 70},
|
||||
{"PARAMEQ1_ST2_B2", 71},
|
||||
{"PARAMEQ1_ST2_A0", 72},
|
||||
{"PARAMEQ1_ST2_A1", 73},
|
||||
{"PARAMEQ1_ST3_B0", 74},
|
||||
{"PARAMEQ1_ST3_B1", 75},
|
||||
{"PARAMEQ1_ST3_B2", 76},
|
||||
{"PARAMEQ1_ST3_A0", 77},
|
||||
{"PARAMEQ1_ST3_A1", 78},
|
||||
{"PARAMEQ1_ST4_B0", 79},
|
||||
{"PARAMEQ1_ST4_B1", 80},
|
||||
{"PARAMEQ1_ST4_B2", 81},
|
||||
{"PARAMEQ1_ST4_A0", 82},
|
||||
{"PARAMEQ1_ST4_A1", 83},
|
||||
{"PARAMEQ1_ST5_B0", 84},
|
||||
{"PARAMEQ1_ST5_B1", 85},
|
||||
{"PARAMEQ1_ST5_B2", 86},
|
||||
{"PARAMEQ1_ST5_A0", 87},
|
||||
{"PARAMEQ1_ST5_A1", 88},
|
||||
{"SPHAT1_B0LO0", 89},
|
||||
{"SPHAT1_B0LO1", 90},
|
||||
{"SPHAT1_B0LO2", 91},
|
||||
{"SPHAT1_B0LO3", 92},
|
||||
{"SPHAT1_B0LO4", 93},
|
||||
{"SPHAT1_B0LO5", 94},
|
||||
{"SPHAT1_B0LO6", 95},
|
||||
{"SPHAT1_B0LO7", 96},
|
||||
{"SPHAT1_B0LO8", 97},
|
||||
{"SPHAT1_B0LO9", 98},
|
||||
{"SPHAT1_GAINHI", 99},
|
||||
{"SPHAT1_GAINLO", 100},
|
||||
{"SPHAT1_GAININV", 101},
|
||||
{"SPHAT1_COMP_BASE0", 102},
|
||||
{"SPHAT1_COMP_BASE1", 103},
|
||||
{"SPHAT1_COMP_BASE2", 104},
|
||||
{"SPHAT1_COMP_BASE3", 105},
|
||||
{"SPHAT1_COMP_BASE4", 106},
|
||||
{"SPHAT1_COMP_BASE5", 107},
|
||||
{"SPHAT1_COMP_BASE6", 108},
|
||||
{"SPHAT1_COMP_BASE7", 109},
|
||||
{"SPHAT1_COMP_BASE8", 110},
|
||||
{"SPHAT1_COMP_BASE9", 111},
|
||||
{"SPHAT1_COMP_BASE10", 112},
|
||||
{"SPHAT1_COMP_BASE11", 113},
|
||||
{"SPHAT1_COMP_BASE12", 114},
|
||||
{"SPHAT1_COMP_BASE13", 115},
|
||||
{"SPHAT1_COMP_BASE14", 116},
|
||||
{"SPHAT1_COMP_BASE15", 117},
|
||||
{"SPHAT1_COMP_BASE16", 118},
|
||||
{"SPHAT1_COMP_BASE17", 119},
|
||||
{"SPHAT1_COMP_BASE18", 120},
|
||||
{"SPHAT1_COMP_BASE19", 121},
|
||||
{"SPHAT1_COMP_BASE20", 122},
|
||||
{"SPHAT1_COMP_BASE21", 123},
|
||||
{"SPHAT1_COMP_BASE22", 124},
|
||||
{"SPHAT1_COMP_BASE23", 125},
|
||||
{"SPHAT1_COMP_BASE24", 126},
|
||||
{"SPHAT1_COMP_BASE25", 127},
|
||||
{"SPHAT1_COMP_BASE26", 128},
|
||||
{"SPHAT1_COMP_BASE27", 129},
|
||||
{"SPHAT1_COMP_BASE28", 130},
|
||||
{"SPHAT1_COMP_BASE29", 131},
|
||||
{"SPHAT1_COMP_BASE30", 132},
|
||||
{"SPHAT1_COMP_BASE31", 133},
|
||||
{"SPHAT1_COMP_BASE32", 134},
|
||||
{"SPHAT1_COMP_BASE33", 135},
|
||||
{"SPHAT1_RMS", 136},
|
||||
{"SPHAT1_DECAY", 137},
|
||||
{"SPHAT1_HOLD", 138},
|
||||
{"SPHAT1_SPREAD1", 139},
|
||||
{"SPHAT1_SPREAD2", 140},
|
||||
{"GENFILTER1_ST0_B0", 141},
|
||||
{"GENFILTER1_ST0_B1", 142},
|
||||
{"GENFILTER1_ST0_B2", 143},
|
||||
{"GENFILTER1_ST0_A1", 144},
|
||||
{"GENFILTER1_ST0_A2", 145},
|
||||
{"MULTIPLE1", 146},
|
||||
{"MULTIPLE1_1", 147},
|
||||
{"BASSBOOST1_BASSFREQUENCY", 148},
|
||||
{"BASSBOOST1_B0", 149},
|
||||
{"BASSBOOST1_B1", 150},
|
||||
{"BASSBOOST1_B2", 151},
|
||||
{"BASSBOOST1_A1", 152},
|
||||
{"BASSBOOST1_A2", 153},
|
||||
{"BASSBOOST1_TABLE0", 154},
|
||||
{"BASSBOOST1_TABLE1", 155},
|
||||
{"BASSBOOST1_TABLE2", 156},
|
||||
{"BASSBOOST1_TABLE3", 157},
|
||||
{"BASSBOOST1_TABLE4", 158},
|
||||
{"BASSBOOST1_TABLE5", 159},
|
||||
{"BASSBOOST1_TABLE6", 160},
|
||||
{"BASSBOOST1_TABLE7", 161},
|
||||
{"BASSBOOST1_TABLE8", 162},
|
||||
{"BASSBOOST1_TABLE9", 163},
|
||||
{"BASSBOOST1_TABLE10", 164},
|
||||
{"BASSBOOST1_TABLE11", 165},
|
||||
{"BASSBOOST1_TABLE12", 166},
|
||||
{"BASSBOOST1_TABLE13", 167},
|
||||
{"BASSBOOST1_TABLE14", 168},
|
||||
{"BASSBOOST1_TABLE15", 169},
|
||||
{"BASSBOOST1_TABLE16", 170},
|
||||
{"BASSBOOST1_TABLE17", 171},
|
||||
{"BASSBOOST1_TABLE18", 172},
|
||||
{"BASSBOOST1_TABLE19", 173},
|
||||
{"BASSBOOST1_TABLE20", 174},
|
||||
{"BASSBOOST1_TABLE21", 175},
|
||||
{"BASSBOOST1_TABLE22", 176},
|
||||
{"BASSBOOST1_TABLE23", 177},
|
||||
{"BASSBOOST1_TABLE24", 178},
|
||||
{"BASSBOOST1_TABLE25", 179},
|
||||
{"BASSBOOST1_TABLE26", 180},
|
||||
{"BASSBOOST1_TABLE27", 181},
|
||||
{"BASSBOOST1_TABLE28", 182},
|
||||
{"BASSBOOST1_TABLE29", 183},
|
||||
{"BASSBOOST1_TABLE30", 184},
|
||||
{"BASSBOOST1_TABLE31", 185},
|
||||
{"BASSBOOST1_TABLE32", 186},
|
||||
{"BASSBOOST1_TIMECONSTANT", 187},
|
||||
{"1XRTA2_2_TCONST", 188},
|
||||
{"1XRTA2_2_HOLD", 189},
|
||||
{"1XRTA2_2_DECAY", 190},
|
||||
{"1XRTA2_2", 2074},
|
||||
{"1XRTA1_2_TCONST", 191},
|
||||
{"1XRTA1_2_HOLD", 192},
|
||||
{"1XRTA1_2_DECAY", 193},
|
||||
{"1XRTA1_2", 2074},
|
||||
{"LIMITER2_S1", 194},
|
||||
{"LIMITER2_INT1", 195},
|
||||
{"LIMITER2_S2", 196},
|
||||
{"LIMITER2_INT2", 197},
|
||||
{"LIMITER2_S3", 198},
|
||||
{"LIMITER2_INT3", 199},
|
||||
{"LIMITER2_S4", 200},
|
||||
{"LIMITER2_INT4", 201},
|
||||
{"LIMITER2_C1", 202},
|
||||
{"LIMITER2_C2", 203},
|
||||
{"LIMITER2_C3", 204},
|
||||
{"LIMITER2_THRESHOLD", 205},
|
||||
{"LIMITER2_RMS", 206},
|
||||
{"LIMITER2_DECAY", 207},
|
||||
{"LIMITER2_DECAYCOMPLEMENT", 208},
|
||||
{"LIMITER1_S1", 209},
|
||||
{"LIMITER1_INT1", 210},
|
||||
{"LIMITER1_S2", 211},
|
||||
{"LIMITER1_INT2", 212},
|
||||
{"LIMITER1_S3", 213},
|
||||
{"LIMITER1_INT3", 214},
|
||||
{"LIMITER1_S4", 215},
|
||||
{"LIMITER1_INT4", 216},
|
||||
{"LIMITER1_C1", 217},
|
||||
{"LIMITER1_C2", 218},
|
||||
{"LIMITER1_C3", 219},
|
||||
{"LIMITER1_THRESHOLD", 220},
|
||||
{"LIMITER1_RMS", 221},
|
||||
{"LIMITER1_DECAY", 222},
|
||||
{"LIMITER1_DECAYCOMPLEMENT", 223},
|
||||
}};
|
||||
|
||||
/**
|
||||
@@ -127,7 +284,7 @@ inline constexpr std::array<Adau1701ParamEntry, 74> kAdau1701ParamTable = {{
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-18
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
[[nodiscard]] inline std::optional<unsigned> findAdau1701ParamAddress(
|
||||
std::string_view name) noexcept
|
||||
|
||||
@@ -358,18 +358,22 @@ namespace adau1701
|
||||
return safeloadFixpoint(paramAddr, core::gainDbToLinearFixpoint(gain));
|
||||
}
|
||||
|
||||
std::expected<void, Adau1701Error> Adau1701Driver::setInputVolume(
|
||||
core::MixSource source, core::GainDb left, core::GainDb right)
|
||||
std::expected<void, Adau1701Error> Adau1701Driver::selectSource(
|
||||
core::ActiveSource source)
|
||||
{
|
||||
if (auto ready = ensureBooted(); !ready)
|
||||
{
|
||||
return ready;
|
||||
}
|
||||
if (auto result = safeloadGain(paramAddrInputLeft(source), left); !result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return safeloadGain(paramAddrInputRight(source), right);
|
||||
// MX1 (DCinputmux_stereo) reads DC1 as a raw 32-bit integer pair
|
||||
// index (0/1/2), NOT the 5.23 fixpoint conversion its compiled
|
||||
// TYPE_DC1/VALUE_DC1 macros would suggest -- confirmed live
|
||||
// 2026-08-25 (a 5.23-encoded write left the source unchanged; the
|
||||
// raw integer switched Radio/Bluetooth/Beep correctly). 0=Radio,
|
||||
// 1=Bluetooth (ESP32), 2=Beep, per paramSourceIndex().
|
||||
const auto index =
|
||||
static_cast<std::int32_t>(paramSourceIndex(source));
|
||||
return safeloadFixpoint(static_cast<unsigned>(ADDR_DC1), index);
|
||||
}
|
||||
|
||||
std::expected<void, Adau1701Error> Adau1701Driver::setMasterVolume(
|
||||
@@ -387,45 +391,6 @@ namespace adau1701
|
||||
return safeloadGain(static_cast<unsigned>(ADDR_MULTIPLE1_1), right);
|
||||
}
|
||||
|
||||
std::expected<void, Adau1701Error> Adau1701Driver::applyMixer(
|
||||
const core::MixerState &mixer)
|
||||
{
|
||||
if (auto ready = ensureBooted(); !ready)
|
||||
{
|
||||
return ready;
|
||||
}
|
||||
if (auto result = setInputVolume(core::MixSource::Si4684, mixer.si4684Left,
|
||||
mixer.si4684Right);
|
||||
!result)
|
||||
{
|
||||
ESP_LOGW(kTag, "applyMixer: si4684 input safeload failed");
|
||||
return result;
|
||||
}
|
||||
if (auto result = setInputVolume(core::MixSource::Esp32, mixer.esp32Left,
|
||||
mixer.esp32Right);
|
||||
!result)
|
||||
{
|
||||
ESP_LOGW(kTag, "applyMixer: esp32 input safeload failed");
|
||||
return result;
|
||||
}
|
||||
if (auto result =
|
||||
safeloadGain(static_cast<unsigned>(ADDR_STMIXER1_ST0_VOLUME),
|
||||
mixer.mixLeft);
|
||||
!result)
|
||||
{
|
||||
ESP_LOGW(kTag, "applyMixer: st0 safeload failed");
|
||||
return result;
|
||||
}
|
||||
if (auto result = safeloadGain(
|
||||
static_cast<unsigned>(ADDR_STMIXER1_ST1_VOLUME), mixer.mixRight);
|
||||
!result)
|
||||
{
|
||||
ESP_LOGW(kTag, "applyMixer: st1 safeload failed");
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, Adau1701Error> Adau1701Driver::setEqBand(
|
||||
core::EqBandIndex band, core::GainDb gain, core::FrequencyHz center, float q)
|
||||
{
|
||||
@@ -487,6 +452,103 @@ namespace adau1701
|
||||
return safeloadFixpoint(static_cast<unsigned>(ADDR_BEEP1_KICK), value);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// Compiled SigmaStudio defaults for Bass Boost1's crossover filter
|
||||
// and 33-point compander curve (DigiRadioFinale_IC_1_PARAM.h,
|
||||
// 2026-08-25 export). setBassBoostLevel() interpolates from these
|
||||
// toward an identity/unity bypass as level goes from 100 to 0 --
|
||||
// ADI's Dynamic Bass Boost algorithm internals aren't documented,
|
||||
// so this is a linear blend of the known-good compiled curve, not
|
||||
// a re-derivation of the algorithm's own math.
|
||||
constexpr float kBassBoostB0 = 4.24433093514364e-05F;
|
||||
constexpr float kBassBoostB1 = 8.48866187028728e-05F;
|
||||
constexpr float kBassBoostB2 = 4.24433093514364e-05F;
|
||||
constexpr float kBassBoostA1 = 1.98148576456209F;
|
||||
constexpr float kBassBoostA2 = -0.981655537799498F;
|
||||
constexpr float kBassBoostTable[33] = {
|
||||
0.319153785510076F, 0.319153785510076F, 0.319153785510076F,
|
||||
0.319153785510076F, 0.319153785510076F, 0.319153785510076F,
|
||||
0.319153785510076F, 0.319153785510076F, 0.319153785510076F,
|
||||
0.319153785510076F, 0.319153785510076F, 0.319153785510076F,
|
||||
0.319153785510076F, 0.319153785510076F, 0.319153785510076F,
|
||||
0.319153785510076F, 0.319153785510076F, 0.319153785510076F,
|
||||
0.319153785510076F, 0.319153785510076F, 0.319153785510076F,
|
||||
0.319153785510076F, 0.319153785510076F, 0.319153785510076F,
|
||||
0.322849412171264F, 0.334195040026114F, 0.345939377826122F,
|
||||
0.358096437102636F, 0.370680721782576F, 0.383707245492279F,
|
||||
0.39719154946944F, 0.411149721104522F, 0.425598413133743F,
|
||||
};
|
||||
|
||||
constexpr float kSpreadSpread1 = 0.0629093159447337F;
|
||||
constexpr float kSpreadSpread2 = 0.0193038143874401F;
|
||||
|
||||
[[nodiscard]] float towardIdentity(float compiled, float identity,
|
||||
float level) noexcept
|
||||
{
|
||||
return identity + (compiled - identity) * level;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::expected<void, Adau1701Error> Adau1701Driver::setBassBoostLevel(
|
||||
core::EnhanceLevel level)
|
||||
{
|
||||
if (auto ready = ensureBooted(); !ready)
|
||||
{
|
||||
return ready;
|
||||
}
|
||||
const float t = level.fraction();
|
||||
unsigned addrs[5U];
|
||||
int values[5U];
|
||||
addrs[0] = static_cast<unsigned>(ADDR_BASSBOOST1_B0);
|
||||
addrs[1] = static_cast<unsigned>(ADDR_BASSBOOST1_B1);
|
||||
addrs[2] = static_cast<unsigned>(ADDR_BASSBOOST1_B2);
|
||||
addrs[3] = static_cast<unsigned>(ADDR_BASSBOOST1_A1);
|
||||
addrs[4] = static_cast<unsigned>(ADDR_BASSBOOST1_A2);
|
||||
values[0] = core::floatToFixpoint823(towardIdentity(kBassBoostB0, 1.0F, t));
|
||||
values[1] = core::floatToFixpoint823(towardIdentity(kBassBoostB1, 0.0F, t));
|
||||
values[2] = core::floatToFixpoint823(towardIdentity(kBassBoostB2, 0.0F, t));
|
||||
values[3] = core::floatToFixpoint823(towardIdentity(kBassBoostA1, 0.0F, t));
|
||||
values[4] = core::floatToFixpoint823(towardIdentity(kBassBoostA2, 0.0F, t));
|
||||
if (sigma_safeload_block(5U, addrs, values) != 0)
|
||||
{
|
||||
return std::unexpected(Adau1701Error::SafeloadFailed);
|
||||
}
|
||||
|
||||
for (unsigned i = 0U; i < 33U; ++i)
|
||||
{
|
||||
const unsigned addr =
|
||||
static_cast<unsigned>(ADDR_BASSBOOST1_TABLE0) + i;
|
||||
const std::int32_t value = core::floatToFixpoint823(
|
||||
towardIdentity(kBassBoostTable[i], 1.0F, t));
|
||||
if (auto result = safeloadFixpoint(addr, value); !result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, Adau1701Error> Adau1701Driver::setStereoSpreadLevel(
|
||||
core::EnhanceLevel level)
|
||||
{
|
||||
if (auto ready = ensureBooted(); !ready)
|
||||
{
|
||||
return ready;
|
||||
}
|
||||
const float t = level.fraction();
|
||||
if (auto result = safeloadFixpoint(
|
||||
static_cast<unsigned>(ADDR_SPHAT1_SPREAD1),
|
||||
core::floatToFixpoint823(kSpreadSpread1 * t));
|
||||
!result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return safeloadFixpoint(
|
||||
static_cast<unsigned>(ADDR_SPHAT1_SPREAD2),
|
||||
core::floatToFixpoint823(kSpreadSpread2 * t));
|
||||
}
|
||||
|
||||
std::expected<void, Adau1701Error> Adau1701Driver::writeRawParam(
|
||||
unsigned address, float value)
|
||||
{
|
||||
@@ -535,7 +597,7 @@ namespace adau1701
|
||||
{
|
||||
return ready;
|
||||
}
|
||||
if (auto result = applyMixer(profile.mixer); !result)
|
||||
if (auto result = selectSource(profile.activeSource); !result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@@ -543,7 +605,17 @@ namespace adau1701
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return setMasterVolume(profile.masterLeft, profile.masterRight);
|
||||
if (auto result = setMasterVolume(profile.masterLeft, profile.masterRight);
|
||||
!result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if (auto result = setBassBoostLevel(profile.enhancements.bass);
|
||||
!result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return setStereoSpreadLevel(profile.enhancements.stereo);
|
||||
}
|
||||
|
||||
} // namespace adau1701
|
||||
|
||||
@@ -43,10 +43,10 @@ std::expected<void, core::DspError> Adau1701Dsp::applyProfile(
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, core::DspError> Adau1701Dsp::applyMixer(
|
||||
const core::MixerState& mixer)
|
||||
std::expected<void, core::DspError> Adau1701Dsp::selectSource(
|
||||
core::ActiveSource source)
|
||||
{
|
||||
if (auto result = driver_.applyMixer(mixer); !result) {
|
||||
if (auto result = driver_.selectSource(source); !result) {
|
||||
return std::unexpected(mapError(result.error()));
|
||||
}
|
||||
return {};
|
||||
@@ -61,15 +61,6 @@ std::expected<void, core::DspError> Adau1701Dsp::applyEq(
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, core::DspError> Adau1701Dsp::setInputVolume(
|
||||
core::MixSource source, core::GainDb left, core::GainDb right)
|
||||
{
|
||||
if (auto result = driver_.setInputVolume(source, left, right); !result) {
|
||||
return std::unexpected(mapError(result.error()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, core::DspError> Adau1701Dsp::setMasterVolume(
|
||||
core::GainDb left, core::GainDb right)
|
||||
{
|
||||
@@ -97,6 +88,24 @@ std::expected<void, core::DspError> Adau1701Dsp::setBeepEnabled(bool enabled)
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, core::DspError> Adau1701Dsp::setBassBoostLevel(
|
||||
core::EnhanceLevel level)
|
||||
{
|
||||
if (auto result = driver_.setBassBoostLevel(level); !result) {
|
||||
return std::unexpected(mapError(result.error()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, core::DspError> Adau1701Dsp::setStereoSpreadLevel(
|
||||
core::EnhanceLevel level)
|
||||
{
|
||||
if (auto result = driver_.setStereoSpreadLevel(level); !result) {
|
||||
return std::unexpected(mapError(result.error()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, core::DspError> Adau1701Dsp::writeRawParam(
|
||||
unsigned address, float value)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user