43 static_assert(std::is_floating_point_v<SampleType>);
44 static_assert(MaxFftSize >= 2);
45 static_assert(MaxChannels > 0);
51 if (config.fft_size < 4u)
55 previous_normalized_magnitudes_.fill(0.0);
56 have_previous_ =
false;
64 previous_normalized_magnitudes_.fill(0.0);
65 have_previous_ =
false;
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 ||
78 for (std::size_t channel = 0; channel < channel_count; ++channel) {
79 if (channels[channel] ==
nullptr)
84 for (std::size_t frame = 0; frame < frames; ++frame) {
88 false, mono, power)) {
93 if (!window_.push(mono, power))
95 if (!render_frame()) {
96 clear_history_after_fault();
109 return window_.prepared() ? window_.config().fft_size / 2u : 0
u;
112 return window_.prepared() ? window_.config().fft_size : 0
u;
115 return window_.retained_fft_bytes();
119 void clear_history_after_fault() noexcept {
120 window_.clear_history_after_fault();
121 previous_normalized_magnitudes_.fill(0.0);
122 have_previous_ =
false;
125 void reject_sample() noexcept {
126 window_.reject_sample();
127 previous_normalized_magnitudes_.fill(0.0);
128 have_previous_ =
false;
131 [[nodiscard]]
bool render_frame() noexcept {
132 if (!window_.render_gain_normalized_spectrum())
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))
143 maximum_magnitude = std::max(maximum_magnitude, magnitude);
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;
159 if (!std::isfinite(scaled_magnitude_sum) || !std::isfinite(scaled_power_sum) ||
160 scaled_magnitude_sum <= 0.0L || scaled_power_sum <= 0.0L)
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_) {
180 static_cast<long double>(normalized - previous_normalized_magnitudes_[bin]);
181 flux_squared += delta * delta;
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;
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));
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))
201 previous_normalized_magnitudes_.fill(0.0);
203 have_previous_ = next.valid;
205 if (!std::isfinite(next.centroid_hz) || !std::isfinite(next.flatness) ||
206 !std::isfinite(next.rolloff_hz) || !std::isfinite(next.flux))
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_;
216 detail::StreamingAnalysisWindowT<SampleType, MaxFftSize, MaxChannels> window_{};
217 std::array<double, MaxFftSize / 2u + 1u> previous_normalized_magnitudes_{};
219 std::uint64_t sequence_ = 0;
220 bool have_previous_ =
false;