Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
source_filter_analysis.hpp
Go to the documentation of this file.
1#pragma once
2
9
11#include <pulp/signal/fft.hpp>
12
13#include <algorithm>
14#include <cmath>
15#include <complex>
16#include <cstddef>
17#include <cstdint>
18#include <limits>
19#include <new>
20#include <span>
21#include <stdexcept>
22#include <type_traits>
23#include <utility>
24#include <vector>
25
26namespace pulp::signal {
27
49
50inline constexpr int kSourceFilterMinimumFftSize = 256;
51inline constexpr int kSourceFilterMaximumFftSize = 1 << 20;
52inline constexpr int kSourceFilterMaximumLpcOrder = 256;
53inline constexpr int kSourceFilterMaximumEnvelopeIterations = 1024;
54
55inline bool source_filter_fft_size_valid(int fft_size) noexcept {
56 return fft_size >= kSourceFilterMinimumFftSize && fft_size <= kSourceFilterMaximumFftSize &&
57 (fft_size & (fft_size - 1)) == 0;
58}
59
60template <typename SampleType> struct CepstralEnvelopeConfigT {
61 static_assert(std::is_floating_point_v<SampleType>);
62
63 int fft_size = 2048;
64 int order = 128;
68 SampleType convergence_tolerance = SampleType{0};
69};
70
71template <typename SampleType> struct CepstralEnvelopeResultT {
74
75 [[nodiscard]] bool ok() const noexcept {
77 }
78};
79
83template <typename SampleType = float> class CepstralEnvelopeAnalyzerT {
84 static_assert(std::is_floating_point_v<SampleType>);
85
86 public:
89
90 static bool checked_retained_bytes(const Config& config, std::uint64_t target_max_bytes,
91 std::uint64_t& bytes) noexcept {
92 if (!config_valid_(config))
93 return false;
94 const auto bins = static_cast<std::uint64_t>(config.fft_size / 2 + 1);
95 std::uint64_t fft_bytes = 0;
96 CheckedRetainedByteCharge charge(target_max_bytes);
97 if (!charge.add<SampleType>(bins) ||
98 !charge.add<std::complex<SampleType>>(static_cast<std::uint64_t>(config.fft_size)) ||
99 !checked_fft_retained_bytes<SampleType>(static_cast<std::uint64_t>(config.fft_size),
100 target_max_bytes, fft_bytes) ||
101 !charge.add_retained_bytes(fft_bytes))
102 return false;
103 bytes = charge.total();
104 return true;
105 }
106
107 [[nodiscard]] SourceFilterAnalysisStatus prepare(const Config& config) {
108 const auto status = config_status_(config);
109 if (status != SourceFilterAnalysisStatus::Ok) {
110 return status;
111 }
112
113#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
114 try {
115#endif
116 FftT<SampleType> next_fft(config.fft_size);
117 if (!next_fft.ready())
119 std::vector<SampleType> next_smooth(static_cast<std::size_t>(config.fft_size / 2 + 1),
120 SampleType{0});
121 std::vector<std::complex<SampleType>> next_cepstrum(
122 static_cast<std::size_t>(config.fft_size),
123 std::complex<SampleType>{SampleType{0}, SampleType{0}});
124 config_ = config;
125 fft_ = std::move(next_fft);
126 smooth_input_ = std::move(next_smooth);
127 cepstrum_ = std::move(next_cepstrum);
128 prepared_ = true;
130#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
131 } catch (const std::bad_alloc&) {
133 } catch (const std::length_error&) {
135 }
136#endif
137 }
138
139 [[nodiscard]] bool prepared() const noexcept {
140 return prepared_;
141 }
142 [[nodiscard]] int fft_size() const noexcept {
143 return prepared_ ? config_.fft_size : 0;
144 }
145 [[nodiscard]] int num_bins() const noexcept {
146 return prepared_ ? config_.fft_size / 2 + 1 : 0;
147 }
148 [[nodiscard]] int order() const noexcept {
149 return prepared_ ? config_.order : 0;
150 }
151
154 [[nodiscard]] Result estimate(std::span<const SampleType> log_magnitude,
155 std::span<SampleType> log_envelope) noexcept {
156 if (!prepared_)
158 const auto bins = static_cast<std::size_t>(num_bins());
159 if (log_magnitude.size() != bins)
161 if (log_envelope.size() != bins)
163 for (const auto value : log_magnitude) {
164 if (!std::isfinite(value))
166 }
167 if (!input_range_valid_(log_magnitude))
169
170 smooth_(log_magnitude, log_envelope);
171 int iterations = 0;
172 for (; iterations < config_.true_envelope_iterations; ++iterations) {
173 for (std::size_t k = 0; k < bins; ++k)
174 smooth_input_[k] = std::max(log_magnitude[k], log_envelope[k]);
175 smooth_(smooth_input_, log_envelope);
176 if (config_.convergence_tolerance > SampleType{0}) {
177 SampleType residual = SampleType{0};
178 for (std::size_t k = 0; k < bins; ++k)
179 residual = std::max(residual, log_magnitude[k] - log_envelope[k]);
180 if (residual <= config_.convergence_tolerance) {
181 ++iterations;
182 break;
183 }
184 }
185 }
186 return {SourceFilterAnalysisStatus::Ok, iterations};
187 }
188
189 private:
190 static bool config_valid_(const Config& config) noexcept {
191 return config_status_(config) == SourceFilterAnalysisStatus::Ok;
192 }
193
194 static SourceFilterAnalysisStatus config_status_(const Config& config) noexcept {
195 if (!source_filter_fft_size_valid(config.fft_size))
197 if (config.order < 0 || config.order > config.fft_size / 2)
199 if (config.true_envelope_iterations < 0 ||
200 config.true_envelope_iterations > kSourceFilterMaximumEnvelopeIterations)
202 if (!std::isfinite(config.convergence_tolerance) ||
203 config.convergence_tolerance < SampleType{0})
206 }
207
208 bool input_range_valid_(std::span<const SampleType> log_spectrum) const noexcept {
209 SampleType maximum = SampleType{0};
210 for (const auto value : log_spectrum)
211 maximum = std::max(maximum, std::abs(value));
212 // A normalized inverse DFT followed by an unnormalized forward DFT is
213 // an orthogonal projection. Its L2 norm cannot grow; each pointwise-max
214 // refinement adds at most one original-spectrum norm. The extra
215 // N*sqrt(N) and factor four conservatively cover unnormalized butterfly
216 // intermediates and complex components without changing accepted data.
217 const long double size = static_cast<long double>(config_.fft_size);
218 const long double passes = static_cast<long double>(config_.true_envelope_iterations + 2);
219 const long double conservative_fft_growth = 4.0L * size * std::sqrt(size) * passes;
220 const long double limit = static_cast<long double>(std::numeric_limits<SampleType>::max()) /
221 conservative_fft_growth;
222 return static_cast<long double>(maximum) <= limit;
223 }
224
225 void smooth_(std::span<const SampleType> log_spectrum,
226 std::span<SampleType> log_envelope) noexcept {
227 const int n = config_.fft_size;
228 const int bins = n / 2 + 1;
229 const auto* input = log_spectrum.data();
230 auto* output = log_envelope.data();
231 for (int k = 0; k < bins; ++k)
232 cepstrum_[static_cast<std::size_t>(k)] = {input[k], SampleType{0}};
233 for (int k = bins; k < n; ++k)
234 cepstrum_[static_cast<std::size_t>(k)] = {input[n - k], SampleType{0}};
235 fft_.inverse(cepstrum_.data());
236 for (int q = config_.order + 1; q < n - config_.order; ++q)
237 cepstrum_[static_cast<std::size_t>(q)] = {SampleType{0}, SampleType{0}};
238 fft_.forward(cepstrum_.data());
239 for (int k = 0; k < bins; ++k)
240 output[k] = cepstrum_[static_cast<std::size_t>(k)].real();
241 }
242
243 Config config_{};
244 FftT<SampleType> fft_{};
245 std::vector<SampleType> smooth_input_;
246 std::vector<std::complex<SampleType>> cepstrum_;
247 bool prepared_ = false;
248};
249
254
255template <typename SampleType> struct AutocorrelationResultT {
257 SampleType input_scale = SampleType{0};
258
259 [[nodiscard]] bool ok() const noexcept {
261 }
262};
263
267template <typename SampleType>
268[[nodiscard]] AutocorrelationResultT<SampleType>
269scaled_autocorrelation(std::span<const SampleType> samples, int order,
270 std::span<SampleType> autocorrelation) noexcept {
271 static_assert(std::is_floating_point_v<SampleType>);
272 if (order < 0 || order > kSourceFilterMaximumLpcOrder)
273 return {SourceFilterAnalysisStatus::InvalidOrder, SampleType{0}};
274 if (samples.size() <= static_cast<std::size_t>(order))
275 return {SourceFilterAnalysisStatus::InvalidInputSize, SampleType{0}};
276 if (autocorrelation.size() != static_cast<std::size_t>(order + 1))
277 return {SourceFilterAnalysisStatus::InvalidOutputSize, SampleType{0}};
278
279 SampleType scale = SampleType{0};
280 for (const auto sample : samples) {
281 if (!std::isfinite(sample))
282 return {SourceFilterAnalysisStatus::NonFiniteInput, SampleType{0}};
283 scale = std::max(scale, std::abs(sample));
284 }
285 if (!(scale > SampleType{0}))
286 return {SourceFilterAnalysisStatus::DegenerateInput, SampleType{0}};
287
288 const long double promoted_scale = static_cast<long double>(scale);
289 const long double divisor = static_cast<long double>(samples.size());
290 for (int lag = 0; lag <= order; ++lag) {
291 long double sum = 0.0L;
292 const auto count = samples.size() - static_cast<std::size_t>(lag);
293 for (std::size_t i = 0; i < count; ++i) {
294 const long double left = static_cast<long double>(samples[i]) / promoted_scale;
295 const long double right =
296 static_cast<long double>(samples[i + static_cast<std::size_t>(lag)]) /
297 promoted_scale;
298 sum += left * right;
299 }
300 autocorrelation[static_cast<std::size_t>(lag)] = static_cast<SampleType>(sum / divisor);
301 }
302 return {SourceFilterAnalysisStatus::Ok, scale};
303}
304
305template <typename SampleType> struct LevinsonDurbinResultT {
307 SampleType normalized_prediction_error = SampleType{0};
308
309 [[nodiscard]] bool ok() const noexcept {
311 }
312};
313
317template <typename SampleType>
318[[nodiscard]] LevinsonDurbinResultT<SampleType>
319levinson_durbin(std::span<const SampleType> autocorrelation, std::span<SampleType> coefficients,
320 std::span<SampleType> reflection_coefficients, std::span<SampleType> workspace,
321 SampleType rank_tolerance = std::numeric_limits<SampleType>::epsilon() *
322 SampleType{64}) noexcept {
323 static_assert(std::is_floating_point_v<SampleType>);
324 const auto order = coefficients.size();
325 const auto clear_outputs = [&] {
326 std::fill(coefficients.begin(), coefficients.end(), SampleType{0});
327 std::fill(reflection_coefficients.begin(), reflection_coefficients.end(), SampleType{0});
328 };
329 clear_outputs();
330 if (order == 0 || order > static_cast<std::size_t>(kSourceFilterMaximumLpcOrder) ||
331 autocorrelation.size() != order + 1 || reflection_coefficients.size() != order)
332 return {SourceFilterAnalysisStatus::InvalidOrder, SampleType{0}};
333 if (workspace.size() != order)
334 return {SourceFilterAnalysisStatus::InvalidWorkspaceSize, SampleType{0}};
335 if (!std::isfinite(rank_tolerance) || rank_tolerance <= SampleType{0} ||
336 rank_tolerance >= SampleType{1})
337 return {SourceFilterAnalysisStatus::InvalidTolerance, SampleType{0}};
338 for (const auto value : autocorrelation) {
339 if (!std::isfinite(value))
340 return {SourceFilterAnalysisStatus::NonFiniteInput, SampleType{0}};
341 }
342 if (!(autocorrelation[0] > SampleType{0}))
343 return {SourceFilterAnalysisStatus::DegenerateInput, SampleType{0}};
344
345 long double error = static_cast<long double>(autocorrelation[0]);
346 const long double tolerance = static_cast<long double>(rank_tolerance);
347 const long double minimum_error = error * tolerance;
348 const long double sample_max = static_cast<long double>(std::numeric_limits<SampleType>::max());
349 const auto assign_checked = [&](long double value, SampleType& destination) {
350 if (!std::isfinite(value) || std::abs(value) > sample_max)
351 return false;
352 destination = static_cast<SampleType>(value);
353 return std::isfinite(destination);
354 };
355 for (std::size_t stage = 0; stage < order; ++stage) {
356 long double numerator = static_cast<long double>(autocorrelation[stage + 1]);
357 for (std::size_t j = 0; j < stage; ++j)
358 numerator += static_cast<long double>(coefficients[j]) *
359 static_cast<long double>(autocorrelation[stage - j]);
360 const long double reflection_ld = -numerator / error;
361 if (!std::isfinite(reflection_ld)) {
362 clear_outputs();
363 return {SourceFilterAnalysisStatus::RankDeficient, SampleType{0}};
364 }
365 if (std::abs(reflection_ld) >= 1.0L - tolerance) {
366 const auto status = std::abs(reflection_ld) > 1.0L + tolerance
367 ? SourceFilterAnalysisStatus::UnstableModel
368 : SourceFilterAnalysisStatus::RankDeficient;
369 clear_outputs();
370 return {status, SampleType{0}};
371 }
372
373 std::copy(coefficients.begin(), coefficients.end(), workspace.begin());
374 for (std::size_t j = 0; j < stage; ++j) {
375 const long double updated =
376 static_cast<long double>(workspace[j]) +
377 reflection_ld * static_cast<long double>(workspace[stage - 1 - j]);
378 if (!assign_checked(updated, coefficients[j])) {
379 clear_outputs();
380 return {SourceFilterAnalysisStatus::NumericalOverflow, SampleType{0}};
381 }
382 }
383 if (!assign_checked(reflection_ld, coefficients[stage]) ||
384 !assign_checked(reflection_ld, reflection_coefficients[stage])) {
385 clear_outputs();
386 return {SourceFilterAnalysisStatus::NumericalOverflow, SampleType{0}};
387 }
388 error *= 1.0L - reflection_ld * reflection_ld;
389 if (!std::isfinite(error) || error <= minimum_error) {
390 clear_outputs();
391 return {SourceFilterAnalysisStatus::RankDeficient, SampleType{0}};
392 }
393 }
394 if (error > sample_max) {
395 clear_outputs();
396 return {SourceFilterAnalysisStatus::NumericalOverflow, SampleType{0}};
397 }
398 return {SourceFilterAnalysisStatus::Ok, static_cast<SampleType>(error)};
399}
400
401template <typename SampleType> struct LpcAnalysisResultT {
403 int order = 0;
404 SampleType input_scale = SampleType{0};
405 SampleType normalized_prediction_error = SampleType{0};
407 SampleType prediction_error = SampleType{0};
408
409 [[nodiscard]] bool ok() const noexcept {
411 }
412};
413
414template <typename SampleType = float> class LpcAnalyzerT {
415 static_assert(std::is_floating_point_v<SampleType>);
416
417 public:
419
420 static bool checked_retained_bytes(int order, std::uint64_t target_max_bytes,
421 std::uint64_t& bytes) noexcept {
422 if (order < 1 || order > kSourceFilterMaximumLpcOrder)
423 return false;
424 CheckedRetainedByteCharge charge(target_max_bytes);
425 if (!charge.add<SampleType>(static_cast<std::uint64_t>(order + 1)) ||
426 !charge.add_repeated<SampleType>(static_cast<std::uint64_t>(order), 3))
427 return false;
428 bytes = charge.total();
429 return true;
430 }
431
432 [[nodiscard]] SourceFilterAnalysisStatus
433 prepare(int order, SampleType rank_tolerance = std::numeric_limits<SampleType>::epsilon() *
434 SampleType{64}) {
435 if (order < 1 || order > kSourceFilterMaximumLpcOrder) {
437 }
438 if (!std::isfinite(rank_tolerance) || rank_tolerance <= SampleType{0} ||
439 rank_tolerance >= SampleType{1}) {
440 return SourceFilterAnalysisStatus::InvalidTolerance;
441 }
442
443#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
444 try {
445#endif
446 std::vector<SampleType> next_autocorrelation(static_cast<std::size_t>(order + 1),
447 SampleType{0});
448 std::vector<SampleType> next_coefficients(static_cast<std::size_t>(order),
449 SampleType{0});
450 std::vector<SampleType> next_reflections(static_cast<std::size_t>(order),
451 SampleType{0});
452 std::vector<SampleType> next_workspace(static_cast<std::size_t>(order), SampleType{0});
453 order_ = order;
454 rank_tolerance_ = rank_tolerance;
455 autocorrelation_ = std::move(next_autocorrelation);
456 coefficients_ = std::move(next_coefficients);
457 reflections_ = std::move(next_reflections);
458 workspace_ = std::move(next_workspace);
459 prepared_ = true;
460 result_ = {};
461 return SourceFilterAnalysisStatus::Ok;
462#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
463 } catch (const std::bad_alloc&) {
464 return SourceFilterAnalysisStatus::AllocationFailure;
465 } catch (const std::length_error&) {
466 return SourceFilterAnalysisStatus::AllocationFailure;
467 }
468#endif
469 }
470
472 [[nodiscard]] Result analyze(std::span<const SampleType> samples) noexcept {
473 clear_result_();
474 if (!prepared_)
476 if (samples.size() <= static_cast<std::size_t>(order_))
478
479 const auto autocorrelation_result =
480 scaled_autocorrelation<SampleType>(samples, order_, autocorrelation_);
481 if (!autocorrelation_result.ok())
482 return result_ = {autocorrelation_result.status};
483 const auto solver_result = levinson_durbin<SampleType>(
484 autocorrelation_, coefficients_, reflections_, workspace_, rank_tolerance_);
485 if (!solver_result.ok()) {
486 clear_result_();
487 return result_ = {solver_result.status};
488 }
489
490 const long double scale = static_cast<long double>(autocorrelation_result.input_scale);
491 const long double prediction_error =
492 static_cast<long double>(solver_result.normalized_prediction_error) * scale * scale;
493 if (!std::isfinite(prediction_error) || prediction_error < 0.0L ||
494 prediction_error > static_cast<long double>(std::numeric_limits<SampleType>::max())) {
495 clear_result_();
497 }
498 result_ = {SourceFilterAnalysisStatus::Ok, order_, autocorrelation_result.input_scale,
499 solver_result.normalized_prediction_error,
500 static_cast<SampleType>(prediction_error)};
501 return result_;
502 }
503
504 [[nodiscard]] bool prepared() const noexcept {
505 return prepared_;
506 }
507 [[nodiscard]] int order() const noexcept {
508 return prepared_ ? order_ : 0;
509 }
510 [[nodiscard]] Result result() const noexcept {
511 return result_;
512 }
513 [[nodiscard]] std::span<const SampleType> coefficients() const noexcept {
514 return result_.ok() ? std::span<const SampleType>(coefficients_)
515 : std::span<const SampleType>{};
516 }
517 [[nodiscard]] std::span<const SampleType> reflection_coefficients() const noexcept {
518 return result_.ok() ? std::span<const SampleType>(reflections_)
519 : std::span<const SampleType>{};
520 }
521
522 private:
523 void clear_result_() noexcept {
524 std::fill(coefficients_.begin(), coefficients_.end(), SampleType{0});
525 std::fill(reflections_.begin(), reflections_.end(), SampleType{0});
526 result_ = {};
527 }
528
529 int order_ = 0;
530 SampleType rank_tolerance_ = SampleType{0};
531 std::vector<SampleType> autocorrelation_;
532 std::vector<SampleType> coefficients_;
533 std::vector<SampleType> reflections_;
534 std::vector<SampleType> workspace_;
535 Result result_{};
536 bool prepared_ = false;
537};
538
541
546template <typename SampleType>
547[[nodiscard]] SourceFilterAnalysisStatus
548lpc_stability(std::span<const SampleType> coefficients, std::span<SampleType> workspace,
549 SampleType stability_margin = std::numeric_limits<SampleType>::epsilon() *
550 SampleType{64}) noexcept {
551 static_assert(std::is_floating_point_v<SampleType>);
552 const auto order = coefficients.size();
553 if (order == 0 || order > static_cast<std::size_t>(kSourceFilterMaximumLpcOrder))
554 return SourceFilterAnalysisStatus::InvalidOrder;
555 if (workspace.size() != order)
556 return SourceFilterAnalysisStatus::InvalidWorkspaceSize;
557 if (!std::isfinite(stability_margin) || stability_margin <= SampleType{0} ||
558 stability_margin >= SampleType{1})
559 return SourceFilterAnalysisStatus::InvalidTolerance;
560 for (const auto coefficient : coefficients) {
561 if (!std::isfinite(coefficient))
562 return SourceFilterAnalysisStatus::NonFiniteInput;
563 }
564 std::copy(coefficients.begin(), coefficients.end(), workspace.begin());
565 for (std::size_t stage = order; stage > 0; --stage) {
566 const long double reflection = static_cast<long double>(workspace[stage - 1]);
567 const long double margin = static_cast<long double>(stability_margin);
568 if (!std::isfinite(reflection) || std::abs(reflection) >= 1.0L - margin)
569 return SourceFilterAnalysisStatus::UnstableModel;
570 const long double denominator = 1.0L - reflection * reflection;
571 const long double sample_max =
572 static_cast<long double>(std::numeric_limits<SampleType>::max());
573 const auto assign_checked = [&](long double value, SampleType& destination) {
574 if (!std::isfinite(value) || std::abs(value) > sample_max)
575 return false;
576 destination = static_cast<SampleType>(value);
577 return std::isfinite(destination);
578 };
579 const std::size_t remaining = stage - 1;
580 for (std::size_t i = 0; i < remaining / 2; ++i) {
581 const std::size_t j = remaining - 1 - i;
582 const long double left = static_cast<long double>(workspace[i]);
583 const long double right = static_cast<long double>(workspace[j]);
584 if (!assign_checked((left - reflection * right) / denominator, workspace[i]) ||
585 !assign_checked((right - reflection * left) / denominator, workspace[j]))
586 return SourceFilterAnalysisStatus::NumericalOverflow;
587 }
588 if ((remaining & 1u) != 0u) {
589 const std::size_t middle = remaining / 2;
590 if (!assign_checked(static_cast<long double>(workspace[middle]) / (1.0L + reflection),
591 workspace[middle]))
592 return SourceFilterAnalysisStatus::NumericalOverflow;
593 }
594 }
595 return SourceFilterAnalysisStatus::Ok;
596}
597
601template <typename SampleType>
603 std::span<const SampleType> coefficients, SampleType gain, SampleType sample_rate, int fft_size,
604 std::span<SampleType> magnitude, std::span<SampleType> stability_workspace,
605 SampleType stability_margin = std::numeric_limits<SampleType>::epsilon() * SampleType{64},
606 SampleType denominator_floor = std::numeric_limits<SampleType>::epsilon() *
607 SampleType{64}) noexcept {
608 static_assert(std::is_floating_point_v<SampleType>);
609 if (!source_filter_fft_size_valid(fft_size))
610 return SourceFilterAnalysisStatus::InvalidFftSize;
611 if (!std::isfinite(sample_rate) || sample_rate <= SampleType{0})
612 return SourceFilterAnalysisStatus::InvalidSampleRate;
613 if (!std::isfinite(gain) || gain < SampleType{0})
614 return SourceFilterAnalysisStatus::InvalidGain;
615 if (magnitude.size() != static_cast<std::size_t>(fft_size / 2 + 1))
616 return SourceFilterAnalysisStatus::InvalidOutputSize;
617 if (!std::isfinite(denominator_floor) || denominator_floor <= SampleType{0})
618 return SourceFilterAnalysisStatus::InvalidTolerance;
619 const auto stability = lpc_stability(coefficients, stability_workspace, stability_margin);
620 if (stability != SourceFilterAnalysisStatus::Ok)
621 return stability;
622
623 constexpr long double pi = 3.141592653589793238462643383279502884L;
624 const auto bins = static_cast<std::size_t>(fft_size / 2 + 1);
625 const auto response_value = [&](std::size_t bin) {
626 const long double frequency = static_cast<long double>(bin) *
627 static_cast<long double>(sample_rate) /
628 static_cast<long double>(fft_size);
629 const long double omega = 2.0L * pi * frequency / static_cast<long double>(sample_rate);
630 std::complex<long double> denominator{1.0L, 0.0L};
631 for (std::size_t k = 0; k < coefficients.size(); ++k)
632 denominator += static_cast<long double>(coefficients[k]) *
633 std::polar(1.0L, -omega * static_cast<long double>(k + 1));
634 const long double absolute = std::abs(denominator);
635 if (!std::isfinite(absolute) || absolute < static_cast<long double>(denominator_floor))
636 return std::numeric_limits<long double>::infinity();
637 return static_cast<long double>(gain) / absolute;
638 };
639 for (std::size_t bin = 0; bin < bins; ++bin) {
640 const long double value = response_value(bin);
641 if (!std::isfinite(value) || value < 0.0L ||
642 value > static_cast<long double>(std::numeric_limits<SampleType>::max()))
643 return SourceFilterAnalysisStatus::ResponseSingularity;
644 }
645 for (std::size_t bin = 0; bin < bins; ++bin)
646 magnitude[bin] = static_cast<SampleType>(response_value(bin));
647 return SourceFilterAnalysisStatus::Ok;
648}
649
650} // namespace pulp::signal
Log-magnitude spectrum to liftered log-envelope.
Definition source_filter_analysis.hpp:83
SourceFilterAnalysisStatus prepare(const Config &config)
Definition source_filter_analysis.hpp:107
int num_bins() const noexcept
Definition source_filter_analysis.hpp:145
int order() const noexcept
Definition source_filter_analysis.hpp:148
bool prepared() const noexcept
Definition source_filter_analysis.hpp:139
static bool checked_retained_bytes(const Config &config, std::uint64_t target_max_bytes, std::uint64_t &bytes) noexcept
Definition source_filter_analysis.hpp:90
CepstralEnvelopeResultT< SampleType > Result
Definition source_filter_analysis.hpp:88
int fft_size() const noexcept
Definition source_filter_analysis.hpp:142
CepstralEnvelopeConfigT< SampleType > Config
Definition source_filter_analysis.hpp:87
Result estimate(std::span< const SampleType > log_magnitude, std::span< SampleType > log_envelope) noexcept
Rejected input leaves log_envelope unchanged.
Definition source_filter_analysis.hpp:154
Definition checked_allocation.hpp:57
bool add(std::uint64_t elements) noexcept
Definition checked_allocation.hpp:63
std::uint64_t total() const noexcept
Definition checked_allocation.hpp:83
bool add_retained_bytes(std::uint64_t bytes) noexcept
Definition checked_allocation.hpp:78
bool add_repeated(std::uint64_t elements_per_allocation, std::uint64_t repetitions) noexcept
Definition checked_allocation.hpp:70
Definition fft.hpp:176
bool ready() const noexcept
True when every backend resource required by forward()/inverse() is available.
Definition fft.hpp:296
Definition source_filter_analysis.hpp:414
Result result() const noexcept
Definition source_filter_analysis.hpp:510
LpcAnalysisResultT< SampleType > Result
Definition source_filter_analysis.hpp:418
bool prepared() const noexcept
Definition source_filter_analysis.hpp:504
Result analyze(std::span< const SampleType > samples) noexcept
Failure clears the published coefficient, reflection, and error views.
Definition source_filter_analysis.hpp:472
std::span< const SampleType > reflection_coefficients() const noexcept
Definition source_filter_analysis.hpp:517
int order() const noexcept
Definition source_filter_analysis.hpp:507
static bool checked_retained_bytes(int order, std::uint64_t target_max_bytes, std::uint64_t &bytes) noexcept
Definition source_filter_analysis.hpp:420
std::span< const SampleType > coefficients() const noexcept
Definition source_filter_analysis.hpp:513
SourceFilterAnalysisStatus prepare(int order, SampleType rank_tolerance=std::numeric_limits< SampleType >::epsilon() *SampleType{64})
Definition source_filter_analysis.hpp:433
@ right
Definition canvas.hpp:221
@ left
Definition canvas.hpp:221
@ middle
Definition canvas.hpp:230
@ error
Definition validation_harness.hpp:20
@ Config
Set via InspectorConfig / setEditorUrlTemplate.
Definition editor_url.hpp:50
@ size
Definition config.hpp:216
constexpr double pi
Definition fir_design.hpp:164
Definition additive_bank.hpp:218
SourceFilterAnalysisStatus lpc_stability(std::span< const SampleType > coefficients, std::span< SampleType > workspace, SampleType stability_margin=std::numeric_limits< SampleType >::epsilon() *SampleType{64}) noexcept
Explicit Schur step-down stability test for the A(z) coefficient convention used above.
Definition source_filter_analysis.hpp:548
SourceFilterAnalysisStatus
Definition source_filter_analysis.hpp:28
@ InvalidTolerance
Definition source_filter_analysis.hpp:34
@ NonFiniteInput
Definition source_filter_analysis.hpp:39
@ InvalidFftSize
Definition source_filter_analysis.hpp:31
@ InvalidGain
Definition source_filter_analysis.hpp:46
@ InvalidWorkspaceSize
Definition source_filter_analysis.hpp:37
@ NotPrepared
Definition source_filter_analysis.hpp:30
@ InvalidSampleRate
Definition source_filter_analysis.hpp:45
@ InvalidOutputSize
Definition source_filter_analysis.hpp:36
@ DegenerateInput
Definition source_filter_analysis.hpp:41
@ InvalidIterationCount
Definition source_filter_analysis.hpp:33
@ Ok
Definition source_filter_analysis.hpp:29
@ RankDeficient
Definition source_filter_analysis.hpp:42
@ InvalidOrder
Definition source_filter_analysis.hpp:32
@ UnstableModel
Definition source_filter_analysis.hpp:43
@ PredictionErrorOverflow
Definition source_filter_analysis.hpp:44
@ NumericalOverflow
Definition source_filter_analysis.hpp:40
@ AllocationFailure
Definition source_filter_analysis.hpp:38
@ ResponseSingularity
Definition source_filter_analysis.hpp:47
@ InvalidInputSize
Definition source_filter_analysis.hpp:35
bool checked_fft_retained_bytes(std::uint64_t fft_size, std::uint64_t target_max_bytes, std::uint64_t &bytes) noexcept
Complete retained-storage contract for one prepared FftT.
Definition fft.hpp:133
CepstralEnvelopeAnalyzerT< double > CepstralEnvelopeAnalyzer64
Definition source_filter_analysis.hpp:253
constexpr int kSourceFilterMaximumEnvelopeIterations
Definition source_filter_analysis.hpp:53
SourceFilterAnalysisStatus all_pole_magnitude_response(std::span< const SampleType > coefficients, SampleType gain, SampleType sample_rate, int fft_size, std::span< SampleType > magnitude, std::span< SampleType > stability_workspace, SampleType stability_margin=std::numeric_limits< SampleType >::epsilon() *SampleType{64}, SampleType denominator_floor=std::numeric_limits< SampleType >::epsilon() *SampleType{64}) noexcept
Evaluates gain / |A(e^jw)| at fft_size / 2 + 1 bins from DC through Nyquist.
Definition source_filter_analysis.hpp:602
CepstralEnvelopeAnalyzerT< float > CepstralEnvelopeAnalyzer
Definition source_filter_analysis.hpp:252
constexpr int kSourceFilterMaximumLpcOrder
Definition source_filter_analysis.hpp:52
LpcAnalyzerT< double > LpcAnalyzer64
Definition source_filter_analysis.hpp:540
CepstralEnvelopeConfigT< double > CepstralEnvelopeConfig64
Definition source_filter_analysis.hpp:251
CepstralEnvelopeConfigT< float > CepstralEnvelopeConfig
Definition source_filter_analysis.hpp:250
@ maximum
Definition spectrum_trace.hpp:34
@ i
Definition formant_filter_bank.hpp:20
bool source_filter_fft_size_valid(int fft_size) noexcept
Definition source_filter_analysis.hpp:55
constexpr int kSourceFilterMinimumFftSize
Definition source_filter_analysis.hpp:50
LevinsonDurbinResultT< SampleType > levinson_durbin(std::span< const SampleType > autocorrelation, std::span< SampleType > coefficients, std::span< SampleType > reflection_coefficients, std::span< SampleType > workspace, SampleType rank_tolerance=std::numeric_limits< SampleType >::epsilon() *SampleType{64}) noexcept
Solves A(z) = 1 + sum(a[k] z^-(k+1)).
Definition source_filter_analysis.hpp:319
LpcAnalyzerT< float > LpcAnalyzer
Definition source_filter_analysis.hpp:539
AutocorrelationResultT< SampleType > scaled_autocorrelation(std::span< const SampleType > samples, int order, std::span< SampleType > autocorrelation) noexcept
Biased autocorrelation of a safely normalized signal.
Definition source_filter_analysis.hpp:269
constexpr int kSourceFilterMaximumFftSize
Definition source_filter_analysis.hpp:51
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
@ j
Definition input_events.hpp:40
@ q
Definition input_events.hpp:41
@ n
Definition input_events.hpp:40
@ k
Definition input_events.hpp:40
Definition source_filter_analysis.hpp:255
SourceFilterAnalysisStatus status
Definition source_filter_analysis.hpp:256
bool ok() const noexcept
Definition source_filter_analysis.hpp:259
SampleType input_scale
Definition source_filter_analysis.hpp:257
Definition source_filter_analysis.hpp:60
int true_envelope_iterations
Definition source_filter_analysis.hpp:65
int fft_size
Definition source_filter_analysis.hpp:63
float convergence_tolerance
Definition source_filter_analysis.hpp:68
int order
Definition source_filter_analysis.hpp:64
Definition source_filter_analysis.hpp:71
int iterations_performed
Definition source_filter_analysis.hpp:73
bool ok() const noexcept
Definition source_filter_analysis.hpp:75
SourceFilterAnalysisStatus status
Definition source_filter_analysis.hpp:72
Definition source_filter_analysis.hpp:305
SourceFilterAnalysisStatus status
Definition source_filter_analysis.hpp:306
SampleType normalized_prediction_error
Definition source_filter_analysis.hpp:307
bool ok() const noexcept
Definition source_filter_analysis.hpp:309
Definition source_filter_analysis.hpp:401
SampleType normalized_prediction_error
Definition source_filter_analysis.hpp:405
SampleType input_scale
Definition source_filter_analysis.hpp:404
bool ok() const noexcept
Definition source_filter_analysis.hpp:409
SampleType prediction_error
Prediction-error power in the original sample-squared units.
Definition source_filter_analysis.hpp:407
SourceFilterAnalysisStatus status
Definition source_filter_analysis.hpp:402
int order
Definition source_filter_analysis.hpp:403