Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
spectral_feature_frontends.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <array>
7#include <cmath>
8#include <cstddef>
9#include <cstdint>
10#include <type_traits>
11
12namespace pulp::signal {
13
19template <typename SampleType> struct SpectralFeatureFrameT {
20 double centroid_hz = 0.0;
21 double flatness = 0.0;
22 double rolloff_hz = 0.0;
23 double flux = 0.0;
24 std::uint64_t window_start_frame = 0;
25 std::uint64_t window_center_frame = 0;
26 std::uint64_t ready_at_frame = 0;
27 std::uint64_t sequence = 0;
28 bool valid = false;
29};
30
41template <typename SampleType = float, std::size_t MaxFftSize = 4096, std::size_t MaxChannels = 8>
43 static_assert(std::is_floating_point_v<SampleType>);
44 static_assert(MaxFftSize >= 2);
45 static_assert(MaxChannels > 0);
46
47 public:
49
50 [[nodiscard]] bool prepare(const StreamingAnalysisConfig& config) {
51 if (config.fft_size < 4u)
52 return false;
53 if (!window_.prepare(config, detail::AnalysisWindow::symmetric_hann))
54 return false;
55 previous_normalized_magnitudes_.fill(0.0);
56 have_previous_ = false;
57 sequence_ = 0;
58 current_ = {};
59 return true;
60 }
61
62 void reset() noexcept {
63 window_.reset();
64 previous_normalized_magnitudes_.fill(0.0);
65 have_previous_ = false;
66 sequence_ = 0;
67 current_ = {};
68 }
69
70 template <typename Sink>
71 [[nodiscard]] bool process(const SampleType* const* channels, std::size_t channel_count,
72 std::size_t frames, Sink&& sink) noexcept {
73 static_assert(std::is_nothrow_invocable_v<Sink&, const Frame&>,
74 "analysis sinks must be noexcept");
75 if (!window_.prepared() || channel_count != window_.config().channels ||
76 channels == nullptr)
77 return false;
78 for (std::size_t channel = 0; channel < channel_count; ++channel) {
79 if (channels[channel] == nullptr)
80 return false;
81 }
82
83 bool finite = true;
84 for (std::size_t frame = 0; frame < frames; ++frame) {
85 double mono{};
86 double power{};
87 if (!detail::mix_planar_frame<SampleType, MaxChannels>(channels, channel_count, frame,
88 false, mono, power)) {
89 reject_sample();
90 finite = false;
91 continue;
92 }
93 if (!window_.push(mono, power))
94 continue;
95 if (!render_frame()) {
96 clear_history_after_fault();
97 finite = false;
98 continue;
99 }
100 sink(current_);
101 }
102 return finite;
103 }
104
105 [[nodiscard]] const Frame& current() const noexcept {
106 return current_;
107 }
108 [[nodiscard]] std::uint32_t algorithmic_latency_samples() const noexcept {
109 return window_.prepared() ? window_.config().fft_size / 2u : 0u;
110 }
111 [[nodiscard]] std::uint32_t startup_samples() const noexcept {
112 return window_.prepared() ? window_.config().fft_size : 0u;
113 }
114 [[nodiscard]] std::uint64_t retained_fft_bytes() const noexcept {
115 return window_.retained_fft_bytes();
116 }
117
118 private:
119 void clear_history_after_fault() noexcept {
120 window_.clear_history_after_fault();
121 previous_normalized_magnitudes_.fill(0.0);
122 have_previous_ = false;
123 }
124
125 void reject_sample() noexcept {
126 window_.reject_sample();
127 previous_normalized_magnitudes_.fill(0.0);
128 have_previous_ = false;
129 }
130
131 [[nodiscard]] bool render_frame() noexcept {
132 if (!window_.render_gain_normalized_spectrum())
133 return false;
134
135 const auto& config = window_.config();
136 const auto& spectrum = window_.spectrum();
137 const auto bins = config.fft_size / 2u + 1u;
138 double maximum_magnitude = 0.0;
139 for (std::size_t bin = 0; bin < bins; ++bin) {
140 const auto magnitude = static_cast<double>(std::abs(spectrum[bin]));
141 if (!std::isfinite(magnitude))
142 return false;
143 maximum_magnitude = std::max(maximum_magnitude, magnitude);
144 }
145
146 Frame next{};
147 if (maximum_magnitude > 0.0) {
148 constexpr long double rolloff_fraction = 0.85L;
149 constexpr long double flatness_floor = 1.0e-20L;
150 constexpr long double inverse_sqrt_two = 0.70710678118654752440L;
151 long double scaled_magnitude_sum = 0.0L;
152 long double scaled_power_sum = 0.0L;
153 for (std::size_t bin = 0; bin < bins; ++bin) {
154 const auto scaled_magnitude = static_cast<long double>(std::abs(spectrum[bin])) /
155 static_cast<long double>(maximum_magnitude);
156 scaled_magnitude_sum += scaled_magnitude;
157 scaled_power_sum += scaled_magnitude * scaled_magnitude;
158 }
159 if (!std::isfinite(scaled_magnitude_sum) || !std::isfinite(scaled_power_sum) ||
160 scaled_magnitude_sum <= 0.0L || scaled_power_sum <= 0.0L)
161 return false;
162
163 long double weighted_frequency = 0.0L;
164 long double log_sum = 0.0L;
165 long double cumulative_power = 0.0L;
166 long double flux_squared = 0.0L;
167 bool rolloff_found = false;
168 for (std::size_t bin = 0; bin < bins; ++bin) {
169 const auto frequency =
170 static_cast<long double>(config.sample_rate) *
171 (static_cast<long double>(bin) / static_cast<long double>(config.fft_size));
172 const auto scaled_magnitude = static_cast<long double>(std::abs(spectrum[bin])) /
173 static_cast<long double>(maximum_magnitude);
174 const auto normalized =
175 static_cast<double>(scaled_magnitude / scaled_magnitude_sum);
176 weighted_frequency += frequency * static_cast<long double>(normalized);
177 log_sum += std::log(std::max(static_cast<long double>(normalized), flatness_floor));
178 if (have_previous_) {
179 const auto delta =
180 static_cast<long double>(normalized - previous_normalized_magnitudes_[bin]);
181 flux_squared += delta * delta;
182 }
183 previous_normalized_magnitudes_[bin] = normalized;
184 cumulative_power += scaled_magnitude * scaled_magnitude;
185 if (!rolloff_found && cumulative_power >= rolloff_fraction * scaled_power_sum) {
186 next.rolloff_hz = static_cast<double>(frequency);
187 rolloff_found = true;
188 }
189 }
190 next.centroid_hz = static_cast<double>(weighted_frequency);
191 const auto arithmetic_mean = 1.0L / static_cast<long double>(bins);
192 const auto geometric_mean = std::exp(log_sum / static_cast<long double>(bins));
193 next.flatness =
194 static_cast<double>(std::clamp(geometric_mean / arithmetic_mean, 0.0L, 1.0L));
195 next.flux = have_previous_
196 ? static_cast<double>(std::clamp(
197 std::sqrt(flux_squared) * inverse_sqrt_two, 0.0L, 1.0L))
198 : 0.0;
199 next.valid = true;
200 } else {
201 previous_normalized_magnitudes_.fill(0.0);
202 }
203 have_previous_ = next.valid;
204
205 if (!std::isfinite(next.centroid_hz) || !std::isfinite(next.flatness) ||
206 !std::isfinite(next.rolloff_hz) || !std::isfinite(next.flux))
207 return false;
208 next.ready_at_frame = window_.stream_frames() - 1u;
209 next.window_start_frame = window_.stream_frames() - config.fft_size;
210 next.window_center_frame = next.window_start_frame + config.fft_size / 2u;
211 next.sequence = ++sequence_;
212 current_ = next;
213 return true;
214 }
215
216 detail::StreamingAnalysisWindowT<SampleType, MaxFftSize, MaxChannels> window_{};
217 std::array<double, MaxFftSize / 2u + 1u> previous_normalized_magnitudes_{};
218 Frame current_{};
219 std::uint64_t sequence_ = 0;
220 bool have_previous_ = false;
221};
222
225
226} // namespace pulp::signal
Fixed-capacity streaming centroid, flatness, rolloff, and flux analysis.
Definition spectral_feature_frontends.hpp:42
std::uint64_t retained_fft_bytes() const noexcept
Definition spectral_feature_frontends.hpp:114
std::uint32_t startup_samples() const noexcept
Definition spectral_feature_frontends.hpp:111
void reset() noexcept
Definition spectral_feature_frontends.hpp:62
const Frame & current() const noexcept
Definition spectral_feature_frontends.hpp:105
std::uint32_t algorithmic_latency_samples() const noexcept
Definition spectral_feature_frontends.hpp:108
bool process(const SampleType *const *channels, std::size_t channel_count, std::size_t frames, Sink &&sink) noexcept
Definition spectral_feature_frontends.hpp:71
bool prepare(const StreamingAnalysisConfig &config)
Definition spectral_feature_frontends.hpp:50
SpectralFeatureFrameT< SampleType > Frame
Definition spectral_feature_frontends.hpp:48
@ config
Per-user settings root (an XDG distinction; equals app_data off Linux).
Definition special_dir.hpp:65
@ symmetric_hann
Definition analysis_frontends.hpp:52
bool mix_planar_frame(const SampleType *const *channels, std::size_t channel_count, std::size_t frame, bool compute_power, double &mono, double &channel_power) noexcept
Definition analysis_frontends.hpp:239
Definition additive_bank.hpp:218
SpectralFeatureFrontEndT< double > SpectralFeatureFrontEnd64
Definition spectral_feature_frontends.hpp:224
@ u
Definition formant_filter_bank.hpp:20
SpectralFeatureFrontEndT< float > SpectralFeatureFrontEnd
Definition spectral_feature_frontends.hpp:223
One complete control-rate spectral-feature window.
Definition spectral_feature_frontends.hpp:19
std::uint64_t window_center_frame
Definition spectral_feature_frontends.hpp:25
double flatness
Definition spectral_feature_frontends.hpp:21
std::uint64_t window_start_frame
Definition spectral_feature_frontends.hpp:24
double rolloff_hz
Definition spectral_feature_frontends.hpp:22
double flux
Definition spectral_feature_frontends.hpp:23
std::uint64_t ready_at_frame
Definition spectral_feature_frontends.hpp:26
double centroid_hz
Definition spectral_feature_frontends.hpp:20
std::uint64_t sequence
Definition spectral_feature_frontends.hpp:27
bool valid
Definition spectral_feature_frontends.hpp:28
Definition analysis_frontends.hpp:42