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:
@@ -29,6 +29,9 @@ add_library(digiradio_core STATIC
|
||||
"${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"
|
||||
)
|
||||
@@ -63,3 +66,7 @@ add_test(NAME biquad_design_test COMMAND biquad_design_test)
|
||||
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)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "core/AudioProfile.hpp"
|
||||
#include "core/AudioProfileJson.hpp"
|
||||
#include "core/EnhanceLevel.hpp"
|
||||
#include "core/EqBandIndex.hpp"
|
||||
#include "core/FrequencyHz.hpp"
|
||||
#include "core/GainDb.hpp"
|
||||
@@ -52,6 +53,22 @@ namespace {
|
||||
std::cerr << "round-trip mismatch\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (parsed->enhancements.stereo.value() != 0U
|
||||
|| parsed->enhancements.bass.value() != 0U) {
|
||||
std::cerr << "default enhancements mismatch\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
profile.enhancements.stereo = *core::EnhanceLevel::tryFromLevel(40U);
|
||||
profile.enhancements.bass = *core::EnhanceLevel::tryFromLevel(75U);
|
||||
const std::string json2 = core::serializeAudioProfileJson(profile);
|
||||
const auto parsed2 = core::parseAudioProfileJson(json2);
|
||||
if (!parsed2 || parsed2->enhancements.stereo.value() != 40U
|
||||
|| parsed2->enhancements.bass.value() != 75U) {
|
||||
std::cerr << "enhancements round-trip mismatch\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
Reference in New Issue
Block a user