Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
audio_bridge.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <array>
5#include <cmath>
6#include <cstdint>
8
9namespace pulp::dataplane {
10
11// ── Metering ─────────────────────────────────────────────────────────────────
12
13// Per-channel level data sent from audio thread to UI
14struct MeterData {
15 static constexpr int max_channels = 8;
16
17 float peak[max_channels] = {}; // Peak level (linear, 0-1+)
18 float rms[max_channels] = {}; // RMS level (linear, 0-1+)
19 int num_channels = 0;
20};
21
22// Ballistics processor for smooth meter display (runs on UI thread)
24 float display_peak = 0;
25 float display_rms = 0;
26 float held_peak = 0;
27 float hold_counter = 0;
28
45 [[nodiscard]] bool update(float new_peak, float new_rms, float dt, float attack = 0.001f,
46 float release = 0.3f, float hold_time = 1.5f) {
47 const float prev_peak = display_peak;
48 const float prev_rms = display_rms;
49 const float prev_held = held_peak;
50
51 // ONE NON-FINITE SAMPLE WOULD DISABLE THE GATE PERMANENTLY. A NaN takes
52 // the release branch, poisons the display value, survives the snaps
53 // below (every comparison against NaN is false), and then compares
54 // unequal to itself forever — so `changed` would be true on every frame
55 // for the rest of the process. Sanitise the input, and self-heal state
56 // that arrived non-finite by another route.
57 if (!std::isfinite(new_peak))
58 new_peak = 0.0f;
59 if (!std::isfinite(new_rms))
60 new_rms = 0.0f;
61 if (!std::isfinite(display_peak))
62 display_peak = 0.0f;
63 if (!std::isfinite(display_rms))
64 display_rms = 0.0f;
65 if (!std::isfinite(held_peak))
66 held_peak = 0.0f;
67
68 // Attack/release envelope
69 float attack_coeff = 1.0f - std::exp(-dt / attack);
70 float release_coeff = 1.0f - std::exp(-dt / release);
71
72 if (new_peak > display_peak)
73 display_peak += (new_peak - display_peak) * attack_coeff;
74 else
75 display_peak += (new_peak - display_peak) * release_coeff;
76
77 if (new_rms > display_rms)
78 display_rms += (new_rms - display_rms) * attack_coeff;
79 else
80 display_rms += (new_rms - display_rms) * release_coeff;
81
82 // Peak hold
83 if (new_peak >= held_peak) {
84 held_peak = new_peak;
85 hold_counter = hold_time;
86 } else {
87 // Floored: left to run it decrements without bound for as long as
88 // the meter is polled, which is forever.
89 hold_counter = std::max(0.0f, hold_counter - dt);
90 if (hold_counter <= 0) {
91 held_peak += (0.0f - held_peak) * release_coeff;
92 }
93 }
94
95 // Clamp to zero for very small values. Each of these decays
96 // exponentially and so approaches zero without ever reaching it — left
97 // unclamped it changes by a fraction of a bit every frame FOREVER, which
98 // is invisible on screen but means the meter never reports a still
99 // frame. Snapping is what makes a silent meter actually settle.
100 //
101 // These three rest at ZERO, so a floor test is enough. A smoother whose
102 // resting value is NOT zero needs the general form —
103 // `if (std::abs(v - target) < eps) v = target` — see
104 // CorrelationMeter::update, which settles wherever the signal is.
105 if (display_peak < 1e-6f)
106 display_peak = 0;
107 if (display_rms < 1e-6f)
108 display_rms = 0;
109 if (held_peak < 1e-6f)
110 held_peak = 0;
111
112 return display_peak != prev_peak || display_rms != prev_rms || held_peak != prev_held;
113 }
114};
115
116// ── Audio→UI Bridge ──────────────────────────────────────────────────────────
117
118// Lock-free bridge for sending audio data from the audio thread to the UI.
119// Uses TripleBuffer instead of FIFO: the audio thread always publishes the
120// latest meter data without risk of overflow, and the UI thread always reads
121// the most recent value. No data loss regardless of UI thread stalls.
123 public:
124 AudioBridge() = default;
125
126 // Called from audio thread: publish new meter data
127 void push_meter(const MeterData& data) {
128 meter_buf_.write(data);
129 }
130
131 // Called from UI thread: get latest meter data
132 // Returns true if data is available (always true after first push)
134 out = meter_buf_.read();
135 return out.num_channels > 0;
136 }
137
138 // Convenience: compute peak and RMS from a buffer and push
139 // Call from the audio callback after processing
140 void analyze_and_push(const float* const* channels, int num_channels, int num_samples) {
141 MeterData data;
142 data.num_channels = (std::min)(num_channels, MeterData::max_channels);
143
144 for (int ch = 0; ch < data.num_channels; ++ch) {
145 float peak = 0;
146 float sum_sq = 0;
147 for (int i = 0; i < num_samples; ++i) {
148 float s = std::abs(channels[ch][i]);
149 if (s > peak)
150 peak = s;
151 sum_sq += channels[ch][i] * channels[ch][i];
152 }
153 data.peak[ch] = peak;
154 data.rms[ch] = num_samples > 0 ? std::sqrt(sum_sq / num_samples) : 0;
155 }
156
157 push_meter(data);
158 }
159
160 private:
162};
163
164} // namespace pulp::dataplane
void analyze_and_push(const float *const *channels, int num_channels, int num_samples)
Definition audio_bridge.hpp:140
bool pop_latest_meter(MeterData &out)
Definition audio_bridge.hpp:133
void push_meter(const MeterData &data)
Definition audio_bridge.hpp:127
Lock-free triple buffer for publishing the latest value from a writer thread to a reader thread witho...
Definition triple_buffer.hpp:45
Definition audio_bridge.hpp:9
Definition audio_bridge.hpp:23
bool update(float new_peak, float new_rms, float dt, float attack=0.001f, float release=0.3f, float hold_time=1.5f)
Update with new audio data.
Definition audio_bridge.hpp:45
float held_peak
Definition audio_bridge.hpp:26
float display_rms
Definition audio_bridge.hpp:25
float display_peak
Definition audio_bridge.hpp:24
float hold_counter
Definition audio_bridge.hpp:27
Definition audio_bridge.hpp:14
float rms[max_channels]
Definition audio_bridge.hpp:18
static constexpr int max_channels
Definition audio_bridge.hpp:15
float peak[max_channels]
Definition audio_bridge.hpp:17
int num_channels
Definition audio_bridge.hpp:19
Lock-free latest-value publication between two threads.