Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
visualization_bridge.hpp
Go to the documentation of this file.
1#pragma once
2
13
14#include <algorithm>
15#include <array>
16#include <atomic>
17#include <cmath>
18#include <cstdint>
23#include <pulp/signal/stft.hpp>
24#include <vector>
25
26namespace pulp::dataplane {
27
35 std::atomic<double>* audio_copy_total_us = nullptr;
36 std::atomic<double>* triplebuffer_publish_total_us = nullptr;
37};
38
47 static constexpr int kMaxBins = 4097; // FFT 8192 → 4097 bins
48
49 std::array<float, kMaxBins> magnitude_db{};
50 int num_bins = 0;
51 std::uint64_t epoch = 0;
52 std::uint64_t sequence_number = 0;
53 std::uint64_t dropped_frames = 0;
55 int fft_size = 0;
56 float sample_rate = 0.0f;
57 float floor_db = -120.0f;
58};
59
62 static constexpr int kMaxSamples = 8192;
63
64 std::array<float, kMaxSamples> samples{};
65 int num_samples = 0;
66 int num_channels = 0; // source layout; samples contain first channel only
67 std::uint64_t epoch = 0;
68 std::uint64_t sequence_number = 0;
69 std::uint64_t dropped_frames = 0;
70};
71
91
94 // STFT
95 int fft_size = 1024;
96 int hop_size = 256;
98 float window_param = 0.0f;
99
100 // Metering and capture layout. The captured channel count is invariant
101 // until the bridge is quiescent and configured again. Blocks with another
102 // layout are metered but intentionally not added to the analyzer stream.
104 float sample_rate = 44100.0f;
105
106 // Waveform capture
107 bool capture_waveform = true;
108 int waveform_length = 1024; // samples to capture per snapshot
109
110 // Audio frames retained between non-RT polls. Zero selects an automatic
111 // capacity of at least four FFT windows and max_callback_frames. When the
112 // consumer falls behind, new frames are dropped and the cumulative count
113 // is stamped in snapshots.
115
116 // Optional upper bound on frames analyzed by one poll. Zero consumes the
117 // frame count visible at entry. A positive value supports predictable UI
118 // pacing while leaving the remainder queued for the next tick.
120
121 // dB value published for silence and used as the finite lower bound.
122 float spectrum_floor_db = -120.0f;
123
124 // Largest frame count the host can deliver in one audio callback. The
125 // automatic capture capacity and any smaller explicit capacity are raised
126 // to this bound so a valid callback can always be published atomically.
127 // Set this to the host's offline/variable-block maximum before configure().
129
130 // Backlog handling for a consumer that polls slower than audio arrives.
131 // A spectrum display that polls from its frame tick should usually select
132 // latest_window; in_order preserves every hop for consumers that need
133 // continuous analysis history. Declared last so positional aggregate
134 // initialization of the earlier fields keeps its meaning.
136};
137
150 public:
152
155 void configure(const VisualizationConfig& config) {
156 config_ = config;
157 config_.num_channels = std::clamp(config.num_channels, 0, signal::kMaxMeterChannels);
158 config_.sample_rate = std::isfinite(config.sample_rate) && config.sample_rate > 0.0f
159 ? config.sample_rate
160 : 44100.0f;
161 config_.spectrum_floor_db = std::isfinite(config.spectrum_floor_db)
162 ? std::clamp(config.spectrum_floor_db, -300.0f, 0.0f)
163 : -120.0f;
164 config_.max_frames_per_poll = std::max(0, config.max_frames_per_poll);
165 config_.max_callback_frames = std::max(1, config.max_callback_frames);
166
167 // Configure one independent STFT per channel. Combining complex audio
168 // before the FFT would make anti-phase stereo disappear visually.
169 signal::StftConfig stft_cfg;
170 stft_cfg.fft_size = config_.fft_size;
171 stft_cfg.hop_size = config_.hop_size;
172 stft_cfg.window = config_.window;
173 stft_cfg.window_param = config_.window_param;
174 channel_stfts_.clear();
175 channel_stfts_.resize(static_cast<std::size_t>(config_.num_channels));
176 for (auto& stft : channel_stfts_)
177 stft.configure(stft_cfg);
178 spectrum_edge_scale_ = 1.0;
179 spectrum_interior_scale_ = 1.0;
180 if (!channel_stfts_.empty()) {
181 const double window_sum = static_cast<double>(channel_stfts_.front().window_sum());
182 if (std::isfinite(window_sum) && std::abs(window_sum) > 1.0e-12) {
183 spectrum_edge_scale_ = 1.0 / window_sum;
184 spectrum_interior_scale_ = 2.0 / window_sum;
185 }
186 }
187
188 // Configure meter
189 meter_.prepare(config_.sample_rate, config_.num_channels);
190
191 // Waveform capture setup
192 waveform_length_ = config_.capture_waveform
193 ? std::clamp(config_.waveform_length, 1,
194 static_cast<int>(WaveformData::kMaxSamples))
195 : 0;
196 waveform_pos_ = 0;
197
198 const int automatic_capacity = std::max(
199 {config_.fft_size * 4, std::max(1, waveform_length_), config_.max_callback_frames});
200 const int requested_capacity =
201 config_.capture_buffer_frames > 0 ? config_.capture_buffer_frames : automatic_capacity;
202 capture_capacity_frames_ =
203 std::max({config_.fft_size, requested_capacity, config_.max_callback_frames});
204 capture_ready_ = capture_.prepare(static_cast<std::uint32_t>(channel_stfts_.size()),
205 static_cast<std::uint64_t>(capture_capacity_frames_));
206
207 consumer_chunk_frames_ = std::min(capture_capacity_frames_, 4096);
208 consumer_storage_.assign(
209 channel_stfts_.size() * static_cast<std::size_t>(consumer_chunk_frames_), 0.0f);
210 consumer_channels_.resize(channel_stfts_.size());
211 for (std::size_t ch = 0; ch < channel_stfts_.size(); ++ch) {
212 consumer_channels_[ch] =
213 consumer_storage_.data() + ch * static_cast<std::size_t>(consumer_chunk_frames_);
214 }
215
216 dropped_capture_frames_.store(0, std::memory_order_relaxed);
217 discontinuity_generation_.store(0, std::memory_order_relaxed);
218 acknowledged_discontinuity_.store(0, std::memory_order_relaxed);
219 observed_discontinuity_ = 0;
220 spectrum_sequence_ = 0;
221 waveform_sequence_ = 0;
222 if (++epoch_ == 0)
223 ++epoch_;
224 spectrum_buf_.write(SpectrumData{});
225 waveform_buf_.write(WaveformData{});
226 }
227
234 void process(const float* const* channels, int num_channels, int num_samples) noexcept;
235
236#ifdef PULP_BENCHMARK
240 void set_bench_sink(const VisualizationBenchSink& sink) {
241 bench_sink_ = sink;
242 }
243#endif
244
245 // ── Non-RT analysis and UI snapshot reads ───────────────────────────────
246
250 bool poll();
251
254 return spectrum_buf_.read();
255 }
256
261 return peek_spectrum();
262 }
263
266 return meter_buf_.read();
267 }
268
271 return waveform_buf_.read();
272 }
273
278 return peek_waveform();
279 }
280
281 // ── Accessors ───────────────────────────────────────────────────────────
282
283 int fft_size() const {
284 return config_.fft_size;
285 }
286 int num_bins() const {
287 return channel_stfts_.empty() ? 0 : channel_stfts_.front().num_bins();
288 }
289 int num_channels() const {
290 return config_.num_channels;
291 }
292 float sample_rate() const {
293 return config_.sample_rate;
294 }
295
298 void reset() {
299 capture_.reset();
300 for (auto& stft : channel_stfts_)
301 stft.reset();
302 meter_.reset();
303 waveform_pos_ = 0;
304 std::fill(waveform_ring_.begin(), waveform_ring_.end(), 0.0f);
305 dropped_capture_frames_.store(0, std::memory_order_relaxed);
306 discontinuity_generation_.store(0, std::memory_order_relaxed);
307 acknowledged_discontinuity_.store(0, std::memory_order_relaxed);
308 observed_discontinuity_ = 0;
309 spectrum_sequence_ = 0;
310 waveform_sequence_ = 0;
311 if (++epoch_ == 0)
312 ++epoch_;
313 spectrum_buf_.write(SpectrumData{});
314 meter_buf_.write(signal::MultiChannelMeterData{});
315 waveform_buf_.write(WaveformData{});
316 }
317
318 private:
319 void reset_analysis_continuity();
320
321 VisualizationConfig config_;
322
323 // Fixed SPSC tap (audio producer, one UI consumer).
325 bool capture_ready_ = false;
326 int capture_capacity_frames_ = 0;
327
328 // Non-RT consumer analysis state.
329 std::vector<signal::Stft> channel_stfts_;
330 std::vector<float> consumer_storage_;
331 std::vector<float*> consumer_channels_;
332 int consumer_chunk_frames_ = 0;
333 // Coherent-gain correction for the one-sided real FFT. Calculated only in
334 // quiescent configure(); poll() applies it after the audio-thread copy.
335 double spectrum_edge_scale_ = 1.0;
336 double spectrum_interior_scale_ = 1.0;
337
338 // Multi-channel meter (audio thread)
340
341 // Lock-free publication buffers
345
346 // Waveform capture ring buffer
347 std::array<float, WaveformData::kMaxSamples> waveform_ring_{};
348 int waveform_length_ = 1024;
349 int waveform_pos_ = 0;
350
351 std::atomic<std::uint64_t> dropped_capture_frames_{0};
352 // A rejected producer block creates a time discontinuity. Capture remains
353 // suspended until poll() flushes the old continuity and acknowledges it,
354 // so post-gap audio can never be concatenated with the old STFT history.
355 std::atomic<std::uint64_t> discontinuity_generation_{0};
356 std::atomic<std::uint64_t> acknowledged_discontinuity_{0};
357 std::uint64_t observed_discontinuity_ = 0; // poll owner only
358 std::uint64_t spectrum_sequence_ = 0;
359 std::uint64_t waveform_sequence_ = 0;
360 std::uint64_t epoch_ = 0;
361
362#ifdef PULP_BENCHMARK
363 VisualizationBenchSink bench_sink_{};
364#endif
365};
366
367} // namespace pulp::dataplane
Definition planar_audio_ring_buffer.hpp:22
const WaveformData & peek_waveform()
Cheap snapshot-only waveform read. Never calls poll().
Definition visualization_bridge.hpp:270
const SpectrumData & read_spectrum()
Legacy snapshot-read name retained for compile compatibility.
Definition visualization_bridge.hpp:260
const WaveformData & read_waveform()
Legacy snapshot-read name retained for compile compatibility.
Definition visualization_bridge.hpp:277
const SpectrumData & peek_spectrum()
Cheap snapshot-only read. Never drains audio or performs FFT work.
Definition visualization_bridge.hpp:253
float sample_rate() const
Definition visualization_bridge.hpp:292
const signal::MultiChannelMeterData & read_meter()
Read the latest multi-channel meter data.
Definition visualization_bridge.hpp:265
void configure(const VisualizationConfig &config)
Configure all visualization subsystems.
Definition visualization_bridge.hpp:155
int fft_size() const
Definition visualization_bridge.hpp:283
void process(const float *const *channels, int num_channels, int num_samples) noexcept
Process audio from the audio callback.
void reset()
Reset all internal state after playback stops.
Definition visualization_bridge.hpp:298
bool poll()
Consume at most the captured frame count visible at entry and publish the latest spectrum/waveform.
int num_channels() const
Definition visualization_bridge.hpp:289
int num_bins() const
Definition visualization_bridge.hpp:286
Lock-free triple buffer for publishing the latest value from a writer thread to a reader thread witho...
Definition triple_buffer.hpp:45
Type
Definition windowing.hpp:43
@ hann
Definition windowing.hpp:45
Multi-channel metering: sample peak, RMS, K-weighted momentary and gated integrated loudness,...
Definition audio_bridge.hpp:9
VisualizationBacklogPolicy
How poll() treats captured audio it has not analyzed yet.
Definition visualization_bridge.hpp:73
@ latest_window
Display-oriented: every poll consumes the whole backlog, treating max_frames_per_poll as unbounded,...
Definition visualization_bridge.hpp:89
@ in_order
Analyze every captured frame in order, at most max_frames_per_poll per call.
Definition visualization_bridge.hpp:79
MultiChannelMeterT< float > MultiChannelMeter
Definition multi_channel_meter.hpp:1083
static constexpr int kMaxMeterChannels
Maximum supported channel count for metering.
Definition multi_channel_meter.hpp:35
Color ramp mapping and frequency axis utilities for spectrogram display.
Short-Time Fourier Transform abstraction for audio visualization.
Published one-sided amplitude spectrum (lock-free via TripleBuffer).
Definition visualization_bridge.hpp:46
int fft_size
Definition visualization_bridge.hpp:55
int source_channels
Definition visualization_bridge.hpp:54
float floor_db
Definition visualization_bridge.hpp:57
static constexpr int kMaxBins
Definition visualization_bridge.hpp:47
std::array< float, kMaxBins > magnitude_db
Definition visualization_bridge.hpp:49
std::uint64_t dropped_frames
Definition visualization_bridge.hpp:53
std::uint64_t sequence_number
Definition visualization_bridge.hpp:52
float sample_rate
Definition visualization_bridge.hpp:56
std::uint64_t epoch
Definition visualization_bridge.hpp:51
int num_bins
Definition visualization_bridge.hpp:50
Where the bridge reports its benchmark timings.
Definition visualization_bridge.hpp:34
std::atomic< double > * triplebuffer_publish_total_us
Definition visualization_bridge.hpp:36
std::atomic< double > * audio_copy_total_us
Definition visualization_bridge.hpp:35
Configuration for the visualization bridge.
Definition visualization_bridge.hpp:93
float spectrum_floor_db
Definition visualization_bridge.hpp:122
int hop_size
Definition visualization_bridge.hpp:96
int capture_buffer_frames
Definition visualization_bridge.hpp:114
VisualizationBacklogPolicy backlog_policy
Definition visualization_bridge.hpp:135
float sample_rate
Definition visualization_bridge.hpp:104
int waveform_length
Definition visualization_bridge.hpp:108
int num_channels
Definition visualization_bridge.hpp:103
int fft_size
Definition visualization_bridge.hpp:95
int max_callback_frames
Definition visualization_bridge.hpp:128
signal::WindowFunction::Type window
Definition visualization_bridge.hpp:97
int max_frames_per_poll
Definition visualization_bridge.hpp:119
bool capture_waveform
Definition visualization_bridge.hpp:107
float window_param
Definition visualization_bridge.hpp:98
Published waveform capture (latest buffer snapshot).
Definition visualization_bridge.hpp:61
static constexpr int kMaxSamples
Definition visualization_bridge.hpp:62
std::uint64_t epoch
Definition visualization_bridge.hpp:67
std::uint64_t sequence_number
Definition visualization_bridge.hpp:68
int num_channels
Definition visualization_bridge.hpp:66
int num_samples
Definition visualization_bridge.hpp:65
std::array< float, kMaxSamples > samples
Definition visualization_bridge.hpp:64
std::uint64_t dropped_frames
Definition visualization_bridge.hpp:69
Complete metering snapshot for all channels, published lock-free.
Definition multi_channel_meter.hpp:130
Configuration for an STFT processor.
Definition stft.hpp:20
float window_param
Definition stft.hpp:24
WindowFunction::Type window
Definition stft.hpp:23
int fft_size
Definition stft.hpp:21
int hop_size
Definition stft.hpp:22
Lock-free latest-value publication between two threads.