65template <
typename SampleType>
66inline std::optional<SpectralDelayMatrixGeometry<SampleType>>
70 static_assert(std::is_floating_point_v<SampleType>);
71 if (!std::isfinite(config.sample_rate) || config.sample_rate <= 0.0 ||
72 !std::isfinite(config.max_delay_ms) || config.max_delay_ms < 0.0 ||
73 config.max_delay_ms > 60000.0 ||
82 const double frames_exact =
83 config.max_delay_ms * config.sample_rate / (1000.0 * config.frame.analysis_hop);
84 if (!std::isfinite(frames_exact) ||
85 frames_exact >
static_cast<double>(std::numeric_limits<int>::max() - 1))
87 const auto history_frames =
static_cast<std::uint64_t
>(std::floor(frames_exact)) + 1u;
88 std::uint64_t channel_frames = 0;
89 std::uint64_t history_elements = 0;
91 UINT64_MAX, channel_frames) ||
93 UINT64_MAX, history_elements))
98 !charge.
add<std::complex<SampleType>>(history_elements) ||
99 !charge.
add_repeated<std::int32_t>(
static_cast<std::uint64_t
>(engine->num_bins), 3) ||
100 !charge.
add_repeated<
float>(
static_cast<std::uint64_t
>(engine->num_bins), 3))
102 const auto retained = charge.
total();
103 if (retained > config.max_retained_bytes)
106 static_cast<int>(history_frames), retained};
110 int num_bins,
int analysis_hop,
double sample_rate,
111 int maximum_delay_frames,
114 analysis_hop <= 0 || maximum_delay_frames < 0 || !std::isfinite(sample_rate) ||
119 if (!std::isfinite(point.freq01) || !std::isfinite(point.delay_ms) ||
120 !std::isfinite(point.attenuation_db) || point.freq01 < 0.0f || point.freq01 > 1.0f ||
125 const double maximum_delay_ms =
126 static_cast<double>(maximum_delay_frames) * 1000.0 * analysis_hop / sample_rate;
128 candidate.version = recipe.
version;
129 candidate.num_bins = num_bins;
130 candidate.delay_frames.resize(
static_cast<std::size_t
>(num_bins));
131 candidate.attenuation_linear.resize(
static_cast<std::size_t
>(num_bins));
132 for (
int bin = 0; bin < num_bins; ++bin) {
133 const float freq01 =
static_cast<float>(bin) /
static_cast<float>(num_bins - 1);
134 auto upper = std::upper_bound(
148 lo == hi ? 0.0 : (freq01 - lo->
freq01) /
static_cast<double>(hi->
freq01 - lo->
freq01);
149 const double delay_ms =
151 const double attenuation_db = std::clamp(
153 const auto delay = std::llround(delay_ms * sample_rate / (1000.0 * analysis_hop));
154 candidate.delay_frames[
static_cast<std::size_t
>(bin)] =
155 static_cast<std::int32_t
>(std::clamp<long long>(
delay, 0, maximum_delay_frames));
156 candidate.attenuation_linear[
static_cast<std::size_t
>(bin)] =
157 static_cast<float>(std::pow(10.0, attenuation_db / 20.0));
159 out_table = std::move(candidate);
165 static_assert(std::is_floating_point_v<SampleType>);
171 auto candidate = std::make_unique<PreparedState>();
172 candidate->config = config;
173 candidate->geometry = *geometry;
174 candidate->engine.prepare(config.frame);
175 candidate->history.assign(
static_cast<std::size_t
>(config.frame.channels) *
176 static_cast<std::size_t
>(geometry->history_frames) *
177 static_cast<std::size_t
>(geometry->num_bins),
180 identity.
num_bins = geometry->num_bins;
181 identity.
delay_frames.assign(
static_cast<std::size_t
>(geometry->num_bins), 0);
182 identity.
attenuation_linear.assign(
static_cast<std::size_t
>(geometry->num_bins), 1.0f);
183 candidate->publication =
184 std::make_unique<runtime::TripleBuffer<PublishedTable>>(PublishedTable{identity, 0});
185 state_ = std::move(candidate);
186 next_generation_ = 1;
191 int analysis_hop,
double sample_rate,
int maximum_delay_frames,
194 maximum_delay_frames, out_table);
198 if (!state_ || !valid_table(table))
200 const auto generation = next_generation_++;
201 if (next_generation_ == 0)
203 state_->publication->write_with([&](PublishedTable& payload) {
210 payload.generation = generation;
212 state_->published_generation.write(generation);
216 void process(
const SampleType*
const* input, SampleType*
const* output,
217 int num_samples)
noexcept {
218 if (!state_ || num_samples <= 0)
220 state_->engine.process(input, output, num_samples,
221 [
this](std::complex<SampleType>*
const* frames,
int bins)
noexcept {
222 process_frame(frames, bins);
230 std::fill(state_->history.begin(), state_->history.end(), std::complex<SampleType>{});
231 state_->frames_seen = 0;
232 state_->write_frame = 0;
239 state_->engine.reset();
244 return state_ ? state_->engine.latency_samples() : 0;
247 return state_ ? state_->geometry.history_frames - 1 : 0;
250 return state_ ? state_->geometry.num_bins : 0;
253 return state_ ? state_->geometry.retained_bytes : 0;
258 state_->config.frame.analysis_hop
262 return state_ ? state_->active_version.read() : 0;
265 return state_ && state_->published_generation.read() != state_->active_generation.read();
269 struct PublishedTable {
271 std::uint64_t generation = 0;
273 struct PreparedState {
274 SpectralDelayMatrixConfig config{};
275 SpectralDelayMatrixGeometry<SampleType> geometry{};
276 SpectralFrameEngineT<SampleType> engine;
277 std::vector<std::complex<SampleType>> history;
278 std::unique_ptr<runtime::TripleBuffer<PublishedTable>> publication;
279 runtime::SeqLock<std::uint64_t> published_generation;
280 runtime::SeqLock<std::uint64_t> active_generation;
281 runtime::SeqLock<std::uint64_t> active_version;
282 std::uint64_t consumed_generation = 0;
283 std::uint64_t frames_seen = 0;
287 bool valid_table(
const SpectralDelayMatrixTable& table)
const noexcept {
288 const auto bins =
static_cast<std::size_t
>(state_->geometry.num_bins);
289 if (table.num_bins != state_->geometry.num_bins || table.delay_frames.size() != bins ||
290 table.attenuation_linear.size() != bins)
292 for (std::size_t
i = 0;
i < bins; ++
i)
294 !std::isfinite(table.attenuation_linear[
i]) || table.attenuation_linear[
i] < 0.0f)
298 std::size_t offset(
int channel,
int frame,
int bin)
const noexcept {
299 return (
static_cast<std::size_t
>(channel) * state_->geometry.history_frames +
300 static_cast<std::size_t
>(frame)) *
301 state_->geometry.num_bins +
302 static_cast<std::size_t
>(bin);
304 void process_frame(std::complex<SampleType>*
const* frames,
int bins)
noexcept {
305 const auto& publication = state_->publication->read();
306 if (state_->consumed_generation != publication.generation) {
307 state_->consumed_generation = publication.generation;
308 state_->active_generation.write(publication.generation);
309 state_->active_version.write(publication.table.version);
311 const auto& table = publication.table;
312 const int history_frames = state_->geometry.history_frames;
313 for (
int channel = 0; channel < state_->config.frame.channels; ++channel) {
314 for (
int bin = 0; bin < bins; ++bin) {
315 state_->history[offset(channel, state_->write_frame, bin)] = frames[channel][bin];
316 const int delay = table.delay_frames[
static_cast<std::size_t
>(bin)];
317 if (
static_cast<std::uint64_t
>(
delay) > state_->frames_seen) {
318 frames[channel][bin] = {};
321 (state_->write_frame -
delay + history_frames) % history_frames;
322 frames[channel][bin] =
323 state_->history[offset(channel, read, bin)] *
324 static_cast<SampleType
>(
325 table.attenuation_linear[
static_cast<std::size_t
>(bin)]);
329 state_->write_frame = (state_->write_frame + 1) % history_frames;
330 ++state_->frames_seen;
333 std::unique_ptr<PreparedState> state_;
334 std::uint64_t next_generation_ = 1;
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 spectral_delay_matrix.hpp:163
static bool compile_table(const SpectralDelayMatrixRecipe &recipe, int num_bins, int analysis_hop, double sample_rate, int maximum_delay_frames, SpectralDelayMatrixTable &out_table)
Definition spectral_delay_matrix.hpp:190
int num_bins() const noexcept
Definition spectral_delay_matrix.hpp:249
void reset() noexcept
Clear delayed content and the underlying analysis/synthesis pipeline.
Definition spectral_delay_matrix.hpp:235
int max_delay_frames() const noexcept
Definition spectral_delay_matrix.hpp:246
std::uint64_t retained_bytes() const noexcept
Definition spectral_delay_matrix.hpp:252
bool publish_table(const SpectralDelayMatrixTable &table)
Definition spectral_delay_matrix.hpp:197
int latency_samples() const noexcept
Fixed engine latency only; per-bin delays are declared content timing.
Definition spectral_delay_matrix.hpp:243
void purge() noexcept
Clear only delayed spectral content; preserve the engine pipeline and table.
Definition spectral_delay_matrix.hpp:227
void process(const SampleType *const *input, SampleType *const *output, int num_samples) noexcept
Definition spectral_delay_matrix.hpp:216
std::uint64_t maximum_tail_samples() const noexcept
Definition spectral_delay_matrix.hpp:255
std::uint64_t active_table_version() const noexcept
Definition spectral_delay_matrix.hpp:261
bool table_publication_pending() const noexcept
Definition spectral_delay_matrix.hpp:264
bool prepare(const SpectralDelayMatrixConfig &config)
Definition spectral_delay_matrix.hpp:167
std::uint64_t read(std::span< const std::uint8_t > b, std::size_t p, unsigned n) noexcept
Definition spectral_frame_codec.hpp:111
Definition additive_bank.hpp:218
std::optional< SpectralFrameEngineGeometry > checked_spectral_frame_engine_geometry(const SpectralFrameEngineConfig &config, std::uint64_t target_max_bytes=kTargetAddressMaximumBytes) noexcept
Derive every integer and typed backing-store capacity used by prepare().
Definition spectral_frame_engine.hpp:151
constexpr std::size_t kSpectralDelayMinimumRetainedBytes
Definition spectral_delay_matrix.hpp:31
SpectralDelayMatrixT< double > SpectralDelayMatrix64
Definition spectral_delay_matrix.hpp:338
@ delay
Definition envelope.hpp:57
std::optional< SpectralDelayMatrixGeometry< SampleType > > checked_spectral_delay_matrix_geometry(const SpectralDelayMatrixConfig &config, std::uint64_t target_max_bytes=kTargetAddressMaximumBytes) noexcept
Definition spectral_delay_matrix.hpp:67
bool checked_capacity_product(std::uint64_t lhs, std::uint64_t rhs, std::uint64_t maximum, std::uint64_t &product) noexcept
Definition checked_allocation.hpp:12
constexpr std::size_t kSpectralDelayMaximumRetainedBytes
Definition spectral_delay_matrix.hpp:32
SpectralDelayMatrixT< float > SpectralDelayMatrix
Definition spectral_delay_matrix.hpp:337
constexpr std::uint64_t kTargetAddressMaximumBytes
Definition checked_allocation.hpp:9
@ i
Definition formant_filter_bank.hpp:20
bool compile_spectral_delay_matrix_table(const SpectralDelayMatrixRecipe &recipe, int num_bins, int analysis_hop, double sample_rate, int maximum_delay_frames, SpectralDelayMatrixTable &out_table)
Definition spectral_delay_matrix.hpp:109
Lock-free sequence-lock for coherent multi-field snapshots.
Streaming STFT analysis + weighted overlap-add (WOLA) synthesis for DSP-grade spectral processing — d...
Definition spectral_delay_matrix.hpp:34
float freq01
Definition spectral_delay_matrix.hpp:35
float attenuation_db
Definition spectral_delay_matrix.hpp:37
float delay_ms
Definition spectral_delay_matrix.hpp:36
Definition spectral_delay_matrix.hpp:52
std::size_t max_retained_bytes
Definition spectral_delay_matrix.hpp:56
SpectralFrameEngineConfig frame
Definition spectral_delay_matrix.hpp:53
double sample_rate
Definition spectral_delay_matrix.hpp:54
double max_delay_ms
Definition spectral_delay_matrix.hpp:55
Definition spectral_delay_matrix.hpp:59
int num_bins
Definition spectral_delay_matrix.hpp:60
int history_frames
Definition spectral_delay_matrix.hpp:61
std::uint64_t retained_bytes
Definition spectral_delay_matrix.hpp:62
Definition spectral_delay_matrix.hpp:40
std::vector< SpectralDelayBreakpoint > breakpoints
Definition spectral_delay_matrix.hpp:42
std::uint64_t version
Definition spectral_delay_matrix.hpp:41
Definition spectral_delay_matrix.hpp:45
std::vector< float > attenuation_linear
Definition spectral_delay_matrix.hpp:49
std::uint64_t version
Definition spectral_delay_matrix.hpp:46
int num_bins
Definition spectral_delay_matrix.hpp:47
std::vector< std::int32_t > delay_frames
Definition spectral_delay_matrix.hpp:48
Configuration for a SpectralFrameEngine.
Definition spectral_frame_engine.hpp:56
Lock-free latest-value publication between two threads.