Skip to content

Commit 4ffd69f

Browse files
Fix meter NaN spike + dB scale + add UI decay
LevelMeasurePipe's rolling +/- accumulation drifts to a tiny negative value during silence due to floating-point cancellation. sqrt(mean) then returned NaN, propagating through log10 into the UI which rendered the NaN as a max-pegged meter spike (very visible when pulling a send fader toward -inf). - Clamp mean to non-negative before sqrt. - Floor rms at 1e-6 so log10(0) doesn't produce -inf either. - Switch 10*log10(rms) -> 20*log10(rms): the previous formula was the intensity scale, not the amplitude scale. RMS peaks now read at the correct dBFS. UI: ShowManager runs a 50 ms QTimer that ramps each channel's displayed level toward -60 dBFS when no fresh meter packet has arrived for that channel in the last 100 ms. Without this the meter sticks at its last value forever when a stream stops (the engine simply stops sending updates rather than sending zeros). Decay rate: 1.2 dB/tick = ~24 dB/s; collapses from 0 dB to floor in ~2.5 s. Fast enough to read as "input went quiet," slow enough not to flicker on a single dropped packet.
1 parent 335b6e4 commit 4ffd69f

3 files changed

Lines changed: 76 additions & 11 deletions

File tree

‎coreui/core/ShowManager.cpp‎

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ bool is_static_range(uint16_t uid) {
9797

9898
ShowManager::ShowManager() : QObject(nullptr) {
9999
m_netconfig = NetworkConfig{};
100+
101+
m_last_meter_ms.fill(0);
102+
m_last_meter_value_db.fill(METER_FLOOR_DB);
103+
m_meter_clock.start();
104+
105+
m_meter_decay_timer.setInterval(50);
106+
connect(&m_meter_decay_timer, &QTimer::timeout, this, &ShowManager::tick_meter_decay);
107+
m_meter_decay_timer.start();
100108
}
101109

102110
ShowManager::~ShowManager() {
@@ -191,18 +199,43 @@ bool ShowManager::init_console(SignalWindow* sw) {
191199
}
192200

193201
void ShowManager::update_pipe_meter_level(const ControlPacket &data) {
194-
for (auto& pipe : m_show_content) {
195-
if (pipe->get_channel() == data.packet_data.channel) {
196-
float db_level = -60.0f;
197-
memcpy(&db_level, data.packet_data.data, sizeof(float));
202+
float db_level = METER_FLOOR_DB;
203+
memcpy(&db_level, data.packet_data.data, sizeof(float));
198204

199-
pipe->set_current_level(db_level);
205+
const uint8_t ch = data.packet_data.channel;
206+
if (ch < METER_CHANNELS) {
207+
m_last_meter_ms[ch] = m_meter_clock.elapsed();
208+
m_last_meter_value_db[ch] = db_level;
209+
}
200210

211+
for (auto& pipe : m_show_content) {
212+
if (pipe->get_channel() == ch) {
213+
pipe->set_current_level(db_level);
201214
break;
202215
}
203216
}
204217
}
205218

219+
void ShowManager::tick_meter_decay() {
220+
const qint64 now = m_meter_clock.elapsed();
221+
for (auto& pipe : m_show_content) {
222+
const uint8_t ch = pipe->get_channel();
223+
if (ch >= METER_CHANNELS) continue;
224+
225+
const qint64 last = m_last_meter_ms[ch];
226+
// If the channel has never received a meter packet, leave it
227+
// at floor — there's nothing to decay from.
228+
if (last == 0) continue;
229+
if (now - last < METER_STALE_MS) continue;
230+
231+
float& v = m_last_meter_value_db[ch];
232+
if (v <= METER_FLOOR_DB) continue;
233+
v -= METER_DECAY_PER_TICK_DB;
234+
if (v < METER_FLOOR_DB) v = METER_FLOOR_DB;
235+
pipe->set_current_level(v);
236+
}
237+
}
238+
206239
void ShowManager::send_to_elem(const ControlPacket &data) {
207240
for (auto& pipe : m_show_content) {
208241
if (pipe->get_channel() == data.packet_data.channel) {

‎coreui/core/ShowManager.h‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@
3131
#include <qjsonarray.h>
3232
#include <qjsonobject.h>
3333
#include <qqueue.h>
34+
#include <QTimer>
35+
#include <QElapsedTimer>
3436

3537
#include <unordered_map>
38+
#include <array>
3639

3740
class ShowManager : public QObject {
3841

@@ -83,6 +86,19 @@ class ShowManager : public QObject {
8386
// started consulting an explicit route table.
8487
void auto_route_bus_if_needed(PipeDesc* desc, uint8_t channel, uint16_t host);
8588

89+
void tick_meter_decay();
90+
91+
static constexpr int METER_CHANNELS = 64;
92+
static constexpr float METER_FLOOR_DB = -60.0f;
93+
// After this gap with no fresh meter packet, decay the visual level
94+
// toward METER_FLOOR_DB. ~100 ms is fast enough that the user sees
95+
// "input went quiet" instantly without flickering on a single
96+
// dropped packet (meters come in at ~125 Hz from the engine).
97+
static constexpr qint64 METER_STALE_MS = 100;
98+
// Decay step per tick. 50 ms tick * 1.2 dB/tick = ~24 dB/s; fully
99+
// collapses from 0 dB to floor in ~2.5 s.
100+
static constexpr float METER_DECAY_PER_TICK_DB = 1.2f;
101+
86102
QList<PipeVisualizer*> m_show_content;
87103

88104
std::shared_ptr<NetworkMapper> m_nmapper;
@@ -91,6 +107,16 @@ class ShowManager : public QObject {
91107

92108
DSPManager* m_dsp_manager = nullptr;
93109
std::shared_ptr<PluginLoader> m_plugin_loader;
110+
111+
// Per-channel meter decay state. m_last_meter_value_db keeps the
112+
// most-recent value (also used as the decay origin); m_last_meter_ms
113+
// is monotonic ms timestamp of the last packet for that channel; the
114+
// QTimer fires every METER_TICK_MS to push the displayed level down
115+
// when no packet has arrived recently.
116+
QTimer m_meter_decay_timer;
117+
QElapsedTimer m_meter_clock;
118+
std::array<qint64, METER_CHANNELS> m_last_meter_ms{};
119+
std::array<float, METER_CHANNELS> m_last_meter_value_db{};
94120
};
95121

96122

‎engine/piping/feedback/LevelMeasurePipe.cpp‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,20 @@ float LevelMeasurePipe::process_sample(float sample) {
4040

4141
m_value_counter++;
4242
if (m_value_counter > 300) {
43-
float temp_sum = 0.0f;
44-
for (auto& val : m_rms_buffer) {
45-
temp_sum += val;
46-
}
47-
43+
// Rolling +/- accumulation drifts to a tiny negative when the
44+
// window is truly silent. Clamp before sqrt so we never feed
45+
// log10 a NaN, which the UI would render as a max-pegged
46+
// meter spike.
4847
float mean = m_sum / 28000.0f;
48+
if (mean < 0.0f) mean = 0.0f;
4949
float rms = std::sqrt(mean);
50-
float mean_db = 10 * std::log10(rms); // Max level is 1.0f
50+
51+
// Floor at -120 dBFS so log10(0) doesn't produce -inf either.
52+
// (UI applies its own -60 dBFS floor on top of this; this is
53+
// just defensive math against inf/NaN propagating.)
54+
constexpr float RMS_FLOOR = 1e-6f; // -120 dBFS
55+
if (rms < RMS_FLOOR) rms = RMS_FLOOR;
56+
float mean_db = 20.0f * std::log10(rms);
5157

5258
feedback_send(mean_db);
5359

0 commit comments

Comments
 (0)