Add stereo and bass enhance APIs via PEQ overlay.

Expose POST /api/audio/stereo-enhance and bass-enhance with 0–100 levels,
persist enhancements in AudioProfile, and document the virtual EQ mapping in
the manual and SigmaStudio chapter.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 17:00:12 +02:00
co-authored by Cursor
parent 6c350f5c48
commit fa532e068f
22 changed files with 826 additions and 19 deletions
@@ -0,0 +1,47 @@
/**
* @file AudioEnhancements.hpp
* @brief Stereo width and bass boost enhancement levels.
*
* 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/EnhanceLevel.hpp"
namespace core {
/**
* @brief AudioEnhancements — psychoacoustic EQ overlays (0100 each).
*
* @dname AudioEnhancements
* @return n/a (type)
* @pubstate Mapped to PEQ bands at runtime (Chapter~\ref{ch:sigmastudio}).
* No dedicated SigmaStudio blocks; see \texttt{applyEnhancementsToEq}.
*
* @author Michele Bigi
* @date 2026-07-06
*/
struct AudioEnhancements {
EnhanceLevel stereo; ///< Stereo depth / presence (PEQ bands 35).
EnhanceLevel bass; ///< Bass emphasis (PEQ bands 12).
/**
* @brief factoryDefault — both enhancements off.
*
* @dname factoryDefault
* @return AudioEnhancements at level 0.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] static AudioEnhancements factoryDefault() noexcept;
};
} // namespace core
@@ -12,6 +12,7 @@
*/
#pragma once
#include "core/AudioEnhancements.hpp"
#include "core/EqProfile.hpp"
#include "core/GainDb.hpp"
#include "core/MixerState.hpp"
@@ -32,8 +33,9 @@ namespace core {
struct AudioProfile {
MixerState mixer; ///< Input and stereo-mixer gains.
EqProfile eq; ///< Six-band parametric EQ.
GainDb masterLeft; ///< Multiple 1 master volume, left.
GainDb masterRight; ///< Multiple 1 master volume, right.
GainDb masterLeft; ///< Multiple 1 master volume, left.
GainDb masterRight; ///< Multiple 1 master volume, right.
AudioEnhancements enhancements; ///< Stereo depth and bass boost (PEQ overlay).
/**
* @brief factoryDefault — factory-flat audio path (0 dB everywhere).
@@ -15,6 +15,8 @@
#include "core/AudioProfile.hpp"
#include "core/ParseError.hpp"
#include "core/EnhanceLevel.hpp"
#include <expected>
#include <string>
#include <string_view>
@@ -74,4 +76,18 @@ namespace core {
*/
[[nodiscard]] std::string serializeAudioErrorJson(std::string_view reason);
/**
* @brief parseEnhanceLevelJson — parse POST body \texttt{\{"level":0..100\}}.
*
* @dname parseEnhanceLevelJson
* @param json Untrusted request body.
* @return EnhanceLevel on success, or ParseError.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<EnhanceLevel, ParseError> parseEnhanceLevelJson(
std::string_view json);
} // namespace core
@@ -0,0 +1,93 @@
/**
* @file EnhanceLevel.hpp
* @brief Strong type for 0100 enhancement intensity (stereo / bass).
*
* 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/ParseError.hpp"
#include <cstdint>
#include <expected>
namespace core {
/**
* @brief EnhanceLevel — validated enhancement intensity (0 = off, 100 = max).
*
* @dname EnhanceLevel
* @return n/a (type)
* @pubstate Owns level_ in 0..100 after construction.
*
* @author Michele Bigi
* @date 2026-07-06
*/
class EnhanceLevel {
public:
/** Maximum enhancement intensity. */
static constexpr std::uint8_t kMax = 100U;
/**
* @brief tryFromLevel — validate an enhancement level at the boundary.
*
* @dname tryFromLevel
* @param level Untrusted 0..100 value from JSON.
* @return EnhanceLevel on success, or ParseError::MissingField.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] static std::expected<EnhanceLevel, ParseError> tryFromLevel(
std::uint32_t level) noexcept;
/**
* @brief zero — enhancement disabled.
*
* @dname zero
* @return EnhanceLevel at 0.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] static EnhanceLevel zero() noexcept;
/**
* @brief value — read the stored level.
*
* @dname value
* @return Level 0..100.
* @pubstate reads level_.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::uint8_t value() const noexcept;
/**
* @brief fraction — normalised intensity in 0.0..1.0.
*
* @dname fraction
* @return level / 100 as float.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] float fraction() const noexcept;
private:
explicit EnhanceLevel(std::uint8_t level) noexcept;
std::uint8_t level_;
};
} // namespace core
@@ -0,0 +1,39 @@
/**
* @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 35): slight 1\,kHz cut plus 3/8\,kHz lift for depth.
* Bass (bands 12): 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