44 double processing_sample_rate)
noexcept {
45 return prepare(profile, processing_sample_rate, processing_sample_rate);
49 double machine_sample_rate,
50 double host_sample_rate)
noexcept {
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))
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];
75 has_companded_8bit_decode_ =
false;
76 machine_sample_rate_ = 0.0;
77 host_sample_rate_ = 0.0;
82 if (!prepared_)
return;
83 for (std::size_t index = 0; index < profile_.voice_count; ++index) {
84 auto&
runtime = runtimes_[index];
87 runtime.one_pole_state.fill(0.0f);
90 for (
auto& filter :
runtime.analog_filters) filter.reset();
91 if (
const auto* converter =
92 std::get_if<SampleHeritageVoiceConverterBlock>(
94 runtime.random_state = converter->seed;
98 bool prepared() const noexcept {
return prepared_; }
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 ==
114 std::uint64_t
state)
noexcept {
116 runtimes_[index].random_state =
state;
121 process_range(buffer, 0, profile_.voice_count,
false, Frame::Machine);
122 process_range(buffer, 0, profile_.voice_count,
false, Frame::Host);
126 bool draining_tail =
false) noexcept {
132 bool draining_tail =
false) noexcept {
134 process_range(buffer, live < profile_.voice_count ? live + 1 : live,
135 profile_.voice_count, draining_tail, Frame::Machine);
139 bool draining_tail =
false) noexcept {
140 process_range(buffer, 0, profile_.voice_count, draining_tail,
145 process_range(buffer, 0, profile_.voice_count,
true, Frame::Machine);
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))
161 return profile_.voice_count;
169 enum class Frame : std::uint8_t { Machine, Host };
171 static bool belongs_to_frame(
const SampleHeritageVoiceBlockSpec& block,
172 Frame frame)
noexcept {
174 std::holds_alternative<SampleHeritageVoiceReconstructionBlock>(
176 std::holds_alternative<SampleHeritageVoiceAnalogColorBlock>(
178 return host == (frame == Frame::Host);
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;
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) ||
189 std::holds_alternative<SampleHeritageVoiceConverterBlock>(
190 runtime.spec.parameters)))
193 [&](
const auto& block)
noexcept {
process(runtime, block, buffer); },
194 runtime.spec.parameters);
203 return machine > std::numeric_limits<std::uint64_t>::max() -
host
204 ? std::numeric_limits<std::uint64_t>::max()
209 return tail_frames(Frame::Machine, machine_sample_rate_);
213 return tail_frames(Frame::Host, host_sample_rate_);
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))
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>(
240 frames += decay *
static_cast<long double>(
241 std::max<std::size_t>(1,
runtime.sos_count + 1));
243 }
else if constexpr (
244 std::is_same_v<Block,
245 SampleHeritageVoiceAnalogColorBlock>) {
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>(
259 runtime.spec.parameters);
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));
266 static constexpr std::size_t kMaximumSos = 8;
268 struct SosCoefficients {
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;
292 float analog_decay_pole = 0.0f;
295 bool initialize(Runtime& runtime)
noexcept {
296 if (runtime.spec.bypass)
return true;
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,
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>) {
323 }
else if constexpr (
324 std::is_same_v<Block,
325 SampleHeritageVoiceAnalogColorBlock>) {
326 return initialize_analog_filter(runtime, block);
330 runtime.spec.parameters);
333 double resolve_cutoff(
334 const SampleHeritageVoiceReconstructionBlock& block)
const noexcept {
336 ?
block.cutoff_value * machine_sample_rate_
337 :
block.cutoff_value;
340 double resolve_cutoff(
341 const SampleHeritageVoiceAnalogColorBlock& block)
const noexcept {
343 ?
block.cutoff_value * machine_sample_rate_
344 :
block.cutoff_value;
347 bool initialize_analog_filter(
349 const SampleHeritageVoiceAnalogColorBlock& block)
noexcept {
351 runtime.analog_decay_pole = 0.0f;
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))
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);
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);
376 bool initialize_filter(
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))
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;
390 runtime.sos_count =
block.order / 2;
391 if (runtime.sos_count == 0 || runtime.sos_count > runtime.sos.size())
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;
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) /
411 const auto second = (-
static_cast<double>(source.a1) - roots) /
413 runtime.maximum_pole_radius = std::max(
414 runtime.maximum_pole_radius,
416 std::max(std::abs(first), std::abs(second))));
418 return runtime.maximum_pole_radius < 1.0f &&
419 std::isfinite(runtime.maximum_pole_radius);
424 const auto warped = std::tan(std::numbers::pi * cutoff /
426 const auto epsilon =
block.family ==
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)
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
440 : -std::sinh(mu) * std::
sin(theta);
441 const auto imaginary = epsilon == 0.0
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) /
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);
460 const auto roots = std::sqrt(std::complex<double>(
461 coefficients.a1 * coefficients.a1 -
462 4.0 * coefficients.a2,
464 const auto first = (-
static_cast<double>(coefficients.a1) + roots) /
466 const auto second = (-
static_cast<double>(coefficients.a1) - roots) /
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))
478 static float compress(
float sample,
480 sample = std::clamp(sample, -1.0f, 1.0f);
481 const auto magnitude = std::abs(sample);
484 constexpr float mu = 255.0f;
485 return std::copysign(std::log1p(mu * magnitude) / std::log1p(mu),
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);
496 static float expand(
float sample,
498 sample = std::clamp(sample, -1.0f, 1.0f);
499 const auto magnitude = std::abs(sample);
502 constexpr float mu = 255.0f;
503 return std::copysign(std::expm1(magnitude * std::log1p(mu)) / mu,
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);
515 static float apply_dac_nonlinearity(
float sample,
float amount)
noexcept {
516 const auto bounded = std::clamp(sample, -1.0f, 1.0f);
521 const SampleHeritageVoiceMachineDomainBlock&,
522 BufferView<float>)
noexcept {}
524 const SampleHeritageVoiceClockBlock&,
525 BufferView<float>)
noexcept {}
527 const SampleHeritageVoicePitchBlock&,
528 BufferView<float>)
noexcept {}
530 const SampleHeritageVoiceLiveCyclicStretchBlock&,
531 BufferView<float>)
noexcept {}
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);
545 const auto dither =
block.dither_lsb == 0.0f
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)]
558 sample = apply_dac_nonlinearity(
559 decoded,
block.dac_nonlinearity);
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;
575 phase = (phase + 1) %
block.hold_samples;
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;
591 runtime.one_pole_state[channel] = state;
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;
608 runtime.sos_z1[channel][section] = z1;
609 runtime.sos_z2[channel][section] = z2;
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 ==
631 : runtime.analog_filters[channel].process(dry);
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)
638 sample = dry +
block.mix * (wet - dry);
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;