Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
spectral_band_mask.hpp
Go to the documentation of this file.
1#pragma once
2
13
15
16#include <algorithm>
17#include <array>
18#include <cmath>
19#include <complex>
20#include <cstddef>
21#include <cstdint>
22#include <limits>
23#include <type_traits>
24
25namespace pulp::signal {
26
27inline constexpr std::size_t kSpectralBandMaskMaximumBands = 64;
28inline constexpr std::size_t kSpectralBandMaskMaximumBins =
29 static_cast<std::size_t>(kSpectralFrameEngineMaximumFftSize / 2 + 1);
30inline constexpr std::uint32_t kSpectralBandMaskMaximumTransitionFrames = 4096;
31
32enum class SpectralBandSpacing : std::uint8_t {
35};
36
41
42enum class SpectralMaskBoundaryKernel : std::uint8_t {
45};
46
47template <typename SampleType>
49 static_assert(std::is_floating_point_v<SampleType>);
50 SampleType gain_db = SampleType{0};
51 bool muted = false;
52};
53
54template <typename SampleType>
56 static_assert(std::is_floating_point_v<SampleType>);
57
58 std::uint32_t active_bands = 32;
59 SampleType min_hz = SampleType{20};
60 SampleType max_hz = SampleType{20000};
65
69 SampleType transition_fraction = SampleType{0.08};
70
73 std::uint32_t transition_frames = 4;
74 std::uint64_t version = 0;
75 std::array<SpectralBandT<SampleType>, kSpectralBandMaskMaximumBands> bands{};
76};
77
83template <typename SampleType>
85 static_assert(std::is_floating_point_v<SampleType>);
86
87 int fft_size = 0;
88 int num_bins = 0;
89 SampleType sample_rate = SampleType{0};
90 SampleType bin_width_hz = SampleType{0};
91 SampleType effective_min_hz = SampleType{0};
92 SampleType effective_max_hz = SampleType{0};
93 std::uint32_t active_bands = 0;
94 std::uint32_t represented_bands = 0;
95 std::uint32_t viewport_bins = 0;
96 std::array<std::uint32_t, kSpectralBandMaskMaximumBands> owned_bins{};
97
98 [[nodiscard]] bool fully_represented() const noexcept {
100 }
101};
102
103template <typename SampleType>
105 static_assert(std::is_floating_point_v<SampleType>);
106
107 int fft_size = 0;
108 int num_bins = 0;
109 SampleType sample_rate = SampleType{0};
110 SampleType effective_min_hz = SampleType{0};
111 SampleType effective_max_hz = SampleType{0};
112 std::uint32_t active_bands = 0;
113 std::uint32_t transition_frames = 0;
114 std::uint64_t version = 0;
115 std::array<SampleType, kSpectralBandMaskMaximumBands + 1> band_edges_hz{};
116 std::array<SampleType, kSpectralBandMaskMaximumBins> gain_linear{};
117};
118
119namespace detail {
120
121template <typename SampleType>
123 SampleType nyquist,
124 SampleType& effective_min,
125 SampleType& effective_max) noexcept {
126 const bool valid_spacing = layout.spacing == SpectralBandSpacing::linear
127 || layout.spacing == SpectralBandSpacing::logarithmic;
128 const bool valid_edge_policy =
129 layout.edge_policy == SpectralBandEdgePolicy::mute_outside
130 || layout.edge_policy == SpectralBandEdgePolicy::extend_edge_band;
131 const bool valid_kernel = layout.boundary_kernel == SpectralMaskBoundaryKernel::hard
132 || layout.boundary_kernel
134 if (layout.active_bands == 0
135 || layout.active_bands > kSpectralBandMaskMaximumBands
136 || !valid_spacing
137 || !valid_edge_policy
138 || !valid_kernel
139 || !std::isfinite(layout.min_hz)
140 || !std::isfinite(layout.max_hz)
141 || !std::isfinite(layout.transition_fraction)
142 || layout.min_hz < SampleType{0}
143 || layout.max_hz <= layout.min_hz
144 || layout.transition_fraction < SampleType{0}
145 || layout.transition_fraction > SampleType{0.5}
146 || layout.transition_frames > kSpectralBandMaskMaximumTransitionFrames)
147 return false;
148
149 effective_min = std::clamp(layout.min_hz, SampleType{0}, nyquist);
150 effective_max = std::clamp(layout.max_hz, SampleType{0}, nyquist);
151 if (!(effective_max > effective_min)) return false;
152 if (layout.spacing == SpectralBandSpacing::logarithmic
153 && !(effective_min > SampleType{0}))
154 return false;
155
156 for (std::uint32_t band = 0; band < layout.active_bands; ++band) {
157 const auto gain_db = layout.bands[band].gain_db;
158 // A broad but bounded framework range. Products may expose a narrower
159 // host contract (Spectr uses -24..+24 dB).
160 if (!std::isfinite(gain_db)
161 || gain_db < SampleType{-192}
162 || gain_db > SampleType{48})
163 return false;
164 }
165 return true;
166}
167
168template <typename SampleType>
169inline SampleType band_coordinate(SampleType hz,
170 SampleType min_hz,
171 SampleType max_hz,
172 SpectralBandSpacing spacing) noexcept {
173 if (spacing == SpectralBandSpacing::linear)
174 return (hz - min_hz) / (max_hz - min_hz);
175 return std::log(hz / min_hz) / std::log(max_hz / min_hz);
176}
177
178template <typename SampleType>
179inline SampleType frequency_at_coordinate(SampleType coordinate,
180 SampleType min_hz,
181 SampleType max_hz,
182 SpectralBandSpacing spacing) noexcept {
183 if (spacing == SpectralBandSpacing::linear)
184 return min_hz + coordinate * (max_hz - min_hz);
185 return min_hz * std::pow(max_hz / min_hz, coordinate);
186}
187
188template <typename SampleType>
189inline SampleType raised_cosine(SampleType position) noexcept {
190 const auto x = std::clamp(position, SampleType{0}, SampleType{1});
191 constexpr SampleType pi =
192 static_cast<SampleType>(3.14159265358979323846264338327950288L);
193 return SampleType{0.5} - SampleType{0.5} * std::cos(pi * x);
194}
195
196template <typename SampleType>
197inline SampleType band_gain(const SpectralBandLayoutT<SampleType>& layout,
198 std::uint32_t index) noexcept {
199 const auto& band = layout.bands[index];
200 return band.muted
201 ? SampleType{0}
202 : std::pow(SampleType{10}, band.gain_db * SampleType{0.05});
203}
204
205template <typename SampleType>
206inline std::uint32_t band_index_at_frequency(
207 SampleType hz,
208 SampleType min_hz,
209 SampleType max_hz,
210 SpectralBandSpacing spacing,
211 std::uint32_t band_count) noexcept {
212 const SampleType coordinate = std::clamp(
213 band_coordinate(hz, min_hz, max_hz, spacing),
214 SampleType{0}, SampleType{1});
215 const SampleType scaled = coordinate * static_cast<SampleType>(band_count);
216 return std::min<std::uint32_t>(static_cast<std::uint32_t>(scaled),
217 band_count - 1);
218}
219
220} // namespace detail
221
227template <typename SampleType>
230 int fft_size,
231 SampleType sample_rate,
232 SpectralBandResolutionT<SampleType>& out_resolution) noexcept {
233 static_assert(std::is_floating_point_v<SampleType>);
234 if (!is_valid_spectral_frame_geometry(fft_size, fft_size / 4)
235 || !std::isfinite(sample_rate)
236 || sample_rate <= SampleType{0})
237 return false;
238
239 const int num_bins = fft_size / 2 + 1;
240 if (num_bins <= 1
241 || static_cast<std::size_t>(num_bins) > kSpectralBandMaskMaximumBins)
242 return false;
243
244 const SampleType nyquist = sample_rate * SampleType{0.5};
245 SampleType min_hz = SampleType{0};
246 SampleType max_hz = SampleType{0};
247 if (!detail::valid_spectral_band_layout(layout, nyquist, min_hz, max_hz))
248 return false;
249
251 candidate.fft_size = fft_size;
252 candidate.num_bins = num_bins;
253 candidate.sample_rate = sample_rate;
254 candidate.bin_width_hz = sample_rate / static_cast<SampleType>(fft_size);
255 candidate.effective_min_hz = min_hz;
256 candidate.effective_max_hz = max_hz;
257 candidate.active_bands = layout.active_bands;
258
259 for (int bin = 0; bin < num_bins; ++bin) {
260 const SampleType hz = static_cast<SampleType>(bin)
261 * candidate.bin_width_hz;
262 if (hz < min_hz || hz > max_hz) continue;
263 const auto band = detail::band_index_at_frequency(
264 hz, min_hz, max_hz, layout.spacing, layout.active_bands);
265 ++candidate.owned_bins[band];
266 ++candidate.viewport_bins;
267 }
268 for (std::uint32_t band = 0; band < layout.active_bands; ++band)
269 if (candidate.owned_bins[band] > 0) ++candidate.represented_bands;
270
271 out_resolution = candidate;
272 return true;
273}
274
278template <typename SampleType>
280 int fft_size,
281 SampleType sample_rate,
282 SpectralMaskTableT<SampleType>& out_table) noexcept {
283 static_assert(std::is_floating_point_v<SampleType>);
284 if (!is_valid_spectral_frame_geometry(fft_size, fft_size / 4)
285 || !std::isfinite(sample_rate)
286 || sample_rate <= SampleType{0})
287 return false;
288
289 const int num_bins = fft_size / 2 + 1;
290 if (num_bins <= 1
291 || static_cast<std::size_t>(num_bins) > kSpectralBandMaskMaximumBins)
292 return false;
293
294 const SampleType nyquist = sample_rate * SampleType{0.5};
295 SampleType min_hz = SampleType{0};
296 SampleType max_hz = SampleType{0};
297 if (!detail::valid_spectral_band_layout(layout, nyquist, min_hz, max_hz))
298 return false;
299
301 candidate.fft_size = fft_size;
302 candidate.num_bins = num_bins;
303 candidate.sample_rate = sample_rate;
304 candidate.effective_min_hz = min_hz;
305 candidate.effective_max_hz = max_hz;
306 candidate.active_bands = layout.active_bands;
307 candidate.transition_frames = layout.transition_frames;
308 candidate.version = layout.version;
309
310 const auto band_count = layout.active_bands;
311 for (std::uint32_t edge = 0; edge <= band_count; ++edge) {
312 const auto coordinate = static_cast<SampleType>(edge)
313 / static_cast<SampleType>(band_count);
314 candidate.band_edges_hz[edge] = detail::frequency_at_coordinate(
315 coordinate, min_hz, max_hz, layout.spacing);
316 }
317
318 const SampleType half_width = layout.transition_fraction
319 / static_cast<SampleType>(band_count);
320 const bool smooth = layout.boundary_kernel
322 && half_width > SampleType{0};
323
324 for (int bin = 0; bin < num_bins; ++bin) {
325 const SampleType hz = (static_cast<SampleType>(bin)
326 / static_cast<SampleType>(fft_size)) * sample_rate;
327 SampleType gain = SampleType{0};
328
329 if (hz < min_hz) {
330 gain = layout.edge_policy == SpectralBandEdgePolicy::extend_edge_band
331 ? detail::band_gain(layout, 0)
332 : SampleType{0};
333 } else if (hz > max_hz) {
334 gain = layout.edge_policy == SpectralBandEdgePolicy::extend_edge_band
335 ? detail::band_gain(layout, band_count - 1)
336 : SampleType{0};
337 } else {
338 const SampleType coordinate = std::clamp(
339 detail::band_coordinate(hz, min_hz, max_hz, layout.spacing),
340 SampleType{0}, SampleType{1});
341 const SampleType scaled = coordinate * static_cast<SampleType>(band_count);
342 const auto band = detail::band_index_at_frequency(
343 hz, min_hz, max_hz, layout.spacing, band_count);
344 gain = detail::band_gain(layout, band);
345
346 if (smooth) {
347 // Internal boundary nearest this bin.
348 const auto boundary_index = static_cast<std::uint32_t>(
349 std::floor(scaled + SampleType{0.5}));
350 if (boundary_index > 0 && boundary_index < band_count) {
351 const SampleType boundary = static_cast<SampleType>(boundary_index)
352 / static_cast<SampleType>(band_count);
353 if (std::abs(coordinate - boundary) <= half_width) {
354 const auto left = detail::band_gain(layout, boundary_index - 1);
355 const auto right = detail::band_gain(layout, boundary_index);
356 const auto position = (coordinate - (boundary - half_width))
357 / (SampleType{2} * half_width);
358 const auto mix = detail::raised_cosine(position);
359 gain = left + (right - left) * mix;
360 }
361 } else if (layout.edge_policy == SpectralBandEdgePolicy::mute_outside) {
362 if (coordinate <= half_width) {
363 const auto mix = detail::raised_cosine(
364 (coordinate + half_width) / (SampleType{2} * half_width));
365 gain = detail::band_gain(layout, 0) * mix;
366 } else if (coordinate >= SampleType{1} - half_width) {
367 const auto mix = detail::raised_cosine(
368 (coordinate - (SampleType{1} - half_width))
369 / (SampleType{2} * half_width));
370 gain = detail::band_gain(layout, band_count - 1)
371 * (SampleType{1} - mix);
372 }
373 }
374 }
375 }
376 candidate.gain_linear[static_cast<std::size_t>(bin)] = gain;
377 }
378
379 out_table = candidate;
380 return true;
381}
382
386template <typename SampleType>
387bool apply_spectral_mask(std::complex<SampleType>* const* frames,
388 int channels,
389 int num_bins,
390 const SpectralMaskTableT<SampleType>& table) noexcept {
391 static_assert(std::is_floating_point_v<SampleType>);
392 if (frames == nullptr || channels <= 0 || num_bins <= 0
393 || table.num_bins != num_bins
394 || table.fft_size < kSpectralFrameEngineMinimumFftSize
395 || table.fft_size > kSpectralFrameEngineMaximumFftSize
396 || table.fft_size / 2 + 1 != num_bins
397 || !std::isfinite(table.sample_rate)
398 || table.sample_rate <= SampleType{0}
399 || static_cast<std::size_t>(num_bins) > kSpectralBandMaskMaximumBins)
400 return false;
401 for (int channel = 0; channel < channels; ++channel)
402 if (frames[channel] == nullptr) return false;
403 for (int bin = 0; bin < num_bins; ++bin) {
404 const auto gain = table.gain_linear[static_cast<std::size_t>(bin)];
405 if (!std::isfinite(gain) || gain < SampleType{0}) return false;
406 }
407
408 for (int channel = 0; channel < channels; ++channel) {
409 for (int bin = 0; bin < num_bins; ++bin) {
410 auto& value = frames[channel][bin];
411 if (!std::isfinite(value.real()) || !std::isfinite(value.imag()))
412 value = {};
413 else {
414 const auto gain = table.gain_linear[static_cast<std::size_t>(bin)];
415 if (gain == SampleType{0}) {
416 value = {};
417 continue;
418 }
419 const auto maximum = std::numeric_limits<SampleType>::max();
420 const auto limit = maximum / gain;
421 const auto scale_component = [gain, limit, maximum](SampleType component) {
422 return std::abs(component) > limit
423 ? std::copysign(maximum, component)
424 : component * gain;
425 };
426 value = {scale_component(value.real()), scale_component(value.imag())};
427 }
428 }
429 }
430 return true;
431}
432
441
442} // namespace pulp::signal
SampleType band_gain(const SpectralBandLayoutT< SampleType > &layout, std::uint32_t index) noexcept
Definition spectral_band_mask.hpp:197
SampleType band_coordinate(SampleType hz, SampleType min_hz, SampleType max_hz, SpectralBandSpacing spacing) noexcept
Definition spectral_band_mask.hpp:169
bool valid_spectral_band_layout(const SpectralBandLayoutT< SampleType > &layout, SampleType nyquist, SampleType &effective_min, SampleType &effective_max) noexcept
Definition spectral_band_mask.hpp:122
SampleType frequency_at_coordinate(SampleType coordinate, SampleType min_hz, SampleType max_hz, SpectralBandSpacing spacing) noexcept
Definition spectral_band_mask.hpp:179
SampleType raised_cosine(SampleType position) noexcept
Definition spectral_band_mask.hpp:189
std::uint32_t band_index_at_frequency(SampleType hz, SampleType min_hz, SampleType max_hz, SpectralBandSpacing spacing, std::uint32_t band_count) noexcept
Definition spectral_band_mask.hpp:206
Definition additive_bank.hpp:218
constexpr std::uint32_t kSpectralBandMaskMaximumTransitionFrames
Definition spectral_band_mask.hpp:30
SpectralMaskBoundaryKernel
Definition spectral_band_mask.hpp:42
@ raised_cosine
Definition spectral_band_mask.hpp:44
@ hard
Definition spectral_band_mask.hpp:43
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
bool apply_spectral_mask(std::complex< SampleType > *const *frames, int channels, int num_bins, const SpectralMaskTableT< SampleType > &table) noexcept
Multiply one coherent positive-frequency frame group by a prepared mask.
Definition spectral_band_mask.hpp:387
SpectralMaskTableT< float > SpectralMaskTable
Definition spectral_band_mask.hpp:439
bool is_valid_spectral_frame_geometry(int fft_size, int analysis_hop) noexcept
Definition spectral_frame_engine.hpp:108
SpectralBandResolutionT< double > SpectralBandResolution64
Definition spectral_band_mask.hpp:438
SpectralBandEdgePolicy
Definition spectral_band_mask.hpp:37
@ extend_edge_band
Definition spectral_band_mask.hpp:39
@ mute_outside
Definition spectral_band_mask.hpp:38
SpectralBandLayoutT< float > SpectralBandLayout
Definition spectral_band_mask.hpp:435
SpectralBandResolutionT< float > SpectralBandResolution
Definition spectral_band_mask.hpp:437
bool analyze_spectral_band_resolution(const SpectralBandLayoutT< SampleType > &layout, int fft_size, SampleType sample_rate, SpectralBandResolutionT< SampleType > &out_resolution) noexcept
Report the number of discrete FFT bin centers directly owned by each band.
Definition spectral_band_mask.hpp:228
constexpr int kSpectralFrameEngineMaximumFftSize
Definition spectral_frame_engine.hpp:94
constexpr std::size_t kSpectralBandMaskMaximumBins
Definition spectral_band_mask.hpp:28
@ logarithmic
Definition modulation_curve.hpp:29
SpectralBandT< float > SpectralBand
Definition spectral_band_mask.hpp:433
bool build_spectral_mask(const SpectralBandLayoutT< SampleType > &layout, int fft_size, SampleType sample_rate, SpectralMaskTableT< SampleType > &out_table) noexcept
Compile a complete positive-frequency gain table.
Definition spectral_band_mask.hpp:279
SpectralMaskTableT< double > SpectralMaskTable64
Definition spectral_band_mask.hpp:440
@ maximum
Definition spectrum_trace.hpp:34
SpectralBandSpacing
Definition spectral_band_mask.hpp:32
@ logarithmic
Definition spectral_band_mask.hpp:34
@ linear
Definition spectral_band_mask.hpp:33
constexpr int kSpectralFrameEngineMinimumFftSize
Definition spectral_frame_engine.hpp:93
@ linear
2-point. Cheapest; audible imaging when pitching up.
Definition granular.hpp:133
constexpr std::size_t kSpectralBandMaskMaximumBands
Definition spectral_band_mask.hpp:27
SpectralBandT< double > SpectralBand64
Definition spectral_band_mask.hpp:434
SpectralBandLayoutT< double > SpectralBandLayout64
Definition spectral_band_mask.hpp:436
Streaming STFT analysis + weighted overlap-add (WOLA) synthesis for DSP-grade spectral processing — d...
Definition spectral_band_mask.hpp:55
float max_hz
Definition spectral_band_mask.hpp:60
float min_hz
Definition spectral_band_mask.hpp:59
float transition_fraction
Definition spectral_band_mask.hpp:69
SpectralBandEdgePolicy edge_policy
Definition spectral_band_mask.hpp:62
std::uint64_t version
Definition spectral_band_mask.hpp:74
SpectralBandSpacing spacing
Definition spectral_band_mask.hpp:61
std::uint32_t transition_frames
Definition spectral_band_mask.hpp:73
std::array< SpectralBandT< float >, kSpectralBandMaskMaximumBands > bands
Definition spectral_band_mask.hpp:75
std::uint32_t active_bands
Definition spectral_band_mask.hpp:58
SpectralMaskBoundaryKernel boundary_kernel
Definition spectral_band_mask.hpp:63
Control-side report of how discrete FFT bin centers represent an authored viewport.
Definition spectral_band_mask.hpp:84
int fft_size
Definition spectral_band_mask.hpp:87
float sample_rate
Definition spectral_band_mask.hpp:89
std::uint32_t viewport_bins
Definition spectral_band_mask.hpp:95
float effective_max_hz
Definition spectral_band_mask.hpp:92
float effective_min_hz
Definition spectral_band_mask.hpp:91
std::uint32_t represented_bands
Definition spectral_band_mask.hpp:94
std::array< std::uint32_t, kSpectralBandMaskMaximumBands > owned_bins
Definition spectral_band_mask.hpp:96
float bin_width_hz
Definition spectral_band_mask.hpp:90
std::uint32_t active_bands
Definition spectral_band_mask.hpp:93
bool fully_represented() const noexcept
Definition spectral_band_mask.hpp:98
int num_bins
Definition spectral_band_mask.hpp:88
Definition spectral_band_mask.hpp:48
bool muted
Definition spectral_band_mask.hpp:51
float gain_db
Definition spectral_band_mask.hpp:50
Definition spectral_band_mask.hpp:104
std::array< float, kSpectralBandMaskMaximumBins > gain_linear
Definition spectral_band_mask.hpp:116
std::uint32_t transition_frames
Definition spectral_band_mask.hpp:113
float sample_rate
Definition spectral_band_mask.hpp:109
std::array< float, kSpectralBandMaskMaximumBands+1 > band_edges_hz
Definition spectral_band_mask.hpp:115
float effective_max_hz
Definition spectral_band_mask.hpp:111
std::uint32_t active_bands
Definition spectral_band_mask.hpp:112
int num_bins
Definition spectral_band_mask.hpp:108
float effective_min_hz
Definition spectral_band_mask.hpp:110
std::uint64_t version
Definition spectral_band_mask.hpp:114
int fft_size
Definition spectral_band_mask.hpp:107