Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
spectral_frame_processor.hpp
Go to the documentation of this file.
1#pragma once
2
6
9
10#include <algorithm>
11#include <array>
12#include <atomic>
13#include <cmath>
14#include <complex>
15#include <cstdint>
16#include <limits>
17#include <memory>
18#include <stdexcept>
19#include <vector>
20
21namespace pulp::format {
22
32
39 std::complex<float>* const* channels = nullptr;
41 int bin_count = 0;
42 int fft_size = 0;
43 int hop = 0;
44 double sample_rate = 0;
46 std::uint64_t sequence = 0;
47 std::int64_t origin_sample = 0;
48};
49
53 public:
54 virtual ~SpectralFrameStage() = default;
55 virtual bool process(const SpectralFrameView& frame) noexcept = 0;
56};
57
61class SpectralFrameProcessor final : public Processor {
62 public:
63 explicit SpectralFrameProcessor(signal::SpectralFrameEngineConfig config) : config_(config) {}
64
65 bool add_stage(std::unique_ptr<SpectralFrameStage> stage) {
66 if (prepared_ || !stage || stages_.size() == kMaxStages)
67 return false;
68 stages_.push_back(std::move(stage));
69 return true;
70 }
71
72 PluginDescriptor descriptor() const override {
74 d.name = "Spectral Frame";
75 d.manufacturer = "Pulp";
76 d.bundle_id = "com.pulp.graph.spectral-frame";
77 d.version = "1.0.0";
78 d.category = PluginCategory::Effect;
79 d.input_buses = {{"Main In", config_.channels, false}};
80 d.output_buses = {{"Main Out", config_.channels, false}};
81 return d;
82 }
83
85
86 void prepare(const PrepareContext& context) override {
87 prepared_ = false;
88 if (!std::isfinite(context.sample_rate) || context.sample_rate <= 0 ||
89 config_.channels <= 0 || config_.channels > kMaxChannels ||
90 context.input_channels != config_.channels ||
91 context.output_channels != config_.channels || context.max_buffer_size <= 0) {
92 error_.store(SpectralFrameError::InvalidGeometry, std::memory_order_relaxed);
93 throw std::invalid_argument("spectral frame geometry disagrees with graph");
94 }
95 config_.max_block = context.max_buffer_size;
97 error_.store(SpectralFrameError::InvalidGeometry, std::memory_order_relaxed);
98 throw std::invalid_argument("invalid spectral frame geometry");
99 }
100 engine_.prepare(config_);
101 sample_rate_ = context.sample_rate;
102 sequence_.store(0, std::memory_order_relaxed);
103 error_.store(SpectralFrameError::None, std::memory_order_relaxed);
104 prepared_ = true;
105 }
106
107 void release() override {
108 prepared_ = false;
109 }
110
112 midi::MidiBuffer&, midi::MidiBuffer&, const ProcessContext& context) override {
113 const auto count = static_cast<int>(output.num_samples());
114 if (!prepared_ || count < 0 || count > config_.max_block ||
115 input.num_samples() != output.num_samples() ||
116 input.num_channels() != static_cast<std::size_t>(config_.channels) ||
117 output.num_channels() != static_cast<std::size_t>(config_.channels)) {
118 output.clear();
120 return;
121 }
122 if (context.reset_requested)
123 reset();
124 std::array<const float*, kMaxChannels> in{};
125 std::array<float*, kMaxChannels> out{};
126 for (int ch = 0; ch < config_.channels; ++ch) {
127 in[ch] = input.channel_ptr(static_cast<std::size_t>(ch));
128 out[ch] = output.channel_ptr(static_cast<std::size_t>(ch));
129 }
130 engine_.process(
131 in.data(), out.data(), count,
132 [this](std::complex<float>* const* bins, int bin_count) noexcept {
133 const auto sequence = sequence_.load(std::memory_order_relaxed);
134 if (sequence > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max() /
135 config_.analysis_hop)) {
136 set_error_(SpectralFrameError::SequenceOverflow);
137 for (int ch = 0; ch < config_.channels; ++ch)
138 std::fill_n(bins[ch], bin_count, std::complex<float>{});
139 return;
140 }
141 const auto origin = engine_.first_frame_start() +
142 static_cast<std::int64_t>(sequence) * config_.analysis_hop;
144 config_.channels,
145 bin_count,
146 config_.fft_size,
147 config_.analysis_hop,
148 sample_rate_,
149 config_.window,
150 sequence,
151 origin};
152 sequence_.store(sequence + 1, std::memory_order_relaxed);
153 if (!finite_(view)) {
155 mute_(view);
156 return;
157 }
158 for (const auto& stage : stages_) {
159 if (!stage->process(view)) {
161 mute_(view);
162 return;
163 }
164 if (!finite_(view)) {
166 mute_(view);
167 return;
168 }
169 }
170 });
171 }
172
173 int latency_samples() const override {
174 return prepared_ ? engine_.latency_samples() : 0;
175 }
176 SpectralFrameError error() const noexcept {
177 return error_.load(std::memory_order_relaxed);
178 }
179 std::uint64_t frames_processed() const noexcept {
180 return sequence_.load(std::memory_order_relaxed);
181 }
182 void reset() noexcept {
183 if (prepared_)
184 engine_.reset();
185 sequence_.store(0, std::memory_order_relaxed);
186 error_.store(SpectralFrameError::None, std::memory_order_relaxed);
187 }
188
189 private:
190 static constexpr std::size_t kMaxStages = 8;
191 static constexpr int kMaxChannels = 16;
192
193 static bool finite_(const SpectralFrameView& frame) noexcept {
194 if (frame.channels == nullptr || frame.channel_count <= 0 ||
195 frame.channel_count > kMaxChannels || frame.bin_count != frame.fft_size / 2 + 1)
196 return false;
197 for (int ch = 0; ch < frame.channel_count; ++ch) {
198 if (frame.channels[ch] == nullptr)
199 return false;
200 for (int bin = 0; bin < frame.bin_count; ++bin) {
201 const auto value = frame.channels[ch][bin];
202 if (!std::isfinite(value.real()) || !std::isfinite(value.imag()))
203 return false;
204 }
205 if (frame.channels[ch][0].imag() != 0 ||
206 frame.channels[ch][frame.bin_count - 1].imag() != 0)
207 return false;
208 }
209 return true;
210 }
211
212 static void mute_(const SpectralFrameView& frame) noexcept {
213 for (int ch = 0; ch < frame.channel_count; ++ch)
214 std::fill_n(frame.channels[ch], frame.bin_count, std::complex<float>{});
215 }
216 void set_error_(SpectralFrameError error) noexcept {
217 auto expected = SpectralFrameError::None;
218 error_.compare_exchange_strong(expected, error, std::memory_order_relaxed);
219 }
220
221 signal::SpectralFrameEngineConfig config_;
222 signal::SpectralFrameEngineT<float> engine_;
223 std::vector<std::unique_ptr<SpectralFrameStage>> stages_;
224 double sample_rate_ = 0;
225 std::atomic<std::uint64_t> sequence_{0};
226 std::atomic<SpectralFrameError> error_{SpectralFrameError::None};
227 bool prepared_ = false;
228};
229
230} // namespace pulp::format
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
std::size_t num_samples() const
Definition buffer.hpp:84
std::size_t num_channels() const
Definition buffer.hpp:83
void clear()
Zero-fill all channels.
Definition buffer.hpp:90
SampleType * channel_ptr(std::size_t index)
Raw pointer to one channel's sample data.
Definition buffer.hpp:68
Processor()
Definition processor.hpp:176
bool add_stage(std::unique_ptr< SpectralFrameStage > stage)
Definition spectral_frame_processor.hpp:65
void process(audio::BufferView< float > &output, const audio::BufferView< const float > &input, midi::MidiBuffer &, midi::MidiBuffer &, const ProcessContext &context) override
Process one buffer of audio.
Definition spectral_frame_processor.hpp:111
int latency_samples() const override
Latency in samples introduced by this processor (default 0).
Definition spectral_frame_processor.hpp:173
void reset() noexcept
Definition spectral_frame_processor.hpp:182
void prepare(const PrepareContext &context) override
Prepare for processing.
Definition spectral_frame_processor.hpp:86
void define_parameters(state::StateStore &) override
Register parameters with the state store.
Definition spectral_frame_processor.hpp:84
SpectralFrameProcessor(signal::SpectralFrameEngineConfig config)
Definition spectral_frame_processor.hpp:63
void release() override
Release resources.
Definition spectral_frame_processor.hpp:107
PluginDescriptor descriptor() const override
Return the plugin's metadata. Called once during initialization.
Definition spectral_frame_processor.hpp:72
std::uint64_t frames_processed() const noexcept
Definition spectral_frame_processor.hpp:179
SpectralFrameError error() const noexcept
Definition spectral_frame_processor.hpp:176
Stage instances are owned by the adapter.
Definition spectral_frame_processor.hpp:52
virtual bool process(const SpectralFrameView &frame) noexcept=0
virtual ~SpectralFrameStage()=default
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
Type
Definition windowing.hpp:43
@ hann
Definition windowing.hpp:45
Centralized parameter storage with lock-free value access.
Definition store.hpp:164
What the product grants a device: the quota table indexed by capability tier and live thermal state.
Definition aax_editor.hpp:31
@ error
Definition validation_harness.hpp:20
@ Effect
Audio effect (takes input, produces output).
Definition plugin_descriptor.hpp:186
SpectralFrameError
Definition spectral_frame_processor.hpp:23
@ StageFailure
Definition spectral_frame_processor.hpp:28
@ InvalidFrame
Definition spectral_frame_processor.hpp:27
@ SequenceOverflow
Definition spectral_frame_processor.hpp:30
@ InvalidGeometry
Definition spectral_frame_processor.hpp:25
@ None
Definition spectral_frame_processor.hpp:24
@ NonFiniteFrame
Definition spectral_frame_processor.hpp:29
@ InvalidBlock
Definition spectral_frame_processor.hpp:26
@ None
Definition ara.hpp:21
Definition host_transport_projector.hpp:8
std::optional< SpectralFrameEngineGeometry > checked_spectral_frame_engine_geometry(const SpectralFrameEngineConfig &config, std::uint64_t target_max_bytes=kTargetAddressMaximumBytes) noexcept
Derive every integer and typed backing-store capacity used by prepare().
Definition spectral_frame_engine.hpp:151
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
Streaming STFT analysis + weighted overlap-add (WOLA) synthesis for DSP-grade spectral processing — d...
Plugin metadata — declared once, immutable.
Definition plugin_descriptor.hpp:245
Definition prepare_resources.hpp:102
double sample_rate
Definition prepare_resources.hpp:103
int input_channels
Definition prepare_resources.hpp:105
int max_buffer_size
Definition prepare_resources.hpp:104
int output_channels
Definition prepare_resources.hpp:106
Process context — passed every audio callback with transport state.
Definition process_context.hpp:89
bool reset_requested
True for the first block after a transport jump, seek, or explicit DSP reset boundary.
Definition process_context.hpp:111
Borrowed only during Stage::process().
Definition spectral_frame_processor.hpp:38
int hop
Definition spectral_frame_processor.hpp:43
signal::WindowFunction::Type window
Definition spectral_frame_processor.hpp:45
int channel_count
Definition spectral_frame_processor.hpp:40
std::int64_t origin_sample
Definition spectral_frame_processor.hpp:47
std::uint64_t sequence
Definition spectral_frame_processor.hpp:46
int bin_count
Definition spectral_frame_processor.hpp:41
double sample_rate
Definition spectral_frame_processor.hpp:44
std::complex< float > *const * channels
Definition spectral_frame_processor.hpp:39
int fft_size
Definition spectral_frame_processor.hpp:42
Configuration for a SpectralFrameEngine.
Definition spectral_frame_engine.hpp:56