49 SampleType
mix = SampleType{1};
62 static_assert(std::is_floating_point_v<SampleType>);
79 !std::isfinite(
config.convergence_tolerance) ||
80 config.convergence_tolerance < SampleType{0})
90 if (
config.lifter_order < 0)
99 std::uint64_t analyzer_bytes = 0;
103 const auto bins =
static_cast<std::uint64_t
>(
config.fft_size / 2 + 1);
104 std::uint64_t elements = 0;
106 std::numeric_limits<std::uint64_t>::max(), elements))
110 !charge.
add<std::uint8_t>(
static_cast<std::uint64_t
>(
config.channels)))
113#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
121 const auto count =
static_cast<std::size_t
>(elements);
122 std::vector<SampleType> next_carrier_log(count, SampleType{0});
123 std::vector<SampleType> next_modulator_log(count, SampleType{0});
124 std::vector<SampleType> next_carrier_envelope(count, SampleType{0});
125 std::vector<SampleType> next_modulator_envelope(count, SampleType{0});
126 std::vector<SampleType> next_carrier_history(count, SampleType{0});
127 std::vector<SampleType> next_modulator_history(count, SampleType{0});
128 std::vector<SampleType> next_carrier_magnitude(count, SampleType{0});
129 std::vector<SampleType> next_wet_magnitude(count, SampleType{0});
130 std::vector<std::uint8_t> next_history_valid(
static_cast<std::size_t
>(
config.channels),
133 analyzer_ = std::move(next_analyzer);
134 carrier_log_ = std::move(next_carrier_log);
135 modulator_log_ = std::move(next_modulator_log);
136 carrier_envelope_ = std::move(next_carrier_envelope);
137 modulator_envelope_ = std::move(next_modulator_envelope);
138 carrier_history_ = std::move(next_carrier_history);
139 modulator_history_ = std::move(next_modulator_history);
140 carrier_magnitude_ = std::move(next_carrier_magnitude);
141 wet_magnitude_ = std::move(next_wet_magnitude);
142 history_valid_ = std::move(next_history_valid);
144 num_bins_ =
config.fft_size / 2 + 1;
145 retained_bytes_ = charge.
total();
147#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
148 }
catch (
const std::bad_alloc&) {
150 }
catch (
const std::length_error&) {
161 if (!valid_config(
config))
163 const SampleType next_retain =
164 config.envelope_smoothing_frames <= 1
166 : std::exp(-SampleType{1} /
167 static_cast<SampleType
>(
config.envelope_smoothing_frames));
169 envelope_retain_ = next_retain;
177 return num_bins_ > 0;
180 return prepared() ? prepare_config_.channels : 0;
183 return prepared() ? prepare_config_.fft_size : 0;
189 return prepared() ? prepare_config_.lifter_order : 0;
192 return retained_bytes_;
198 std::fill(carrier_history_.begin(), carrier_history_.end(), SampleType{0});
199 std::fill(modulator_history_.begin(), modulator_history_.end(), SampleType{0});
200 std::fill(history_valid_.begin(), history_valid_.end(), std::uint8_t{0});
207 bool process(
const std::complex<SampleType>*
const* carrier,
208 const std::complex<SampleType>*
const* modulator,
209 std::complex<SampleType>*
const* output,
int channels,
int num_bins)
noexcept {
211 carrier ==
nullptr || modulator ==
nullptr || output ==
nullptr)
213 for (
int ch = 0; ch <
channels; ++ch) {
214 if (carrier[ch] ==
nullptr || modulator[ch] ==
nullptr || output[ch] ==
nullptr)
218 for (
int ch = 0; ch <
channels; ++ch) {
219 const auto offset =
static_cast<std::size_t
>(ch) *
static_cast<std::size_t
>(num_bins_);
220 for (
int bin = 0; bin < num_bins_; ++bin) {
221 const auto index = offset +
static_cast<std::size_t
>(bin);
222 const bool endpoint = bin == 0 || bin == num_bins_ - 1;
223 const SampleType carrier_magnitude = decoded_magnitude(carrier[ch][bin], endpoint);
224 const SampleType modulator_magnitude =
225 decoded_magnitude(modulator[ch][bin], endpoint);
226 carrier_magnitude_[index] = carrier_magnitude;
227 carrier_log_[index] =
228 std::log(std::max(carrier_magnitude, config_.magnitude_floor));
229 modulator_log_[index] =
230 std::log(std::max(modulator_magnitude, config_.magnitude_floor));
232 const auto carrier_result =
233 analyzer_.estimate(std::span<const SampleType>{carrier_log_.data() + offset,
234 static_cast<std::size_t
>(num_bins_)},
235 std::span<SampleType>{carrier_envelope_.data() + offset,
236 static_cast<std::size_t
>(num_bins_)});
237 if (!carrier_result.ok())
239 const auto modulator_result =
240 analyzer_.estimate(std::span<const SampleType>{modulator_log_.data() + offset,
241 static_cast<std::size_t
>(num_bins_)},
242 std::span<SampleType>{modulator_envelope_.data() + offset,
243 static_cast<std::size_t
>(num_bins_)});
244 if (!modulator_result.ok())
248 const SampleType maximum_transfer_ln =
249 config_.maximum_transfer_db *
static_cast<SampleType
>(0.1151292546497022842L);
250 for (
int ch = 0; ch <
channels; ++ch) {
251 const auto offset =
static_cast<std::size_t
>(ch) *
static_cast<std::size_t
>(num_bins_);
252 const bool initialize = history_valid_[
static_cast<std::size_t
>(ch)] == 0;
253 for (
int bin = 0; bin < num_bins_; ++bin) {
254 const auto index = offset +
static_cast<std::size_t
>(bin);
256 carrier_history_[index] = carrier_envelope_[index];
257 modulator_history_[index] = modulator_envelope_[index];
259 carrier_history_[index] = std::fma(
260 envelope_retain_, carrier_history_[index] - carrier_envelope_[index],
261 carrier_envelope_[index]);
262 modulator_history_[index] = std::fma(
263 envelope_retain_, modulator_history_[index] - modulator_envelope_[index],
264 modulator_envelope_[index]);
266 const SampleType transfer = std::clamp(
267 config_.amount * (modulator_history_[index] - carrier_history_[index]),
268 -maximum_transfer_ln, maximum_transfer_ln);
269 wet_magnitude_[index] = transferred_magnitude(carrier_magnitude_[index], transfer);
271 history_valid_[
static_cast<std::size_t
>(ch)] = 1;
273 SampleType normalization_gain = SampleType{1};
275 normalization_gain = rms_ratio(carrier_magnitude_.data() + offset,
276 wet_magnitude_.data() + offset, num_bins_);
278 for (
int bin = 0; bin < num_bins_; ++bin) {
279 const auto index = offset +
static_cast<std::size_t
>(bin);
280 const auto carrier_value = carrier[ch][bin];
281 if (!finite(carrier_value) || carrier_magnitude_[index] == SampleType{0}) {
282 output[ch][bin] = {};
285 const SampleType magnitude = mixed_magnitude(
286 carrier_magnitude_[index], wet_magnitude_[index], normalization_gain,
288 const bool endpoint = bin == 0 || bin == num_bins_ - 1;
290 const SampleType sign =
291 carrier_value.real() < SampleType{0} ? SampleType{-1} : SampleType{1};
292 output[ch][bin] = {sign * magnitude, SampleType{0}};
294 output[ch][bin] = unit_phase(carrier_value) * magnitude;
311 static bool valid_config(
const Config&
config)
noexcept {
312 const bool normalization_valid =
318 config.envelope_smoothing_frames >= 1 &&
320 std::isfinite(
config.maximum_transfer_db) &&
321 config.maximum_transfer_db >= SampleType{0} &&
323 std::isfinite(
config.magnitude_floor) &&
config.magnitude_floor > SampleType{0} &&
327 static bool finite(std::complex<SampleType> value)
noexcept {
328 return std::isfinite(
value.real()) && std::isfinite(
value.imag());
331 static SampleType decoded_magnitude(std::complex<SampleType> value,
bool endpoint)
noexcept {
333 return SampleType{0};
335 return std::abs(
value.real());
336 const SampleType
scale = std::max(std::abs(
value.real()), std::abs(
value.imag()));
337 if (scale == SampleType{0})
338 return SampleType{0};
339 const SampleType normalized = std::hypot(
value.real() / scale,
value.imag() / scale);
340 const SampleType
maximum = std::numeric_limits<SampleType>::max();
344 static std::complex<SampleType> unit_phase(std::complex<SampleType> value)
noexcept {
345 const SampleType
scale = std::max(std::abs(
value.real()), std::abs(
value.imag()));
348 const SampleType length = std::hypot(real, imag);
349 return {real / length, imag / length};
352 static SampleType saturate_positive(
long double value)
noexcept {
354 return SampleType{0};
356 static_cast<long double>(std::numeric_limits<SampleType>::max());
357 return static_cast<SampleType
>(std::min(value,
maximum));
360 static SampleType saturating_positive_product(SampleType
a, SampleType b,
361 SampleType
c)
noexcept {
362 std::array<SampleType, 3> factors{
a,
b,
c};
363 std::sort(factors.begin(), factors.end());
364 if (!(factors[0] > SampleType{0}))
365 return SampleType{0};
366 const SampleType
maximum = std::numeric_limits<SampleType>::max();
367 if (factors[0] >
maximum / factors[1])
369 const SampleType
partial = factors[0] * factors[1];
373 static SampleType mixed_magnitude(SampleType dry, SampleType wet,
374 SampleType normalization_gain, SampleType mix)
noexcept {
375 if (mix == SampleType{0})
377 if (mix == SampleType{1})
378 return saturating_positive_product(wet, normalization_gain, SampleType{1});
379 const SampleType dry_component = (SampleType{1} - mix) * dry;
380 const SampleType wet_component = saturating_positive_product(wet, normalization_gain, mix);
381 const SampleType
maximum = std::numeric_limits<SampleType>::max();
383 : dry_component + wet_component;
386 static SampleType transferred_magnitude(SampleType carrier_magnitude,
387 SampleType transfer)
noexcept {
388 if (carrier_magnitude == SampleType{0})
389 return SampleType{0};
390 const long double log_target = std::log(
static_cast<long double>(carrier_magnitude)) +
391 static_cast<long double>(transfer);
392 const long double maximum_log =
393 std::log(
static_cast<long double>(std::numeric_limits<SampleType>::max()));
394 return saturate_positive(std::exp(std::min(log_target, maximum_log)));
397 static SampleType rms_ratio(
const SampleType* dry,
const SampleType* wet,
int count)
noexcept {
398 long double dry_scale = 0.0L;
399 long double dry_sum = 0.0L;
400 long double wet_scale = 0.0L;
401 long double wet_sum = 0.0L;
402 const auto add_scaled_square = [](SampleType
value,
long double&
scale,
403 long double&
sum)
noexcept {
404 const long double magnitude = std::abs(
static_cast<long double>(value));
405 if (magnitude == 0.0L)
407 if (scale < magnitude) {
417 add_scaled_square(dry[index], dry_scale, dry_sum);
418 add_scaled_square(wet[index], wet_scale, wet_sum);
420 if (!(dry_scale > 0.0L) || !(wet_scale > 0.0L))
421 return SampleType{1};
422 const long double ratio = (dry_scale / wet_scale) * std::sqrt(dry_sum / wet_sum);
423 return saturate_positive(
ratio);
428 CepstralEnvelopeAnalyzerT<SampleType> analyzer_{};
430 SampleType envelope_retain_ = SampleType{0};
431 std::uint64_t retained_bytes_ = 0;
432 std::vector<SampleType> carrier_log_;
433 std::vector<SampleType> modulator_log_;
434 std::vector<SampleType> carrier_envelope_;
435 std::vector<SampleType> modulator_envelope_;
436 std::vector<SampleType> carrier_history_;
437 std::vector<SampleType> modulator_history_;
438 std::vector<SampleType> carrier_magnitude_;
439 std::vector<SampleType> wet_magnitude_;
440 std::vector<std::uint8_t> history_valid_;
Log-magnitude spectrum to liftered log-envelope.
Definition source_filter_analysis.hpp:83
SourceFilterAnalysisStatus prepare(const Config &config)
Definition source_filter_analysis.hpp:107
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
CepstralEnvelopeConfigT< SampleType > Config
Definition source_filter_analysis.hpp:87
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
Definition spectral_cross_synthesis.hpp:60
static constexpr int maximum_channels
Definition spectral_cross_synthesis.hpp:67
SpectralCrossSynthesisPrepareConfigT< SampleType > PrepareConfig
Definition spectral_cross_synthesis.hpp:64
bool prepared() const noexcept
Definition spectral_cross_synthesis.hpp:176
static constexpr int maximum_true_envelope_iterations
Definition spectral_cross_synthesis.hpp:68
void reset() noexcept
Clear temporal envelope history while preserving prepared capacity and both configs.
Definition spectral_cross_synthesis.hpp:197
int channels() const noexcept
Definition spectral_cross_synthesis.hpp:179
bool prepare(PrepareConfig config)
Allocate the analyzer, envelope history, and all frame scratch off the audio thread.
Definition spectral_cross_synthesis.hpp:87
Config config() const noexcept
Definition spectral_cross_synthesis.hpp:173
static constexpr int added_tail_samples() noexcept
Definition spectral_cross_synthesis.hpp:306
SpectralCrossSynthesisConfigT< SampleType > Config
Definition spectral_cross_synthesis.hpp:65
static constexpr int maximum_smoothing_frames
Definition spectral_cross_synthesis.hpp:69
bool set_config(const Config &config) noexcept
Replace every runtime control transactionally.
Definition spectral_cross_synthesis.hpp:160
static bool supports_configuration(const PrepareConfig &config) noexcept
Definition spectral_cross_synthesis.hpp:72
int lifter_order() const noexcept
Definition spectral_cross_synthesis.hpp:188
int num_bins() const noexcept
Definition spectral_cross_synthesis.hpp:185
std::uint64_t retained_bytes() const noexcept
Definition spectral_cross_synthesis.hpp:191
int fft_size() const noexcept
Definition spectral_cross_synthesis.hpp:182
static constexpr int added_latency_samples() noexcept
Analysis/synthesis buffering belongs to the surrounding frame engine; retained envelope history does ...
Definition spectral_cross_synthesis.hpp:303
bool process(const std::complex< SampleType > *const *carrier, const std::complex< SampleType > *const *modulator, std::complex< SampleType > *const *output, int channels, int num_bins) noexcept
Cross-synthesize one coherent frame group.
Definition spectral_cross_synthesis.hpp:207
static constexpr float maximum_transfer_db
Definition spectral_cross_synthesis.hpp:70
@ partial
Inspectable or editable, but not fully rebuildable or playable.
Definition component.hpp:176
@ count
sentinel: number of real capabilities (keep LAST)
Definition canvas_capability.hpp:38
@ b
Definition pitch.hpp:31
@ index
Definition gpu_pass.hpp:140
Definition additive_bank.hpp:218
@ Ok
Definition source_filter_analysis.hpp:29
SpectralCrossSynthesisConfigT< float > SpectralCrossSynthesisConfig
Definition spectral_cross_synthesis.hpp:445
SpectralCrossSynthesisConfigT< double > SpectralCrossSynthesisConfig64
Definition spectral_cross_synthesis.hpp:446
SpectralCrossSynthesisT< double > SpectralCrossSynthesis64
Definition spectral_cross_synthesis.hpp:448
@ c
Definition spectrum_trace.hpp:35
constexpr int kSpectralFrameEngineMaximumFftSize
Definition spectral_frame_engine.hpp:94
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
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
SpectralCrossSynthesisPrepareConfigT< float > SpectralCrossSynthesisPrepareConfig
Definition spectral_cross_synthesis.hpp:443
@ maximum
Definition spectrum_trace.hpp:34
SpectralCrossSynthesisNormalization
Definition spectral_cross_synthesis.hpp:31
@ none
Definition spectral_cross_synthesis.hpp:32
@ match_carrier_rms
Definition spectral_cross_synthesis.hpp:33
constexpr std::uint64_t kTargetAddressMaximumBytes
Definition checked_allocation.hpp:9
@ a
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
constexpr int kSpectralFrameEngineMinimumFftSize
Definition spectral_frame_engine.hpp:93
@ none
Definition dither.hpp:17
SpectralCrossSynthesisPrepareConfigT< double > SpectralCrossSynthesisPrepareConfig64
Definition spectral_cross_synthesis.hpp:444
SpectralCrossSynthesisT< float > SpectralCrossSynthesis
Definition spectral_cross_synthesis.hpp:447
float sum(const float *data, std::size_t count) noexcept
void scale(const float *a, float scalar, float *dst, std::size_t count) noexcept
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
Prepared, allocation-free spectral-envelope and linear-prediction analysis.
Streaming STFT analysis + weighted overlap-add (WOLA) synthesis for DSP-grade spectral processing — d...
int true_envelope_iterations
Definition source_filter_analysis.hpp:65
int fft_size
Definition source_filter_analysis.hpp:63
SampleType convergence_tolerance
Maximum remaining positive log-magnitude residual.
Definition source_filter_analysis.hpp:68
int order
Definition source_filter_analysis.hpp:64
Definition spectral_cross_synthesis.hpp:45
SpectralCrossSynthesisNormalization normalization
Definition spectral_cross_synthesis.hpp:56
float mix
Definition spectral_cross_synthesis.hpp:49
float amount
Definition spectral_cross_synthesis.hpp:47
int envelope_smoothing_frames
Definition spectral_cross_synthesis.hpp:51
float magnitude_floor
Definition spectral_cross_synthesis.hpp:55
float maximum_transfer_db
Definition spectral_cross_synthesis.hpp:53
Definition spectral_cross_synthesis.hpp:36
int true_envelope_iterations
Definition spectral_cross_synthesis.hpp:41
int channels
Definition spectral_cross_synthesis.hpp:37
int lifter_order
Definition spectral_cross_synthesis.hpp:40
int fft_size
Definition spectral_cross_synthesis.hpp:38
float convergence_tolerance
Definition spectral_cross_synthesis.hpp:42