27 if (!std::isfinite(previous) || !std::isfinite(current))
28 return std::numeric_limits<double>::quiet_NaN();
29 return std::max(0.0, current - previous);
33 std::size_t bin)
noexcept {
34 if (!std::isfinite(previous) || !std::isfinite(current))
35 return std::numeric_limits<double>::quiet_NaN();
36 const auto positive = std::max(0.0, current - previous);
38 ?
positive *
static_cast<double>(bin + 1u)
54template <
typename SampleType, std::
size_t MaxFftSize, std::
size_t MaxChannels>
56 static_assert(std::is_floating_point_v<SampleType>);
57 static_assert(MaxFftSize >= 2);
58 static_assert(MaxChannels > 0);
62 bool require_fft =
true) {
63 if (!valid_config(
config, require_fft))
67 std::optional<FftT<SampleType>> fft;
72 fft.emplace(
static_cast<int>(
config.fft_size));
76 std::array<double, MaxFftSize> coefficients{};
77 for (std::size_t
i = 0;
i <
config.fft_size; ++
i) {
79 constexpr double two_pi = 6.28318530717958647692;
81 two_pi *
static_cast<double>(
i) /
static_cast<double>(
config.fft_size - 1u);
82 coefficients[
i] = 0.5 - 0.5 * std::cos(phase);
84 coefficients[
i] = 1.0;
90 fft_ = std::move(fft);
91 window_ = coefficients;
98 mono_ring_.fill(SampleType{0});
99 power_ring_.fill(0.0);
100 time_frame_.fill(SampleType{0});
104 until_next_frame_ = 0;
109 mono_ring_.fill(SampleType{0});
110 power_ring_.fill(0.0);
111 time_frame_.fill(SampleType{0});
115 until_next_frame_ = 0;
126 [[nodiscard]]
bool push(
double mono,
double channel_power)
noexcept {
127 mono_ring_[write_] = mono;
128 power_ring_[write_] = channel_power;
129 write_ = (write_ + 1u) % config_.fft_size;
132 if (filled_ < config_.fft_size) {
134 if (filled_ == config_.fft_size) {
135 until_next_frame_ = config_.hop_size;
141 if (--until_next_frame_ == 0) {
142 until_next_frame_ = config_.hop_size;
157 long double sum = 0.0L;
158 const auto divisor =
static_cast<long double>(config_.fft_size);
159 for (std::size_t
i = 0;
i < config_.fft_size; ++
i) {
160 const auto source = (write_ +
i) % config_.fft_size;
163 sum +=
static_cast<long double>(power_ring_[source]) / divisor;
165 return static_cast<double>(sum);
170 [[nodiscard]]
const std::array<std::complex<SampleType>, MaxFftSize>&
178 return stream_frames_;
184 return retained_fft_bytes_;
192 if (normalize_gain) {
193 for (std::size_t
i = 0;
i < config_.
fft_size; ++
i) {
194 const auto source = (write_ +
i) % config_.
fft_size;
195 const auto windowed = mono_ring_[source] * window_[
i];
196 if (!std::isfinite(windowed))
201 for (std::size_t
i = 0;
i < config_.
fft_size; ++
i) {
202 const auto source = (write_ +
i) % config_.
fft_size;
203 const auto windowed = mono_ring_[source] * window_[
i];
204 time_frame_[
i] =
static_cast<SampleType
>(
maximum > 0.0 ? windowed /
maximum : windowed);
205 if (!std::isfinite(time_frame_[
i]))
208 fft_->forward_real_half(time_frame_.data(), spectrum_.data());
209 for (std::size_t bin = 0; bin <= config_.
fft_size / 2u; ++bin) {
210 if (!std::isfinite(spectrum_[bin].real()) || !std::isfinite(spectrum_[bin].imag()))
215 static bool valid_config(
const StreamingAnalysisConfig&
config,
bool require_fft)
noexcept {
216 return std::isfinite(
config.sample_rate) &&
config.sample_rate > 0.0 &&
218 config.fft_size <= MaxFftSize &&
219 (!require_fft || (
config.fft_size & (
config.fft_size - 1u)) == 0) &&
220 config.hop_size > 0 && (!require_fft ||
config.max_retained_fft_bytes > 0);
223 StreamingAnalysisConfig config_{};
224 std::optional<FftT<SampleType>> fft_{};
225 std::array<double, MaxFftSize> mono_ring_{};
226 std::array<double, MaxFftSize> power_ring_{};
227 std::array<double, MaxFftSize> window_{};
228 std::array<SampleType, MaxFftSize> time_frame_{};
229 std::array<std::complex<SampleType>, MaxFftSize> spectrum_{};
230 std::size_t write_ = 0;
231 std::size_t filled_ = 0;
232 std::uint64_t until_next_frame_ = 0;
233 std::uint64_t stream_frames_ = 0;
234 std::uint64_t retained_fft_bytes_ = 0;
235 bool prepared_ =
false;
238template <
typename SampleType, std::
size_t MaxChannels>
240 std::size_t frame,
bool compute_power,
double& mono,
241 double& channel_power)
noexcept {
242 if (channels ==
nullptr || channel_count == 0 || channel_count > MaxChannels)
245 for (std::size_t channel = 0; channel < channel_count; ++channel) {
246 if (channels[channel] ==
nullptr)
248 const auto sample =
static_cast<double>(channels[channel][frame]);
249 if (!std::isfinite(sample))
251 scale = std::max(scale, std::abs(sample));
260 double scaled_sum = 0.0;
261 double scaled_power = 0.0;
262 for (std::size_t channel = 0; channel < channel_count; ++channel) {
263 const auto scaled =
static_cast<double>(channels[channel][frame]) / scale;
264 scaled_sum += scaled;
266 scaled_power += scaled * scaled;
268 const auto divisor =
static_cast<double>(channel_count);
269 const auto mixed = scale * (scaled_sum / divisor);
270 if (!std::isfinite(mixed) ||
271 std::abs(mixed) >
static_cast<double>(std::numeric_limits<SampleType>::max()))
276 if (scale > std::sqrt(std::numeric_limits<double>::max()))
278 channel_power = (scale * scale) * (scaled_power / divisor);
279 if (!std::isfinite(channel_power))
286 if (!(frequency_hz > 0.0) || !std::isfinite(frequency_hz))
288 const auto midi = 69.0 + 12.0 * std::log2(frequency_hz / 440.0);
289 if (!std::isfinite(
midi))
291 auto pitch_class =
static_cast<int>(std::lround(
midi)) % 12;
320template <
typename SampleType =
float, std::
size_t MaxFftSize = 4096, std::
size_t MaxChannels = 8>
322 static_assert(std::is_floating_point_v<SampleType>);
323 static_assert(MaxFftSize >= 2);
324 static_assert(MaxChannels > 0);
343 template <
typename Sink>
344 [[nodiscard]]
bool process(
const SampleType*
const* channels, std::size_t channel_count,
345 std::size_t frames, Sink&& sink)
noexcept {
346 static_assert(std::is_nothrow_invocable_v<Sink&, const Frame&>,
347 "analysis sinks must be noexcept");
348 if (!window_.prepared() || channel_count != window_.config().channels ||
351 for (std::size_t channel = 0; channel < channel_count; ++channel) {
352 if (channels[channel] ==
nullptr)
357 for (std::size_t frame = 0; frame < frames; ++frame) {
361 false, mono, power)) {
362 window_.reject_sample();
366 if (!window_.push(mono, power))
368 if (!render_frame()) {
369 window_.clear_history_after_fault();
382 return window_.prepared() ? window_.config().fft_size / 2u : 0
u;
385 return window_.prepared() ? window_.config().fft_size : 0
u;
388 return window_.retained_fft_bytes();
392 [[nodiscard]]
bool render_frame() noexcept {
393 if (!window_.render_spectrum())
396 const auto& config = window_.config();
397 const auto& spectrum = window_.spectrum();
398 for (std::size_t bin = 1; bin < config.fft_size / 2u; ++bin) {
399 const auto magnitude =
static_cast<double>(std::abs(spectrum[bin]));
400 const auto left =
static_cast<double>(std::abs(spectrum[bin - 1u]));
401 const auto right =
static_cast<double>(std::abs(spectrum[bin + 1u]));
402 if (!std::isfinite(magnitude) || !std::isfinite(
left) || !std::isfinite(
right))
404 if (magnitude <
left || magnitude <
right)
406 const auto normalized_bin =
407 static_cast<double>(bin) /
static_cast<double>(config.fft_size);
408 const auto frequency = config.sample_rate * normalized_bin;
409 if (frequency < 55.0 || frequency > 5000.0)
412 if (pitch_class >= 0)
413 next.magnitude[
static_cast<std::size_t
>(pitch_class)] += magnitude;
415 long double total = 0.0L;
416 for (
const auto value : next.magnitude)
417 total +=
static_cast<long double>(value);
418 if (!std::isfinite(total))
421 for (std::size_t pc = 0; pc < 12; ++pc)
422 next.normalized[pc] =
423 static_cast<SampleType
>(
static_cast<long double>(next.magnitude[pc]) / total);
426 next.ready_at_frame = window_.stream_frames() - 1u;
427 next.window_start_frame = window_.stream_frames() -
config.fft_size;
428 next.window_center_frame = next.window_start_frame +
config.fft_size / 2u;
429 next.sequence = ++sequence_;
434 detail::StreamingAnalysisWindowT<SampleType, MaxFftSize, MaxChannels> window_{};
436 std::uint64_t sequence_ = 0;
454template <
typename SampleType =
float, std::
size_t MaxFftSize = 4096, std::
size_t MaxChannels = 8>
456 static_assert(std::is_floating_point_v<SampleType>);
457 static_assert(MaxFftSize >= 2);
458 static_assert(MaxChannels > 0);
472 previous_magnitudes_.fill(0.0);
473 previous_energy_ = 0.0;
474 have_previous_ =
false;
482 previous_magnitudes_.fill(0.0);
483 previous_energy_ = 0.0;
484 have_previous_ =
false;
489 template <
typename Sink>
490 [[nodiscard]]
bool process(
const SampleType*
const* channels, std::size_t channel_count,
491 std::size_t frames, Sink&& sink)
noexcept {
492 static_assert(std::is_nothrow_invocable_v<Sink&, const Frame&>,
493 "analysis sinks must be noexcept");
494 if (!window_.prepared() || channel_count != window_.config().channels ||
497 for (std::size_t channel = 0; channel < channel_count; ++channel) {
498 if (channels[channel] ==
nullptr)
503 for (std::size_t frame = 0; frame < frames; ++frame) {
513 if (!window_.push(mono, power))
515 if (!render_frame()) {
516 clear_history_after_fault();
529 return window_.prepared() ? window_.config().fft_size - 1u : 0
u;
532 return window_.retained_fft_bytes();
536 void clear_history_after_fault() noexcept {
537 window_.clear_history_after_fault();
538 previous_magnitudes_.fill(0.0);
539 previous_energy_ = 0.0;
540 have_previous_ =
false;
543 void reject_sample() noexcept {
544 window_.reject_sample();
545 previous_magnitudes_.fill(0.0);
546 previous_energy_ = 0.0;
547 have_previous_ =
false;
550 [[nodiscard]]
bool render_frame() noexcept {
551 double novelty = 0.0;
553 const auto energy = window_.frame_energy();
554 if (!std::isfinite(
energy))
558 previous_energy_ =
energy;
560 if (!window_.render_spectrum())
562 const auto&
spectrum = window_.spectrum();
563 const auto bins = window_.config().fft_size / 2u + 1u;
564 for (std::size_t bin = 0; bin < bins; ++bin) {
565 const auto magnitude =
static_cast<double>(std::abs(spectrum[bin]));
566 if (!std::isfinite(magnitude))
568 if (have_previous_) {
572 previous_magnitudes_[bin] = magnitude;
575 if (!std::isfinite(novelty))
577 have_previous_ =
true;
579 const auto&
config = window_.config();
581 next.novelty = novelty;
582 next.ready_at_frame = window_.stream_frames() - 1u;
583 next.window_start_frame = window_.stream_frames() -
config.fft_size;
584 next.window_center_frame = next.window_start_frame +
config.fft_size / 2u;
585 next.sequence = ++sequence_;
586 next.method = method_;
591 detail::StreamingAnalysisWindowT<SampleType, MaxFftSize, MaxChannels> window_{};
592 std::array<double, MaxFftSize / 2u + 1u> previous_magnitudes_{};
593 double previous_energy_ = 0.0;
596 std::uint64_t sequence_ = 0;
597 bool have_previous_ =
false;
Fixed-capacity chroma analysis over caller-owned planar input.
Definition analysis_frontends.hpp:321
std::uint64_t retained_fft_bytes() const noexcept
Definition analysis_frontends.hpp:387
std::uint32_t algorithmic_latency_samples() const noexcept
Definition analysis_frontends.hpp:381
bool prepare(const StreamingAnalysisConfig &config)
Definition analysis_frontends.hpp:329
std::uint32_t startup_samples() const noexcept
Definition analysis_frontends.hpp:384
const Frame & current() const noexcept
Definition analysis_frontends.hpp:378
ChromaFrameT< SampleType > Frame
Definition analysis_frontends.hpp:327
void reset() noexcept
Definition analysis_frontends.hpp:337
bool process(const SampleType *const *channels, std::size_t channel_count, std::size_t frames, Sink &&sink) noexcept
Definition analysis_frontends.hpp:344
Fixed-capacity streaming novelty analysis.
Definition analysis_frontends.hpp:455
std::uint32_t readiness_latency_samples() const noexcept
Definition analysis_frontends.hpp:528
OnsetNoveltyFrameT< SampleType > Frame
Definition analysis_frontends.hpp:461
bool prepare(const StreamingAnalysisConfig &config, OnsetDetectionMethod method)
Definition analysis_frontends.hpp:463
bool process(const SampleType *const *channels, std::size_t channel_count, std::size_t frames, Sink &&sink) noexcept
Definition analysis_frontends.hpp:490
std::uint64_t retained_fft_bytes() const noexcept
Definition analysis_frontends.hpp:531
const Frame & current() const noexcept
Definition analysis_frontends.hpp:525
void reset() noexcept
Definition analysis_frontends.hpp:480
Definition analysis_frontends.hpp:55
double frame_energy() const noexcept
Definition analysis_frontends.hpp:156
void reject_sample() noexcept
Definition analysis_frontends.hpp:121
bool render_spectrum() noexcept
Definition analysis_frontends.hpp:148
void clear_history_after_fault() noexcept
Definition analysis_frontends.hpp:108
void reset() noexcept
Definition analysis_frontends.hpp:97
bool prepared() const noexcept
Definition analysis_frontends.hpp:180
bool render_gain_normalized_spectrum() noexcept
Definition analysis_frontends.hpp:152
std::uint64_t stream_frames() const noexcept
Definition analysis_frontends.hpp:177
bool push(double mono, double channel_power) noexcept
Definition analysis_frontends.hpp:126
const StreamingAnalysisConfig & config() const noexcept
Definition analysis_frontends.hpp:174
const std::array< std::complex< SampleType >, MaxFftSize > & spectrum() const noexcept
Latest rendered spectrum.
Definition analysis_frontends.hpp:171
std::uint64_t retained_fft_bytes() const noexcept
Definition analysis_frontends.hpp:183
bool prepare(const StreamingAnalysisConfig &config, AnalysisWindow window, bool require_fft=true)
Definition analysis_frontends.hpp:61
Definition custom_node_events.hpp:26
Round-2 effect-wave vocabulary.
Definition analysis_frontends.hpp:50
AnalysisWindow
Definition analysis_frontends.hpp:52
@ symmetric_hann
Definition analysis_frontends.hpp:52
@ rectangular
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
int pitch_class_for_frequency(double frequency_hz) noexcept
Definition analysis_frontends.hpp:285
Definition additive_bank.hpp:218
ChromaFrontEndT< double > ChromaFrontEnd64
Definition analysis_frontends.hpp:601
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
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
@ energy
sum h^2 = 1 per output ear (default; convolution reverb).
Definition zero_latency_convolver_support.hpp:160
double onset_energy_flux(double previous, double current) noexcept
Pure novelty transitions shared by fixed-capacity streaming analysis and background/offline adapters.
Definition analysis_frontends.hpp:26
@ maximum
Definition spectrum_trace.hpp:34
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
OnsetNoveltyFrontEndT< float > OnsetNoveltyFrontEnd
Definition analysis_frontends.hpp:602
OnsetDetectionMethod
Definition analysis_frontends.hpp:17
@ HighFrequencyContent
Definition analysis_frontends.hpp:20
@ SpectralFlux
Definition analysis_frontends.hpp:19
@ EnergyFlux
Definition analysis_frontends.hpp:18
ChromaFrontEndT< float > ChromaFrontEnd
Definition analysis_frontends.hpp:600
double onset_spectral_bin_flux(OnsetDetectionMethod method, double previous, double current, std::size_t bin) noexcept
Definition analysis_frontends.hpp:32
@ positive
y = dry + wet. Notches at (2k+1)/(2D).
Definition flanger.hpp:148
OnsetNoveltyFrontEndT< double > OnsetNoveltyFrontEnd64
Definition analysis_frontends.hpp:603
@ spectrum
Definition design_ir.hpp:265
One complete chroma analysis window.
Definition analysis_frontends.hpp:302
std::array< double, 12 > magnitude
Definition analysis_frontends.hpp:303
std::uint64_t window_center_frame
Definition analysis_frontends.hpp:306
std::uint64_t ready_at_frame
Definition analysis_frontends.hpp:307
std::uint64_t sequence
Definition analysis_frontends.hpp:308
std::array< SampleType, 12 > normalized
Definition analysis_frontends.hpp:304
std::uint64_t window_start_frame
Definition analysis_frontends.hpp:305
bool valid
Definition analysis_frontends.hpp:309
One complete novelty window.
Definition analysis_frontends.hpp:442
std::uint64_t sequence
Definition analysis_frontends.hpp:447
std::uint64_t window_center_frame
Definition analysis_frontends.hpp:445
double novelty
Definition analysis_frontends.hpp:443
std::uint64_t ready_at_frame
Definition analysis_frontends.hpp:446
std::uint64_t window_start_frame
Definition analysis_frontends.hpp:444
OnsetDetectionMethod method
Definition analysis_frontends.hpp:448
Definition analysis_frontends.hpp:42
double sample_rate
Definition analysis_frontends.hpp:43
std::size_t channels
Definition analysis_frontends.hpp:44
std::uint32_t fft_size
Definition analysis_frontends.hpp:45
std::uint64_t max_retained_fft_bytes
Definition analysis_frontends.hpp:47
std::uint64_t hop_size
Definition analysis_frontends.hpp:46