Add live VU-meter readback via ADAU1701 Data Capture Register
Implements GET /api/audio/levels, reading all six 1×RTA level-detector cells on demand (no background polling, no caching -- only runs when called, per explicit request this session). The read mechanism is the ADAU1701's documented Data Capture Register (address 2074/0x081A, datasheet Rev.0 pp.30,36: write a (program-step, register-select) pair to configure what the register mirrors, then read back a 3-byte 5.19 twos-complement value). The per-meter program- step indices are taken verbatim from SigmaStudio's own compiler output (Firmware/ADAU1701-Firmware/IC 1_DigiRadioFinale/net_list_out2/ trap.dat), not invented -- the datasheet explicitly says these indices must come from that compiler-generated file. Confirmed live: all six points return distinct, plausible dBFS values, and the output meters track a master-volume change. Also folds two pieces of live user feedback into the app brief: the volume slider should reach +12 dB (the firmware's real ceiling), not stop at 0 dB, and the new levels endpoint is what a "VU meter" UI section should poll instead of faking one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file AudioLevels.hpp
|
||||
* @brief Snapshot of the six ADAU1701 Data Capture level meters.
|
||||
*
|
||||
* 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 AudioLevels — one on-demand read of all six 1×RTA meters.
|
||||
*
|
||||
* @dname AudioLevels
|
||||
* @return n/a (type)
|
||||
* @pubstate Read-only snapshot; not part of AudioProfile, never persisted.
|
||||
*
|
||||
* Each field is in dBFS, read live from the ADAU1701 Data Capture Register
|
||||
* (address 2074, MAC_out tap) at the program-step index SigmaStudio's
|
||||
* compiler assigned to that 1×RTA cell (see
|
||||
* Firmware/ADAU1701-Firmware/IC 1_DigiRadioFinale/net_list_out2/trap.dat).
|
||||
* Captured only when requested by an API call -- no background polling.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
struct AudioLevels {
|
||||
float radioInLeftDb; ///< 1×RTA1, post-compressor Si4684 L.
|
||||
float radioInRightDb; ///< 1×RTA2, post-compressor Si4684 R.
|
||||
float bluetoothInLeftDb; ///< 1×RTA3, ESP32 L.
|
||||
float bluetoothInRightDb; ///< 1×RTA4, ESP32 R.
|
||||
float outputLeftDb; ///< 1×RTA1_2, post Bass Boost1, L.
|
||||
float outputRightDb; ///< 1×RTA2_2, post Bass Boost1, R.
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/AudioLevels.hpp"
|
||||
#include "core/AudioProfile.hpp"
|
||||
#include "core/ParseError.hpp"
|
||||
|
||||
@@ -104,4 +105,17 @@ namespace core {
|
||||
[[nodiscard]] std::expected<bool, ParseError> parseBeepEnabledJson(
|
||||
std::string_view json);
|
||||
|
||||
/**
|
||||
* @brief serializeAudioLevelsJson — serialise a level-meter snapshot.
|
||||
*
|
||||
* @dname serializeAudioLevelsJson
|
||||
* @param levels On-demand read from IDsp::readLevels().
|
||||
* @return JSON object string for GET /api/audio/levels.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
[[nodiscard]] std::string serializeAudioLevelsJson(const AudioLevels& levels);
|
||||
|
||||
} // namespace core
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/ActiveSource.hpp"
|
||||
#include "core/AudioLevels.hpp"
|
||||
#include "core/AudioProfile.hpp"
|
||||
#include "core/DspError.hpp"
|
||||
#include "core/EnhanceLevel.hpp"
|
||||
@@ -183,6 +184,20 @@ public:
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<void, DspError> writeRawParam(
|
||||
unsigned address, float value) = 0;
|
||||
|
||||
/**
|
||||
* @brief readLevels — on-demand read of all six 1×RTA level meters.
|
||||
*
|
||||
* @dname readLevels
|
||||
* @return AudioLevels snapshot, or DspError.
|
||||
* @pubstate Reads live from the ADAU1701; not part of AudioProfile,
|
||||
* never persisted. Only runs when called, no background
|
||||
* polling.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-25
|
||||
*/
|
||||
[[nodiscard]] virtual std::expected<AudioLevels, DspError> readLevels() = 0;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
|
||||
@@ -338,4 +338,16 @@ std::expected<bool, ParseError> parseBeepEnabledJson(std::string_view json)
|
||||
return enabled;
|
||||
}
|
||||
|
||||
std::string serializeAudioLevelsJson(const AudioLevels& levels)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "{\"radio_in_left_db\":" << levels.radioInLeftDb
|
||||
<< ",\"radio_in_right_db\":" << levels.radioInRightDb
|
||||
<< ",\"bluetooth_in_left_db\":" << levels.bluetoothInLeftDb
|
||||
<< ",\"bluetooth_in_right_db\":" << levels.bluetoothInRightDb
|
||||
<< ",\"output_left_db\":" << levels.outputLeftDb
|
||||
<< ",\"output_right_db\":" << levels.outputRightDb << "}";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
|
||||
@@ -82,6 +82,12 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<core::AudioLevels, core::DspError>
|
||||
readLevels() override
|
||||
{
|
||||
return core::AudioLevels{};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<void, core::DspError> writeRawParam(
|
||||
unsigned, float) override
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user