Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
unison.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <cmath>
5#include <cstddef>
6#include <cstdint>
7
8#include <pulp/signal/rng.hpp>
9
10namespace pulp::signal {
11
12enum class UnisonGainLaw : std::uint8_t { PeakSafe, EqualPower };
13
20struct UnisonSpec {
21 std::size_t voice_count = 1;
22 double detune_cents = 0.0;
23 double stereo_spread = 0.0;
24 double phase_randomization = 0.0;
25 double drift_cents = 0.0;
26 std::uint64_t drift_period_frames = 2048;
28};
29
31 double detune_cents = 0.0;
32 double pan = 0.0;
33 double gain = 1.0;
34 double phase = 0.0;
35};
36
37template <std::size_t MaximumVoices = 16>
39public:
40 static_assert(MaximumVoices > 0);
41
42 bool configure(const UnisonSpec& spec, std::uint64_t seed = 0,
43 std::uint64_t note_instance_id = 0) noexcept {
44 if (spec.voice_count == 0 || spec.voice_count > MaximumVoices ||
45 !finite_nonnegative(spec.detune_cents) || !unit(spec.stereo_spread) ||
46 !unit(spec.phase_randomization) || !finite_nonnegative(spec.drift_cents) ||
47 (spec.drift_cents > 0.0 && spec.drift_period_frames == 0) ||
48 !valid_gain_law(spec.gain_law)) {
49 return false;
50 }
51 std::array<UnisonVoiceParameters, MaximumVoices> next{};
52 const double gain = spec.gain_law == UnisonGainLaw::PeakSafe
53 ? 1.0 / static_cast<double>(spec.voice_count)
54 : 1.0 / std::sqrt(static_cast<double>(spec.voice_count));
55 for (std::size_t i = 0; i < spec.voice_count; ++i) {
56 const double position = spec.voice_count == 1
57 ? 0.0
58 : -1.0 + 2.0 * static_cast<double>(i) /
59 static_cast<double>(spec.voice_count - 1);
60 const auto key = mix64(seed, note_instance_id, i + 0x554e49534f4eull);
61 next[i] = {position * spec.detune_cents,
62 position * spec.stereo_spread,
63 gain,
64 spec.voice_count == 1 ? 0.0
65 : unit_from(key) * spec.phase_randomization};
66 }
67 voices_ = next;
68 spec_ = spec;
69 seed_ = seed;
70 note_instance_id_ = note_instance_id;
71 configured_ = true;
72 return true;
73 }
74
75 bool configured() const noexcept { return configured_; }
76 std::size_t size() const noexcept { return configured_ ? spec_.voice_count : 0; }
77 const UnisonVoiceParameters& operator[](std::size_t index) const noexcept {
78 return voices_[index];
79 }
80 const UnisonSpec& spec() const noexcept { return spec_; }
81
82 // Stateless absolute-frame drift makes results independent of callback
83 // partitioning. Adjacent deterministic knots are smoothly interpolated.
84 double drift_cents(std::size_t voice, std::uint64_t absolute_frame) const noexcept {
85 if (!configured_ || voice >= spec_.voice_count || spec_.drift_cents == 0.0)
86 return 0.0;
87 const auto period = spec_.drift_period_frames;
88 const auto knot = absolute_frame / period;
89 double t = static_cast<double>(absolute_frame % period) / static_cast<double>(period);
90 t = t * t * (3.0 - 2.0 * t);
91 const auto base = mix64(seed_, note_instance_id_, voice + 0x4452494654ull);
92 const double a = bipolar_from(mix64(base, knot, 0));
93 const double b = bipolar_from(mix64(base, knot + 1, 0));
94 return ((1.0 - t) * a + t * b) * spec_.drift_cents;
95 }
96
97private:
98 static bool valid_gain_law(UnisonGainLaw law) noexcept {
100 }
101 static bool finite_nonnegative(double value) noexcept {
102 return std::isfinite(value) && value >= 0.0;
103 }
104 static bool unit(double value) noexcept {
105 return std::isfinite(value) && value >= 0.0 && value <= 1.0;
106 }
107
108 std::array<UnisonVoiceParameters, MaximumVoices> voices_{};
109 UnisonSpec spec_{};
110 std::uint64_t seed_ = 0;
111 std::uint64_t note_instance_id_ = 0;
112 bool configured_ = false;
113};
114
115} // namespace pulp::signal
Definition unison.hpp:38
std::size_t size() const noexcept
Definition unison.hpp:76
const UnisonVoiceParameters & operator[](std::size_t index) const noexcept
Definition unison.hpp:77
double drift_cents(std::size_t voice, std::uint64_t absolute_frame) const noexcept
Definition unison.hpp:84
bool configured() const noexcept
Definition unison.hpp:75
bool configure(const UnisonSpec &spec, std::uint64_t seed=0, std::uint64_t note_instance_id=0) noexcept
Definition unison.hpp:42
const UnisonSpec & spec() const noexcept
Definition unison.hpp:80
Definition additive_bank.hpp:218
UnisonGainLaw
Definition unison.hpp:12
@ EqualPower
Definition unison.hpp:12
@ PeakSafe
Definition unison.hpp:12
constexpr std::uint64_t mix64(std::uint64_t z)
splitmix64 finalizer (Steele, Lea & Flood, OOPSLA 2014).
Definition rng.hpp:140
@ EqualPower
(cos(u·π/2), sin(u·π/2)) — power sum == 1.
Definition crossfade.hpp:43
@ a
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
T bipolar_from(std::uint64_t key)
Definition rng.hpp:175
float unit_from(std::uint64_t key)
Stateless purpose-keyed draw in [0, 1).
Definition rng.hpp:164
Deterministic randomness for modulation: a counter-free PRNG, a Gaussian draw, a stateless purpose-ke...
Control-side configuration for a fixed unison layout.
Definition unison.hpp:20
std::uint64_t drift_period_frames
Definition unison.hpp:26
UnisonGainLaw gain_law
Definition unison.hpp:27
double phase_randomization
Definition unison.hpp:24
std::size_t voice_count
Definition unison.hpp:21
double drift_cents
Definition unison.hpp:25
double stereo_spread
Definition unison.hpp:23
double detune_cents
Definition unison.hpp:22
Definition unison.hpp:30
double gain
Definition unison.hpp:33
double pan
Definition unison.hpp:32
double phase
Definition unison.hpp:34
double detune_cents
Definition unison.hpp:31