397 if (!(source.host_sample_rate >= 8000.0 && source.host_sample_rate <= 384000.0) ||
398 !std::isfinite(source.host_sample_rate)) {
402 const bool has_typed_blocks = !source.voice.empty() || !source.bus.empty() ||
403 !source.record_commit.empty();
404 if (has_typed_blocks && !source.stages.empty()) {
419 if (has_typed_blocks) {
420 const auto finite_range = [](
auto value,
auto minimum,
auto maximum) {
421 return std::isfinite(value) && value >= minimum && value <= maximum;
427 const auto validate_seed = [&](
float amount, std::uint64_t seed,
429 return valid_seed_policy(policy) &&
434 double voice_machine_rate = source.host_sample_rate;
435 for (
const auto& spec : source.voice) {
436 if (spec.bypass)
continue;
437 if (
const auto* machine =
438 std::get_if<SampleHeritageVoiceMachineDomainBlock>(&spec.parameters))
439 voice_machine_rate = machine->sample_rate;
443 double sample_rate) {
445 return std::isfinite(value) && value > 0.0 && value < 0.5;
447 std::isfinite(value) && value >= 1.0 &&
448 std::isfinite(sample_rate) && value < sample_rate * 0.5;
453 ? value * voice_machine_rate
456 const auto validate_ordered = [&](
const auto& blocks,
458 auto validate_block) {
459 std::array<bool, std::variant_size_v<std::decay_t<
decltype(blocks.front().parameters)>>> seen{};
460 std::size_t previous = 0;
461 bool have_previous =
false;
462 for (std::size_t index = 0; index < blocks.size(); ++index) {
465 if (blocks[index].domain != domain)
467 const auto type = blocks[index].parameters.index();
468 if (std::exchange(seen[type],
true))
470 if (have_previous && type < previous)
473 have_previous =
true;
474 if (!validate_block(blocks[index].parameters))
480 auto status = validate_ordered(
484 [&](const auto& block) {
485 using Block = std::decay_t<decltype(block)>;
486 if constexpr (std::is_same_v<Block,
487 SampleHeritageVoiceMachineDomainBlock>)
488 return finite_range(block.sample_rate, 8000.0, 384000.0);
489 else if constexpr (std::is_same_v<Block,
490 SampleHeritageVoiceClockBlock>)
491 return finite_range(block.ratio, 0.015625, 64.0);
492 else if constexpr (std::is_same_v<Block,
493 SampleHeritageVoicePitchBlock>)
494 return (block.family == SampleHeritagePitchFamily::VariableClock ||
495 block.family == SampleHeritagePitchFamily::DropRepeat ||
496 block.family == SampleHeritagePitchFamily::EarlyLinear) &&
497 finite_range(block.max_transpose_semitones, 0.0, 96.0);
498 else if constexpr (std::is_same_v<Block,
499 SampleHeritageVoiceConverterBlock>)
500 return (block.family == SampleHeritageConverterFamily::LinearPcm ||
501 block.family == SampleHeritageConverterFamily::MuLaw ||
502 block.family == SampleHeritageConverterFamily::ALaw) &&
503 finite_range(block.bit_depth, 4.0f, 16.0f) &&
504 finite_range(block.dac_nonlinearity, 0.0f, 1.0f) &&
505 finite_range(block.dither_lsb, 0.0f, 2.0f) &&
506 validate_seed(block.dither_lsb, block.seed,
508 else if constexpr (std::is_same_v<Block,
509 SampleHeritageVoiceHoldDroopBlock>)
510 return block.mode == SampleHeritageHoldMode::ZeroOrder &&
511 block.hold_samples >= 1 && block.hold_samples <= 65536 &&
512 finite_range(block.droop, 0.0f, 1.0f);
513 else if constexpr (std::is_same_v<Block,
514 SampleHeritageVoiceReconstructionBlock>)
515 return validate_cutoff(block.cutoff_law, block.cutoff_value,
516 voice_machine_rate) &&
517 resolved_voice_cutoff(block.cutoff_law,
518 block.cutoff_value) <
519 source.host_sample_rate * 0.5 &&
521 SampleHeritageReconstructionFamily::OnePole &&
522 block.order == 1 && block.ripple_db == 0.0f &&
523 block.stopband_attenuation_db == 0.0f) ||
525 SampleHeritageReconstructionFamily::Butterworth &&
526 block.order >= 2 && block.order <= 16 &&
527 (block.order & 1u) == 0 &&
528 block.ripple_db == 0.0f &&
529 block.stopband_attenuation_db == 0.0f) ||
531 SampleHeritageReconstructionFamily::Chebyshev &&
532 block.order >= 2 && block.order <= 16 &&
533 (block.order & 1u) == 0 &&
534 finite_range(block.ripple_db, 0.0f, 12.0f) &&
535 block.ripple_db > 0.0f &&
536 block.stopband_attenuation_db == 0.0f) ||
538 SampleHeritageReconstructionFamily::Elliptic &&
539 block.order >= 2 && block.order <= 16 &&
540 (block.order & 1u) == 0 &&
541 finite_range(block.ripple_db, 0.0f, 12.0f) &&
542 block.ripple_db > 0.0f &&
543 finite_range(block.stopband_attenuation_db,
544 block.ripple_db, 180.0f) &&
545 block.stopband_attenuation_db > block.ripple_db));
546 else if constexpr (std::is_same_v<Block,
547 SampleHeritageVoiceAnalogColorBlock>)
548 return finite_range(block.drive, 0.0f, 16.0f) &&
549 finite_range(block.asymmetry, -1.0f, 1.0f) &&
550 finite_range(block.mix, 0.0f, 1.0f) &&
551 ((block.filter_family ==
552 SampleHeritageAnalogFilterFamily::None &&
554 SampleHeritageCutoffLaw::FixedHz &&
555 block.cutoff_value == 0.0 &&
556 block.resonance == 0.0f) ||
557 (block.filter_family ==
558 SampleHeritageAnalogFilterFamily::Ladder4Pole &&
559 ((block.cutoff_law ==
560 SampleHeritageCutoffLaw::MachineRateRatio &&
561 std::isfinite(block.cutoff_value) &&
562 block.cutoff_value * voice_machine_rate >= 1.0 &&
563 block.cutoff_value <=
564 kSampleHeritageMaximumLadderCutoffRatio) ||
566 SampleHeritageCutoffLaw::FixedHz &&
567 std::isfinite(block.cutoff_value) &&
568 block.cutoff_value >= 1.0 &&
569 block.cutoff_value <=
571 kSampleHeritageMaximumLadderCutoffRatio)) &&
572 resolved_voice_cutoff(block.cutoff_law,
573 block.cutoff_value) <=
574 source.host_sample_rate *
575 kSampleHeritageMaximumLadderCutoffRatio &&
577 block.resonance, 0.0f,
578 kSampleHeritageMaximumLadderResonance)));
580 return finite_range(block.factor, 0.25, 20.0) &&
581 std::isfinite(block.cycle_ms) && block.cycle_ms > 0.0 &&
582 block.cycle_ms * voice_machine_rate * 0.001 <=
584 finite_range(block.splice_ms, 0.0, 20.0) &&
585 block.splice_ms <= block.cycle_ms * 0.5 &&
586 (block.shuffle_divisions == 0 ||
587 (block.shuffle_divisions >= 2 &&
588 block.shuffle_divisions <= 64)) &&
589 (block.pitch_mode == SampleHeritageLivePitchMode::Preserve ||
590 block.pitch_mode == SampleHeritageLivePitchMode::RateLinked) &&
591 valid_seed_policy(block.seed_policy) &&
592 !((block.shuffle_divisions != 0 ||
594 SampleHeritageSeedPolicy::ContinueSerializedState) &&
599 status = validate_ordered(
603 [&](const auto& block) {
604 using Block = std::decay_t<decltype(block)>;
605 if constexpr (std::is_same_v<Block,
606 SampleHeritageBusNoiseIdleBlock>)
607 return finite_range(block.noise_amplitude, 0.0f, 1.0f) &&
608 finite_range(block.idle_amplitude, 0.0f, 1.0f) &&
609 finite_range(block.tilt_db_per_octave, -24.0f,
611 std::isfinite(block.tilt_floor_hz) &&
612 block.tilt_floor_hz >= 1.0 &&
613 std::isfinite(block.tilt_reference_hz) &&
614 block.tilt_reference_hz >= block.tilt_floor_hz &&
615 block.tilt_reference_hz <
616 source.host_sample_rate * 0.5 &&
617 (block.gate == SampleHeritageNoiseGate::AlwaysOn ||
619 SampleHeritageNoiseGate::VoiceActive) &&
621 std::max(block.noise_amplitude,
622 block.idle_amplitude),
623 block.seed, block.seed_policy);
625 return finite_range(block.drive, 0.0f, 16.0f) &&
626 finite_range(block.ceiling, 0.001f, 4.0f);
629 double record_processing_rate = source.host_sample_rate;
631 status = validate_ordered(
635 [&](const auto& block) {
636 using Block = std::decay_t<decltype(block)>;
637 if constexpr (std::is_same_v<Block,
638 SampleHeritageRecordInputDriveClipBlock>)
639 return finite_range(block.drive, 0.0f, 16.0f) &&
640 finite_range(block.clip_level, 0.001f, 4.0f);
641 else if constexpr (std::is_same_v<Block,
642 SampleHeritageRecordRateBlock>) {
644 finite_range(block.sample_rate, 8000.0,
646 validate_cutoff(block.cutoff_law,
648 record_processing_rate) &&
649 ((block.filter_family ==
650 SampleHeritageRecordFilterFamily::OnePole &&
651 block.order == 1 && block.ripple_db == 0.0f &&
652 block.stopband_attenuation_db == 0.0f) ||
653 (block.filter_family ==
654 SampleHeritageRecordFilterFamily::Butterworth &&
655 block.order >= 2 && block.order <= 16 &&
656 (block.order & 1u) == 0 &&
657 block.ripple_db == 0.0f &&
658 block.stopband_attenuation_db == 0.0f) ||
659 (block.filter_family ==
660 SampleHeritageRecordFilterFamily::Chebyshev &&
661 block.order >= 2 && block.order <= 16 &&
662 (block.order & 1u) == 0 &&
663 finite_range(block.ripple_db, 0.0f, 12.0f) &&
664 block.ripple_db > 0.0f &&
665 block.stopband_attenuation_db == 0.0f) ||
666 (block.filter_family ==
667 SampleHeritageRecordFilterFamily::Elliptic &&
668 block.order >= 2 && block.order <= 16 &&
669 (block.order & 1u) == 0 &&
670 finite_range(block.ripple_db, 0.0f, 12.0f) &&
671 block.ripple_db > 0.0f &&
672 finite_range(block.stopband_attenuation_db,
673 block.ripple_db, 180.0f) &&
674 block.stopband_attenuation_db >
677 !source.record_commit[result.stage_index].bypass)
678 record_processing_rate = block.sample_rate;
681 else if constexpr (std::is_same_v<Block,
682 SampleHeritageRecordConverterBlock>)
683 return (block.family == SampleHeritageConverterFamily::LinearPcm ||
684 block.family == SampleHeritageConverterFamily::MuLaw ||
685 block.family == SampleHeritageConverterFamily::ALaw) &&
686 finite_range(block.bit_depth, 4.0f, 16.0f) &&
687 finite_range(block.dac_nonlinearity, 0.0f, 1.0f) &&
688 finite_range(block.dither_lsb, 0.0f, 2.0f) &&
689 validate_seed(block.dither_lsb, block.seed,
691 else if constexpr (std::is_same_v<
693 SampleHeritageRecordCommitCyclicStretchBlock>)
695 finite_range(block.factor, 0.25, 20.0) &&
696 block.cycle_samples >= 1 &&
697 block.cycle_samples <= 1048576 &&
698 block.crossfade_samples <= block.cycle_samples / 2 &&
699 ((block.zone_start_frame == 0 &&
700 block.zone_end_frame == 0) ||
701 block.zone_start_frame < block.zone_end_frame);
703 return finite_range(block.factor, 0.25, 20.0) &&
704 block.decision_hop_samples >= 1 &&
705 block.decision_hop_samples <= 1048576 &&
706 block.search_radius_samples <= 1048576 &&
707 block.search_stride_samples >= 1 &&
708 block.search_stride_samples <= 1048576 &&
709 block.crossfade_samples <= 524288 &&
710 block.crossfade_samples <= block.decision_hop_samples &&
711 block.quality <= 99 && block.width <= 99 &&
712 ((block.zone_start_frame == 0 &&
713 block.zone_end_frame == 0) ||
714 block.zone_start_frame < block.zone_end_frame);
717 if (status != SampleHeritageProfileStatus::Ok) {
718 result.status = status;
722 result.profile.schema_version = source.schema_version;
723 std::copy(source.profile_id.begin(), source.profile_id.end(),
724 result.profile.profile_id.begin());
725 result.profile.host_sample_rate =
726 source.host_sample_rate == 0.0 ? 0.0 : source.host_sample_rate;
727 result.profile.voice_count = source.voice.size();
728 result.profile.bus_count = source.bus.size();
729 result.profile.record_commit_count = source.record_commit.size();
730 std::copy(source.voice.begin(), source.voice.end(), result.profile.voice.begin());
731 std::copy(source.bus.begin(), source.bus.end(), result.profile.bus.begin());
732 std::copy(source.record_commit.begin(), source.record_commit.end(),
733 result.profile.record_commit.begin());
737 result.status = SampleHeritageProfileStatus::DigestUnavailable;
740 result.status = SampleHeritageProfileStatus::Ok;
741 result.stage_index = source.record_commit.size();
745 double machine_sample_rate = source.host_sample_rate;
746 double clock_ratio = 1.0;
747 for (
const auto& spec : source.stages) {
748 if (spec.bypass)
continue;
749 if (
const auto* machine =
750 std::get_if<SampleHeritageMachineDomainStage>(&spec.parameters)) {
751 machine_sample_rate = machine->sample_rate;
752 }
else if (
const auto* clock =
753 std::get_if<SampleHeritageClockPitchStage>(&spec.parameters)) {
754 clock_ratio = clock->ratio;
757 const auto clocked_sample_rate = machine_sample_rate * clock_ratio;
759 std::array<bool, kSampleHeritageMaximumStages> seen{};
760 for (std::size_t index = 0;
index < source.stages.size(); ++
index) {
761 const auto type = std::visit(
762 [](
const auto& stage)
noexcept {
763 using Stage = std::decay_t<
decltype(stage)>;
764 return detail::stage_type_index<Stage>;
766 source.stages[
index].parameters);
767 if (std::exchange(seen[type],
true)) {
768 result.status = SampleHeritageProfileStatus::DuplicateStage;
769 result.stage_index =
index;
774 std::size_t previous_type = 0;
775 bool have_previous_type =
false;
776 for (std::size_t index = 0;
index < source.stages.size(); ++
index) {
777 result.stage_index =
index;
778 const auto status = std::visit(
779 [&](
const auto& stage)
noexcept {
780 using Stage = std::decay_t<
decltype(stage)>;
781 constexpr auto type = detail::stage_type_index<Stage>;
782 if (have_previous_type && type < previous_type)
783 return SampleHeritageProfileStatus::InvalidStageOrder;
784 previous_type =
type;
785 have_previous_type =
true;
786 if constexpr (std::is_same_v<Stage, SampleHeritageMachineDomainStage>) {
787 if (!(stage.sample_rate >= 8000.0 && stage.sample_rate <= 384000.0) ||
788 !std::isfinite(stage.sample_rate))
789 return SampleHeritageProfileStatus::InvalidStageParameter;
790 }
else if constexpr (std::is_same_v<
Stage,
791 SampleHeritageQuantizationStage>) {
792 if (stage.bit_depth < 2 || stage.bit_depth > 24 ||
793 !(stage.dither_lsb >= 0.0f && stage.dither_lsb <= 2.0f) ||
794 !std::isfinite(stage.dither_lsb) ||
795 (stage.seed_policy !=
796 SampleHeritageSeedPolicy::RestartFromProfileSeed &&
798 SampleHeritageSeedPolicy::ContinueSerializedState) ||
799 ((stage.dither_lsb > 0.0f ||
801 SampleHeritageSeedPolicy::ContinueSerializedState) &&
803 return SampleHeritageProfileStatus::InvalidStageParameter;
804 }
else if constexpr (std::is_same_v<Stage, SampleHeritageClockPitchStage>) {
805 if (!(stage.ratio > 0.0) || !std::isfinite(stage.ratio))
806 return SampleHeritageProfileStatus::InvalidStageParameter;
807 }
else if constexpr (std::is_same_v<Stage, SampleHeritageDacHoldStage>) {
808 if (stage.hold_samples == 0 || stage.hold_samples > 65536)
809 return SampleHeritageProfileStatus::InvalidStageParameter;
810 }
else if constexpr (std::is_same_v<
Stage,
811 SampleHeritageReconstructionFilterStage>) {
812 if (!(stage.cutoff_hz > 0.0 &&
813 stage.cutoff_hz < clocked_sample_rate * 0.5) ||
814 !std::isfinite(stage.cutoff_hz))
815 return SampleHeritageProfileStatus::InvalidStageParameter;
816 }
else if constexpr (std::is_same_v<Stage, SampleHeritageNoiseStage>) {
817 if (!(stage.amplitude >= 0.0f && stage.amplitude <= 1.0f) ||
818 !std::isfinite(stage.amplitude) ||
819 (stage.seed_policy !=
820 SampleHeritageSeedPolicy::RestartFromProfileSeed &&
822 SampleHeritageSeedPolicy::ContinueSerializedState) ||
823 ((stage.amplitude > 0.0f ||
825 SampleHeritageSeedPolicy::ContinueSerializedState) &&
827 return SampleHeritageProfileStatus::InvalidStageParameter;
828 }
else if constexpr (std::is_same_v<Stage, SampleHeritageOutputStage>) {
829 if (!(stage.gain >= 0.0f && stage.gain <= 16.0f) ||
830 !std::isfinite(stage.gain))
831 return SampleHeritageProfileStatus::InvalidStageParameter;
833 return SampleHeritageProfileStatus::Ok;
835 source.stages[
index].parameters);
836 if (status != SampleHeritageProfileStatus::Ok) {
837 result.status = status;
842 const auto host_to_machine = source.host_sample_rate / machine_sample_rate;
843 const auto clocked_to_host = clocked_sample_rate / source.host_sample_rate;
844 if (!(host_to_machine > 0.0 && host_to_machine <= 128.0) ||
845 !(clocked_to_host > 0.0 && clocked_to_host <= 128.0) ||
846 !std::isfinite(host_to_machine) || !std::isfinite(clocked_to_host)) {
847 result.status = SampleHeritageProfileStatus::UnsupportedRateConversion;
848 result.stage_index = source.stages.size();
852 result.profile.schema_version = source.schema_version;
853 std::copy(source.profile_id.begin(), source.profile_id.end(),
854 result.profile.profile_id.begin());
855 const auto canonical_zero = []<
typename Number>(Number
value)
noexcept {
856 return value == Number{0} ? Number{0} :
value;
858 result.profile.host_sample_rate = canonical_zero(source.host_sample_rate);
859 result.profile.stage_count = source.stages.size();
860 std::copy(source.stages.begin(), source.stages.end(), result.profile.stages.begin());
861 for (std::size_t index = 0;
index < result.profile.stage_count; ++
index) {
863 [&](
auto& stage)
noexcept {
864 using Stage = std::decay_t<
decltype(stage)>;
865 if constexpr (std::is_same_v<Stage, SampleHeritageMachineDomainStage>)
866 stage.sample_rate = canonical_zero(stage.sample_rate);
867 else if constexpr (std::is_same_v<Stage, SampleHeritageQuantizationStage>)
868 stage.dither_lsb = canonical_zero(stage.dither_lsb);
869 else if constexpr (std::is_same_v<Stage, SampleHeritageClockPitchStage>)
870 stage.ratio = canonical_zero(stage.ratio);
871 else if constexpr (std::is_same_v<
Stage,
872 SampleHeritageReconstructionFilterStage>)
873 stage.cutoff_hz = canonical_zero(stage.cutoff_hz);
874 else if constexpr (std::is_same_v<Stage, SampleHeritageNoiseStage>)
875 stage.amplitude = canonical_zero(stage.amplitude);
876 else if constexpr (std::is_same_v<Stage, SampleHeritageOutputStage>)
877 stage.gain = canonical_zero(stage.gain);
878 }, result.profile.stages[
index].parameters);
883 result.status = SampleHeritageProfileStatus::DigestUnavailable;
886 result.status = SampleHeritageProfileStatus::Ok;
887 result.stage_index = source.stages.size();