Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
sample_heritage_voice_dsp.hpp
Go to the documentation of this file.
1#pragma once
2
7
8#include <algorithm>
9#include <array>
10#include <cmath>
11#include <complex>
12#include <cstddef>
13#include <cstdint>
14#include <limits>
15#include <numbers>
16#include <type_traits>
17#include <variant>
18
19namespace pulp::audio {
20
21namespace detail {
22
23inline std::uint64_t heritage_next_random(std::uint64_t& state) noexcept {
24 state ^= state >> 12;
25 state ^= state << 25;
26 state ^= state >> 27;
27 return state * UINT64_C(2685821657736338717);
28}
29
30inline float heritage_bipolar_random(std::uint64_t& state) noexcept {
31 const auto bits = static_cast<std::uint32_t>(heritage_next_random(state) >> 40);
32 return static_cast<float>(bits) * (2.0f / 16777215.0f) - 1.0f;
33}
34
35} // namespace detail
36
39public:
40 static constexpr float kTailSilenceThreshold = 1.0e-6f;
41 static constexpr double kMaximumTailSeconds = 10.0;
42
44 double processing_sample_rate) noexcept {
45 return prepare(profile, processing_sample_rate, processing_sample_rate);
46 }
47
49 double machine_sample_rate,
50 double host_sample_rate) noexcept {
51 release();
52 if (profile.voice_count > runtimes_.size() ||
53 !(machine_sample_rate > 0.0) || !std::isfinite(machine_sample_rate) ||
54 !(host_sample_rate > 0.0) || !std::isfinite(host_sample_rate))
55 return false;
56 profile_ = profile;
57 machine_sample_rate_ = machine_sample_rate;
58 host_sample_rate_ = host_sample_rate;
59 for (std::size_t index = 0; index < profile_.voice_count; ++index) {
60 auto& runtime = runtimes_[index];
61 runtime.spec = profile_.voice[index];
62 if (!initialize(runtime)) {
63 release();
64 return false;
65 }
66 }
67 prepared_ = true;
68 reset();
69 return true;
70 }
71
72 void release() noexcept {
73 profile_ = {};
74 runtimes_ = {};
75 has_companded_8bit_decode_ = false;
76 machine_sample_rate_ = 0.0;
77 host_sample_rate_ = 0.0;
78 prepared_ = false;
79 }
80
81 void reset() noexcept {
82 if (!prepared_) return;
83 for (std::size_t index = 0; index < profile_.voice_count; ++index) {
84 auto& runtime = runtimes_[index];
85 runtime.hold_value.fill(0.0f);
86 runtime.hold_phase.fill(0);
87 runtime.one_pole_state.fill(0.0f);
88 runtime.sos_z1 = {};
89 runtime.sos_z2 = {};
90 for (auto& filter : runtime.analog_filters) filter.reset();
91 if (const auto* converter =
92 std::get_if<SampleHeritageVoiceConverterBlock>(
93 &runtime.spec.parameters))
94 runtime.random_state = converter->seed;
95 }
96 }
97
98 bool prepared() const noexcept { return prepared_; }
99
100 bool converter_continues_state(std::size_t index) const noexcept {
101 if (!prepared_ || index >= profile_.voice_count) return false;
102 const auto* converter = std::get_if<SampleHeritageVoiceConverterBlock>(
103 &runtimes_[index].spec.parameters);
104 return converter != nullptr && !runtimes_[index].spec.bypass &&
105 converter->seed_policy ==
107 }
108
109 std::uint64_t converter_random_state(std::size_t index) const noexcept {
110 return converter_continues_state(index) ? runtimes_[index].random_state : 0;
111 }
112
113 bool restore_converter_random_state(std::size_t index,
114 std::uint64_t state) noexcept {
115 if (!converter_continues_state(index) || state == 0) return false;
116 runtimes_[index].random_state = state;
117 return true;
118 }
119
120 void process(BufferView<float> buffer) noexcept {
121 process_range(buffer, 0, profile_.voice_count, false, Frame::Machine);
122 process_range(buffer, 0, profile_.voice_count, false, Frame::Host);
123 }
124
126 bool draining_tail = false) noexcept {
127 process_range(buffer, 0, live_stage_index(), draining_tail,
128 Frame::Machine);
129 }
130
132 bool draining_tail = false) noexcept {
133 const auto live = live_stage_index();
134 process_range(buffer, live < profile_.voice_count ? live + 1 : live,
135 profile_.voice_count, draining_tail, Frame::Machine);
136 }
137
139 bool draining_tail = false) noexcept {
140 process_range(buffer, 0, profile_.voice_count, draining_tail,
141 Frame::Host);
142 }
143
145 process_range(buffer, 0, profile_.voice_count, true, Frame::Machine);
146 }
147
148 void process_tail(BufferView<float> buffer) noexcept {
149 process_machine_tail(buffer);
150 process_host_frame(buffer, true);
151 }
152
153 std::size_t live_stage_index() const noexcept {
154 if (!prepared_) return profile_.voice_count;
155 for (std::size_t index = 0; index < profile_.voice_count; ++index) {
156 if (!runtimes_[index].spec.bypass &&
157 std::holds_alternative<SampleHeritageVoiceLiveCyclicStretchBlock>(
158 runtimes_[index].spec.parameters))
159 return index;
160 }
161 return profile_.voice_count;
162 }
163
164 bool live_stage_active() const noexcept {
165 return live_stage_index() < profile_.voice_count;
166 }
167
168private:
169 enum class Frame : std::uint8_t { Machine, Host };
170
171 static bool belongs_to_frame(const SampleHeritageVoiceBlockSpec& block,
172 Frame frame) noexcept {
173 const auto host =
174 std::holds_alternative<SampleHeritageVoiceReconstructionBlock>(
175 block.parameters) ||
176 std::holds_alternative<SampleHeritageVoiceAnalogColorBlock>(
177 block.parameters);
178 return host == (frame == Frame::Host);
179 }
180
181 void process_range(BufferView<float> buffer, std::size_t begin,
182 std::size_t end, bool draining_tail, Frame frame) noexcept {
183 if (!prepared_) return;
184 end = std::min(end, profile_.voice_count);
185 for (std::size_t index = begin; index < end; ++index) {
186 auto& runtime = runtimes_[index];
187 if (runtime.spec.bypass || !belongs_to_frame(runtime.spec, frame) ||
188 (draining_tail &&
189 std::holds_alternative<SampleHeritageVoiceConverterBlock>(
190 runtime.spec.parameters)))
191 continue;
192 std::visit(
193 [&](const auto& block) noexcept { process(runtime, block, buffer); },
194 runtime.spec.parameters);
195 }
196 }
197
198public:
199
200 std::uint64_t tail_machine_frames() const noexcept {
201 const auto machine = tail_machine_domain_frames();
202 const auto host = tail_host_frames();
203 return machine > std::numeric_limits<std::uint64_t>::max() - host
204 ? std::numeric_limits<std::uint64_t>::max()
205 : machine + host;
206 }
207
208 std::uint64_t tail_machine_domain_frames() const noexcept {
209 return tail_frames(Frame::Machine, machine_sample_rate_);
210 }
211
212 std::uint64_t tail_host_frames() const noexcept {
213 return tail_frames(Frame::Host, host_sample_rate_);
214 }
215
216private:
217 std::uint64_t tail_frames(Frame frame, double sample_rate) const noexcept {
218 if (!prepared_) return 0;
219 long double frames = 0.0L;
220 for (std::size_t index = 0; index < profile_.voice_count; ++index) {
221 const auto& runtime = runtimes_[index];
222 if (runtime.spec.bypass || !belongs_to_frame(runtime.spec, frame))
223 continue;
224 std::visit(
225 [&](const auto& block) noexcept {
226 using Block = std::decay_t<decltype(block)>;
227 if constexpr (std::is_same_v<Block,
229 frames += block.hold_samples;
230 } else if constexpr (
231 std::is_same_v<Block,
232 SampleHeritageVoiceReconstructionBlock>) {
233 auto radius = static_cast<long double>(runtime.maximum_pole_radius);
234 if (radius > 0.0L && radius < 1.0L) {
235 constexpr long double reference_peak = 16.0L;
236 const auto decay = std::ceil(
237 std::log(static_cast<long double>(
238 kTailSilenceThreshold) / reference_peak) /
239 std::log(radius));
240 frames += decay * static_cast<long double>(
241 std::max<std::size_t>(1, runtime.sos_count + 1));
242 }
243 } else if constexpr (
244 std::is_same_v<Block,
245 SampleHeritageVoiceAnalogColorBlock>) {
246 auto radius =
247 static_cast<long double>(runtime.analog_decay_pole);
248 if (block.filter_family !=
250 block.mix > 0.0f && radius > 0.0L && radius < 1.0L) {
251 constexpr long double reference_peak = 16.0L;
252 frames += 4.0L * std::ceil(
253 std::log(static_cast<long double>(
254 kTailSilenceThreshold) / reference_peak) /
255 std::log(radius));
256 }
257 }
258 },
259 runtime.spec.parameters);
260 }
261 const auto cap = std::ceil(static_cast<long double>(sample_rate) *
263 return static_cast<std::uint64_t>(std::clamp(frames, 0.0L, cap));
264 }
265
266 static constexpr std::size_t kMaximumSos = 8;
267
268 struct SosCoefficients {
269 float b0 = 1.0f;
270 float b1 = 0.0f;
271 float b2 = 0.0f;
272 float a1 = 0.0f;
273 float a2 = 0.0f;
274 };
275
276 struct Runtime {
277 SampleHeritageVoiceBlockSpec spec{};
278 std::uint64_t random_state = 0;
279 std::array<float, kSampleHeritageMaximumChannels> hold_value{};
280 std::array<std::uint32_t, kSampleHeritageMaximumChannels> hold_phase{};
281 float one_pole_pole = 0.0f;
282 std::array<float, kSampleHeritageMaximumChannels> one_pole_state{};
283 std::array<SosCoefficients, kMaximumSos> sos{};
284 std::size_t sos_count = 0;
285 std::array<std::array<float, kMaximumSos>,
287 std::array<std::array<float, kMaximumSos>,
289 float maximum_pole_radius = 0.0f;
291 kSampleHeritageMaximumChannels> analog_filters{};
292 float analog_decay_pole = 0.0f;
293 };
294
295 bool initialize(Runtime& runtime) noexcept {
296 if (runtime.spec.bypass) return true;
297 return std::visit(
298 [&](const auto& block) noexcept {
299 using Block = std::decay_t<decltype(block)>;
300 if constexpr (std::is_same_v<Block,
301 SampleHeritageVoiceConverterBlock>) {
302 runtime.random_state = block.seed;
303 has_companded_8bit_decode_ =
304 block.bit_depth == 8.0f &&
306 if (has_companded_8bit_decode_) {
307 for (std::size_t index = 0;
308 index < companded_8bit_decode_.size(); ++index) {
309 const auto code = static_cast<int>(index) - 128;
310 companded_8bit_decode_[index] = expand(
311 static_cast<float>(code) / 128.0f,
312 block.family);
313 }
314 }
315 } else if constexpr (
316 std::is_same_v<Block,
317 SampleHeritageVoiceReconstructionBlock>) {
318 return initialize_filter(runtime, block);
319 } else if constexpr (
320 std::is_same_v<Block,
321 SampleHeritageVoiceLiveCyclicStretchBlock>) {
322 return true;
323 } else if constexpr (
324 std::is_same_v<Block,
325 SampleHeritageVoiceAnalogColorBlock>) {
326 return initialize_analog_filter(runtime, block);
327 }
328 return true;
329 },
330 runtime.spec.parameters);
331 }
332
333 double resolve_cutoff(
334 const SampleHeritageVoiceReconstructionBlock& block) const noexcept {
336 ? block.cutoff_value * machine_sample_rate_
337 : block.cutoff_value;
338 }
339
340 double resolve_cutoff(
341 const SampleHeritageVoiceAnalogColorBlock& block) const noexcept {
343 ? block.cutoff_value * machine_sample_rate_
344 : block.cutoff_value;
345 }
346
347 bool initialize_analog_filter(
348 Runtime& runtime,
349 const SampleHeritageVoiceAnalogColorBlock& block) noexcept {
350 if (block.filter_family == SampleHeritageAnalogFilterFamily::None) {
351 runtime.analog_decay_pole = 0.0f;
352 return true;
353 }
354 const auto cutoff = resolve_cutoff(block);
355 if (!(cutoff >= 1.0 &&
356 cutoff <= host_sample_rate_ *
358 !std::isfinite(cutoff) || block.resonance < 0.0f ||
360 !std::isfinite(block.resonance))
361 return false;
362 for (auto& filter : runtime.analog_filters) {
363 filter.set_sample_rate(static_cast<float>(host_sample_rate_));
364 filter.set_frequency(static_cast<float>(cutoff));
365 filter.set_resonance(block.resonance);
366 }
367 const auto one_pole_decay = static_cast<float>(std::exp(
368 -2.0 * std::numbers::pi * cutoff / host_sample_rate_));
369 runtime.analog_decay_pole =
370 std::max(one_pole_decay, std::sqrt(block.resonance));
371 return runtime.analog_decay_pole >= 0.0f &&
372 runtime.analog_decay_pole < 1.0f &&
373 std::isfinite(runtime.analog_decay_pole);
374 }
375
376 bool initialize_filter(
377 Runtime& runtime,
378 const SampleHeritageVoiceReconstructionBlock& block) noexcept {
379 const auto cutoff = resolve_cutoff(block);
380 if (!(cutoff > 0.0 && cutoff < host_sample_rate_ * 0.5) ||
381 !std::isfinite(cutoff))
382 return false;
384 runtime.one_pole_pole = static_cast<float>(std::exp(
385 -2.0 * std::numbers::pi * cutoff / host_sample_rate_));
386 runtime.maximum_pole_radius = runtime.one_pole_pole;
387 return true;
388 }
389
390 runtime.sos_count = block.order / 2;
391 if (runtime.sos_count == 0 || runtime.sos_count > runtime.sos.size())
392 return false;
394 try {
395 const auto designed = pulp::signal::IirDesign::elliptic_lowpass(
396 block.order, static_cast<float>(cutoff), block.ripple_db,
397 block.stopband_attenuation_db,
398 static_cast<float>(host_sample_rate_));
399 if (designed.size() != runtime.sos_count) return false;
400 runtime.maximum_pole_radius = 0.0f;
401 for (std::size_t section = 0; section < runtime.sos_count;
402 ++section) {
403 const auto& source = designed[section];
404 auto& destination = runtime.sos[section];
405 destination = {source.b0, source.b1, source.b2,
406 source.a1, source.a2};
407 const auto roots = std::sqrt(std::complex<double>(
408 source.a1 * source.a1 - 4.0 * source.a2, 0.0));
409 const auto first = (-static_cast<double>(source.a1) + roots) /
410 2.0;
411 const auto second = (-static_cast<double>(source.a1) - roots) /
412 2.0;
413 runtime.maximum_pole_radius = std::max(
414 runtime.maximum_pole_radius,
415 static_cast<float>(
416 std::max(std::abs(first), std::abs(second))));
417 }
418 return runtime.maximum_pole_radius < 1.0f &&
419 std::isfinite(runtime.maximum_pole_radius);
420 } catch (...) {
421 return false;
422 }
423 }
424 const auto warped = std::tan(std::numbers::pi * cutoff /
425 host_sample_rate_);
426 const auto epsilon = block.family ==
428 ? 0.0
429 : std::sqrt(std::pow(10.0, block.ripple_db / 10.0) - 1.0);
430 const auto mu = epsilon > 0.0
431 ? std::asinh(1.0 / epsilon) / static_cast<double>(block.order)
432 : 0.0;
433 runtime.maximum_pole_radius = 0.0f;
434 for (std::size_t section = 0; section < runtime.sos_count; ++section) {
435 const auto theta = std::numbers::pi *
436 (2.0 * static_cast<double>(section) + 1.0) /
437 (2.0 * static_cast<double>(block.order));
438 const auto real = epsilon == 0.0
439 ? -std::sin(theta)
440 : -std::sinh(mu) * std::sin(theta);
441 const auto imaginary = epsilon == 0.0
442 ? std::cos(theta)
443 : std::cosh(mu) * std::cos(theta);
444 const auto analog_a1 = -2.0 * real;
445 const auto analog_a0 = real * real + imaginary * imaginary;
446 const auto denominator = 1.0 + analog_a1 * warped +
447 analog_a0 * warped * warped;
448 auto& coefficients = runtime.sos[section];
449 coefficients.a1 = static_cast<float>(
450 2.0 * (analog_a0 * warped * warped - 1.0) / denominator);
451 coefficients.a2 = static_cast<float>(
452 (1.0 - analog_a1 * warped + analog_a0 * warped * warped) /
453 denominator);
454
455 const auto gain = analog_a0 * warped * warped / denominator;
456 coefficients.b0 = static_cast<float>(gain);
457 coefficients.b1 = static_cast<float>(2.0 * gain);
458 coefficients.b2 = static_cast<float>(gain);
459
460 const auto roots = std::sqrt(std::complex<double>(
461 coefficients.a1 * coefficients.a1 -
462 4.0 * coefficients.a2,
463 0.0));
464 const auto first = (-static_cast<double>(coefficients.a1) + roots) /
465 2.0;
466 const auto second = (-static_cast<double>(coefficients.a1) - roots) /
467 2.0;
468 runtime.maximum_pole_radius = std::max(
469 runtime.maximum_pole_radius,
470 static_cast<float>(std::max(std::abs(first), std::abs(second))));
471 if (!(runtime.maximum_pole_radius < 1.0f) ||
472 !std::isfinite(runtime.maximum_pole_radius))
473 return false;
474 }
475 return true;
476 }
477
478 static float compress(float sample,
479 SampleHeritageConverterFamily family) noexcept {
480 sample = std::clamp(sample, -1.0f, 1.0f);
481 const auto magnitude = std::abs(sample);
482 if (family == SampleHeritageConverterFamily::LinearPcm) return sample;
484 constexpr float mu = 255.0f;
485 return std::copysign(std::log1p(mu * magnitude) / std::log1p(mu),
486 sample);
487 }
488 constexpr float a = 87.6f;
489 const float denominator = 1.0f + std::log(a);
490 const auto compressed = magnitude < 1.0f / a
491 ? a * magnitude / denominator
492 : (1.0f + std::log(a * magnitude)) / denominator;
493 return std::copysign(compressed, sample);
494 }
495
496 static float expand(float sample,
497 SampleHeritageConverterFamily family) noexcept {
498 sample = std::clamp(sample, -1.0f, 1.0f);
499 const auto magnitude = std::abs(sample);
500 if (family == SampleHeritageConverterFamily::LinearPcm) return sample;
502 constexpr float mu = 255.0f;
503 return std::copysign(std::expm1(magnitude * std::log1p(mu)) / mu,
504 sample);
505 }
506 constexpr float a = 87.6f;
507 const float denominator = 1.0f + std::log(a);
508 const auto threshold = 1.0f / denominator;
509 const auto expanded = magnitude < threshold
510 ? magnitude * denominator / a
511 : std::exp(magnitude * denominator - 1.0f) / a;
512 return std::copysign(expanded, sample);
513 }
514
515 static float apply_dac_nonlinearity(float sample, float amount) noexcept {
516 const auto bounded = std::clamp(sample, -1.0f, 1.0f);
517 return bounded + amount * bounded * (1.0f - std::abs(bounded));
518 }
519
520 static void process(Runtime&,
521 const SampleHeritageVoiceMachineDomainBlock&,
522 BufferView<float>) noexcept {}
523 static void process(Runtime&,
524 const SampleHeritageVoiceClockBlock&,
525 BufferView<float>) noexcept {}
526 static void process(Runtime&,
527 const SampleHeritageVoicePitchBlock&,
528 BufferView<float>) noexcept {}
529 static void process(Runtime&,
530 const SampleHeritageVoiceLiveCyclicStretchBlock&,
531 BufferView<float>) noexcept {}
532
533 void process(Runtime& runtime,
534 const SampleHeritageVoiceConverterBlock& block,
535 BufferView<float> buffer) noexcept {
536 const auto levels = std::exp2(block.bit_depth - 1.0f);
537 const auto minimum_code = -std::floor(levels);
538 const auto maximum_code = std::floor(levels - 1.0f);
539 for (std::size_t frame = 0; frame < buffer.num_samples(); ++frame) {
540 for (std::size_t channel = 0; channel < buffer.num_channels(); ++channel) {
541 auto& sample = buffer.channel(channel)[frame];
542 const auto encoded = compress(sample, block.family);
543 // Dither is expressed in quantizer LSBs and is applied in the
544 // compressed code domain for every converter family.
545 const auto dither = block.dither_lsb == 0.0f
546 ? 0.0f
547 : detail::heritage_bipolar_random(runtime.random_state) *
548 block.dither_lsb;
549 const auto code = std::clamp(std::round(encoded * levels + dither),
550 minimum_code, maximum_code);
551 const auto use_cached_decode = has_companded_8bit_decode_ &&
552 std::isfinite(code) && code >= -128.0f && code <= 127.0f &&
553 !(code == 0.0f && std::signbit(code));
554 const auto decoded = use_cached_decode
555 ? companded_8bit_decode_[
556 static_cast<std::size_t>(static_cast<int>(code) + 128)]
557 : expand(code / levels, block.family);
558 sample = apply_dac_nonlinearity(
559 decoded, block.dac_nonlinearity);
560 }
561 }
562 }
563
564 static void process(Runtime& runtime,
565 const SampleHeritageVoiceHoldDroopBlock& block,
566 BufferView<float> buffer) noexcept {
567 const auto retention = 1.0f - block.droop;
568 for (std::size_t channel = 0; channel < buffer.num_channels(); ++channel) {
569 auto& held = runtime.hold_value[channel];
570 auto& phase = runtime.hold_phase[channel];
571 for (auto& sample : buffer.channel(channel)) {
572 if (phase == 0) held = sample;
573 sample = held;
574 held *= retention;
575 phase = (phase + 1) % block.hold_samples;
576 }
577 }
578 }
579
580 static void process(Runtime& runtime,
581 const SampleHeritageVoiceReconstructionBlock& block,
582 BufferView<float> buffer) noexcept {
584 const auto pole = runtime.one_pole_pole;
585 for (std::size_t channel = 0; channel < buffer.num_channels(); ++channel) {
586 auto state = runtime.one_pole_state[channel];
587 for (auto& sample : buffer.channel(channel)) {
588 state = (1.0f - pole) * sample + pole * state;
589 sample = state;
590 }
591 runtime.one_pole_state[channel] = state;
592 }
593 return;
594 }
595 for (std::size_t channel = 0; channel < buffer.num_channels(); ++channel) {
596 auto samples = buffer.channel(channel);
597 for (std::size_t section = 0; section < runtime.sos_count; ++section) {
598 const auto coefficients = runtime.sos[section];
599 auto z1 = runtime.sos_z1[channel][section];
600 auto z2 = runtime.sos_z2[channel][section];
601 for (auto& sample : samples) {
602 const auto input = sample;
603 const auto output = coefficients.b0 * input + z1;
604 z1 = coefficients.b1 * input - coefficients.a1 * output + z2;
605 z2 = coefficients.b2 * input - coefficients.a2 * output;
606 sample = output;
607 }
608 runtime.sos_z1[channel][section] = z1;
609 runtime.sos_z2[channel][section] = z2;
610 }
611 }
612 }
613
614 static void process(Runtime& runtime,
615 const SampleHeritageVoiceAnalogColorBlock& block,
616 BufferView<float> buffer) noexcept {
617 if (block.mix == 0.0f) return;
618 const auto bias = 1.5 * static_cast<double>(block.asymmetry);
619 const auto center = std::tanh(bias);
620 const auto drive = static_cast<double>(block.drive);
621 const auto positive_scale = std::tanh(drive + bias) - center;
622 const auto negative_scale = center - std::tanh(bias - drive);
623 constexpr auto minimum_scale =
624 16.0 * std::numeric_limits<double>::epsilon();
625 for (std::size_t channel = 0; channel < buffer.num_channels(); ++channel) {
626 for (auto& sample : buffer.channel(channel)) {
627 const auto dry = sample;
628 const auto filtered = block.filter_family ==
630 ? dry
631 : runtime.analog_filters[channel].process(dry);
632 const auto scale =
633 filtered >= 0.0f ? positive_scale : negative_scale;
634 const auto shifted = std::tanh(drive * filtered + bias) - center;
635 const auto wet = std::abs(scale) > minimum_scale
636 ? static_cast<float>(shifted / scale)
637 : filtered;
638 sample = dry + block.mix * (wet - dry);
639 }
640 }
641 }
642
643 SampleHeritagePreparedProfile profile_{};
644 std::array<Runtime, kSampleHeritageMaximumVoiceBlocks> runtimes_{};
645 std::array<float, 256> companded_8bit_decode_{};
646 double machine_sample_rate_ = 0.0;
647 double host_sample_rate_ = 0.0;
648 bool has_companded_8bit_decode_ = false;
649 bool prepared_ = false;
650};
651
652} // namespace pulp::audio
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
Prepared, fixed-memory processor for the typed per-voice heritage blocks.
Definition sample_heritage_voice_dsp.hpp:38
void process_machine_tail(BufferView< float > buffer) noexcept
Definition sample_heritage_voice_dsp.hpp:144
void process_before_live(BufferView< float > buffer, bool draining_tail=false) noexcept
Definition sample_heritage_voice_dsp.hpp:125
void process_after_live(BufferView< float > buffer, bool draining_tail=false) noexcept
Definition sample_heritage_voice_dsp.hpp:131
bool prepare(const SampleHeritagePreparedProfile &profile, double machine_sample_rate, double host_sample_rate) noexcept
Definition sample_heritage_voice_dsp.hpp:48
void reset() noexcept
Definition sample_heritage_voice_dsp.hpp:81
std::uint64_t tail_host_frames() const noexcept
Definition sample_heritage_voice_dsp.hpp:212
bool live_stage_active() const noexcept
Definition sample_heritage_voice_dsp.hpp:164
void release() noexcept
Definition sample_heritage_voice_dsp.hpp:72
static constexpr float kTailSilenceThreshold
Definition sample_heritage_voice_dsp.hpp:40
bool prepare(const SampleHeritagePreparedProfile &profile, double processing_sample_rate) noexcept
Definition sample_heritage_voice_dsp.hpp:43
void process_host_frame(BufferView< float > buffer, bool draining_tail=false) noexcept
Definition sample_heritage_voice_dsp.hpp:138
bool prepared() const noexcept
Definition sample_heritage_voice_dsp.hpp:98
std::uint64_t tail_machine_frames() const noexcept
Definition sample_heritage_voice_dsp.hpp:200
void process(BufferView< float > buffer) noexcept
Definition sample_heritage_voice_dsp.hpp:120
bool converter_continues_state(std::size_t index) const noexcept
Definition sample_heritage_voice_dsp.hpp:100
std::uint64_t tail_machine_domain_frames() const noexcept
Definition sample_heritage_voice_dsp.hpp:208
std::size_t live_stage_index() const noexcept
Definition sample_heritage_voice_dsp.hpp:153
void process_tail(BufferView< float > buffer) noexcept
Definition sample_heritage_voice_dsp.hpp:148
std::uint64_t converter_random_state(std::size_t index) const noexcept
Definition sample_heritage_voice_dsp.hpp:109
static constexpr double kMaximumTailSeconds
Definition sample_heritage_voice_dsp.hpp:41
bool restore_converter_random_state(std::size_t index, std::uint64_t state) noexcept
Definition sample_heritage_voice_dsp.hpp:113
High-order analog-prototype IIR design (Chebyshev Type I, Chebyshev Type II, Elliptic / Cauer) produc...
Definition impulse_response.hpp:69
std::uint64_t heritage_next_random(std::uint64_t &state) noexcept
Definition sample_heritage_voice_dsp.hpp:23
float heritage_bipolar_random(std::uint64_t &state) noexcept
Definition sample_heritage_voice_dsp.hpp:30
Definition analyzer_provider.hpp:12
@ MachineRateRatio
Definition sample_heritage_schema.hpp:47
@ ContinueSerializedState
Definition sample_heritage_schema.hpp:32
@ None
Definition sample_heritage_schema.hpp:61
SampleHeritageConverterFamily
Definition sample_heritage_schema.hpp:41
@ MuLaw
Definition sample_heritage_schema.hpp:43
@ LinearPcm
Definition sample_heritage_schema.hpp:42
@ OnePole
Definition sample_heritage_schema.hpp:50
@ Butterworth
Definition sample_heritage_schema.hpp:51
@ Elliptic
Definition sample_heritage_schema.hpp:53
constexpr double kSampleHeritageMaximumLadderCutoffRatio
Definition sample_heritage_schema.hpp:21
constexpr std::size_t kSampleHeritageMaximumChannels
Definition sample_heritage_schema.hpp:27
constexpr float kSampleHeritageMaximumLadderResonance
Definition sample_heritage_schema.hpp:22
@ center
Definition canvas.hpp:221
Bounded event contracts shared by host ingress and graph-authored nodes.
Definition anticipation_eligibility.hpp:9
@ second
Definition chord.hpp:40
@ a
Definition pitch.hpp:29
@ index
Definition gpu_pass.hpp:140
Definition generated_event_source.hpp:33
@ drive
Definition config.hpp:223
T cos(T x) noexcept
Counted std::cos. Bit-identical result.
Definition rt_work_counter.hpp:147
T sin(T x) noexcept
Counted std::sin. Bit-identical result.
Definition rt_work_counter.hpp:140
@ buffer
A borrowed static buffer with a traversable playhead.
Definition granular.hpp:110
@ first
Definition dither.hpp:23
@ bounded
Definition fm_operator_engine.hpp:35
LadderFilterT< float > LadderFilter
Definition ladder_filter.hpp:127
@ levels
Definition multi_channel_meter.hpp:48
void scale(const float *a, float scalar, float *dst, std::size_t count) noexcept
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
@ block
Filled glyph cell; the covered glyph is drawn in the bg color.
Definition caret.hpp:66
Definition sample_heritage_schema.hpp:296
std::size_t voice_count
Definition sample_heritage_schema.hpp:302
Definition sample_heritage_schema.hpp:79
static std::vector< Coefficients > elliptic_lowpass(int order, float freq_hz, float passband_ripple_db, float stopband_attn_db, float sample_rate)
Elliptic (Cauer) lowpass cascade — equiripple in both bands.
Definition iir_design.hpp:123