Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
particle_percussion_voice.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <algorithm>
8#include <array>
9#include <cmath>
10#include <cstddef>
11#include <cstdint>
12#include <limits>
13#include <span>
14#include <type_traits>
15
16namespace pulp::signal {
17
25template <typename SampleType = float>
27 static_assert(std::is_floating_point_v<SampleType>);
28
29public:
30 enum class Model : std::uint8_t { shaker, maraca, rattle, tambourine };
31
32 static constexpr std::size_t kMaxBands = 4;
33 static constexpr std::size_t kTambourineModes = 5;
34 static constexpr double kMinSupportedSampleRate =
36
38 configure_model();
39 apply_routing_seed();
40 reset();
41 }
42
45 void prepare(double sample_rate) {
46 if (std::isfinite(sample_rate) && sample_rate >= kMinSupportedSampleRate)
47 sample_rate_ = sample_rate;
48 exciter_.prepare(sample_rate_);
49 modal_bank_.prepare(sample_rate_, static_cast<int>(kTambourineModes), 1);
50 prepared_ = true;
51 configure_model();
52 reset();
53 }
54
57 switch (model) {
58 case Model::shaker:
59 case Model::maraca:
60 case Model::rattle:
62 model_ = model;
63 break;
64 default:
65 return;
66 }
67 configure_model();
68 reset();
69 }
70
71 void set_seed(std::uint64_t seed) noexcept {
72 seed_ = seed;
73 exciter_.set_seed(seed_);
74 apply_routing_seed();
75 reset();
76 }
77
78 void set_particle_count(int count) noexcept { exciter_.set_particle_count(count); }
79 void set_event_rate(SampleType rate) noexcept { exciter_.set_event_rate(rate); }
80 void set_energy_decay(SampleType milliseconds) noexcept {
81 exciter_.set_energy_decay(milliseconds);
82 }
83 void set_sustain_floor(SampleType floor) noexcept { exciter_.set_sustain_floor(floor); }
84 void set_collision_energy_loss(SampleType loss) noexcept {
85 exciter_.set_collision_energy_loss(loss);
86 }
87
88 void reset() noexcept {
89 exciter_.reset();
90 routing_rng_.reset();
91 for (auto& resonator : resonators_)
92 resonator.reset();
93 modal_bank_.reset();
94 }
95
96 void excite(SampleType amount01) noexcept { exciter_.excite(amount01); }
97
98 SampleType process() noexcept {
99 const auto event = exciter_.next();
100 if (model_ == Model::tambourine) {
101 const SampleType input = event.occurred ? event.amplitude : SampleType{};
102 SampleType output{};
103 modal_bank_.process_add(&input, &output, 1);
104 return std::isfinite(static_cast<double>(output)) ? output : SampleType{};
105 }
106
107 std::size_t selected = kMaxBands;
108 if (event.occurred)
109 selected = select_band();
110
111 SampleType output{};
112 for (std::size_t band = 0; band < kMaxBands; ++band) {
113 const SampleType input = selected == band
114 ? event.amplitude * bands_[band].gain
115 : SampleType{};
116 output += resonators_[band].process(input);
117 }
118 return std::isfinite(static_cast<double>(output)) ? output : SampleType{};
119 }
120
121 void process(SampleType* output, int num_samples) noexcept {
122 if (output == nullptr || num_samples <= 0)
123 return;
124 for (int sample = 0; sample < num_samples; ++sample)
125 output[sample] = process();
126 }
127
128 [[nodiscard]] Model model() const noexcept { return model_; }
129 [[nodiscard]] double sample_rate() const noexcept { return sample_rate_; }
130 [[nodiscard]] bool prepared() const noexcept { return prepared_; }
131
132 static constexpr int latency_samples() noexcept { return 0; }
133
136 [[nodiscard]] std::size_t retained_bytes() const noexcept {
137 if (!prepared_)
138 return 0;
139 constexpr std::size_t bytes_per_buffer =
140 ((ModalBankT<SampleType>::kLanes * sizeof(SampleType) + kSimdAlignment - 1) /
142 return 7u * bytes_per_buffer;
143 }
144
147 [[nodiscard]] int tail_samples() const noexcept {
148 const int exciter_tail = exciter_.tail_samples();
149 if (exciter_tail < 0)
150 return -1;
151 const double body_samples = std::ceil(6.0 * longest_body_t60_seconds_ * sample_rate_);
152 const double total = static_cast<double>(exciter_tail) + body_samples;
153 return total >= static_cast<double>(std::numeric_limits<int>::max())
154 ? std::numeric_limits<int>::max()
155 : static_cast<int>(total);
156 }
157
158private:
159 struct Band {
160 double frequency_hz;
161 double q;
162 SampleType gain;
163 double weight;
164 };
165
166 static constexpr std::array<Band, kMaxBands> kShaker{{
167 {2200.0, 1.5, SampleType{1.0}, 0.35},
168 {4200.0, 2.0, SampleType{0.8}, 0.30},
169 {6800.0, 1.8, SampleType{0.6}, 0.20},
170 {9500.0, 1.2, SampleType{0.4}, 0.15},
171 }};
172 static constexpr std::array<Band, kMaxBands> kMaraca{{
173 {2900.0, 3.0, SampleType{1.0}, 0.40},
174 {5100.0, 2.5, SampleType{0.5}, 0.25},
175 {7600.0, 2.0, SampleType{0.3}, 0.20},
176 {10400.0, 1.5, SampleType{0.2}, 0.15},
177 }};
178 static constexpr std::array<Band, kMaxBands> kRattle{{
179 {550.0, 4.0, SampleType{1.0}, 0.30},
180 {950.0, 3.5, SampleType{0.7}, 0.30},
181 {1600.0, 3.0, SampleType{0.5}, 0.25},
182 {2600.0, 2.5, SampleType{0.3}, 0.15},
183 }};
184
185 static constexpr std::array<ModalMode, kTambourineModes> kTambourine{{
186 {3200.0f, 0.180f, 1.0f},
187 {7584.0f, 0.140f, 0.7f},
188 {12416.0f, 0.100f, 0.5f},
189 {17952.0f, 0.070f, 0.35f},
190 {23328.0f, 0.045f, 0.2f},
191 }};
192
193 void configure_model() {
194 if (model_ == Model::tambourine) {
195 modal_bank_.set_modes(std::span<const ModalMode>(kTambourine));
196 longest_body_t60_seconds_ = 0.180;
197 return;
198 }
199
200 switch (model_) {
201 case Model::shaker: bands_ = kShaker; break;
202 case Model::maraca: bands_ = kMaraca; break;
203 case Model::rattle: bands_ = kRattle; break;
204 case Model::tambourine: break;
205 }
206
207 longest_body_t60_seconds_ = 0.0;
208 for (std::size_t band = 0; band < kMaxBands; ++band) {
209 const double maximum_frequency = std::max(1.0, 0.499 * sample_rate_);
210 const double effective_frequency =
211 std::clamp(bands_[band].frequency_hz, 1.0, maximum_frequency);
212 const double t60_ms = 1000.0 * 3.0 * std::log(10.0) * bands_[band].q /
213 (3.14159265358979323846 * effective_frequency);
214 resonators_[band].set_sample_rate(sample_rate_);
215 resonators_[band].set_frequency(effective_frequency);
216 resonators_[band].set_t60_ms(t60_ms);
217 longest_body_t60_seconds_ = std::max(longest_body_t60_seconds_, 0.001 * t60_ms);
218 }
219 }
220
221 std::size_t select_band() noexcept {
222 const double draw = 0.5 * (static_cast<double>(routing_rng_.white()) + 1.0);
223 double total = 0.0;
224 for (const auto& band : bands_)
225 total += band.weight;
226 double cumulative = 0.0;
227 for (std::size_t band = 0; band + 1 < kMaxBands; ++band) {
228 cumulative += bands_[band].weight / total;
229 if (draw < cumulative)
230 return band;
231 }
232 return kMaxBands - 1;
233 }
234
235 void apply_routing_seed() noexcept {
236 const std::uint64_t mixed = mix64(seed_, 0u, 0x524F5554494E47ull);
237 const auto folded = static_cast<std::uint32_t>(mixed ^ (mixed >> 32));
238 routing_rng_.set_seed(folded == 0u ? NoiseSourceT<double>::default_seed : folded);
239 }
240
241 ParticleCollisionExciterT<SampleType> exciter_;
242 std::array<TwoPoleResonatorT<SampleType>, kMaxBands> resonators_;
243 ModalBankT<SampleType> modal_bank_;
244 NoiseSourceT<double> routing_rng_;
245 std::array<Band, kMaxBands> bands_ = kShaker;
247 double longest_body_t60_seconds_ = 0.0;
249 Model model_ = Model::shaker;
250 bool prepared_ = false;
251};
252
255
256} // namespace pulp::signal
static constexpr int kLanes
Lane-group width.
Definition modal_bank.hpp:158
static constexpr std::uint32_t default_seed
Default seed.
Definition noise_source.hpp:55
static constexpr double kMinSupportedSampleRate
Definition particle_collision_exciter.hpp:30
static constexpr double kDefaultSampleRate
Definition particle_collision_exciter.hpp:29
static constexpr std::uint64_t kDefaultSeed
Definition particle_collision_exciter.hpp:31
Single particle-percussion voice composed from the shared collision model and Pulp's existing resonat...
Definition particle_percussion_voice.hpp:26
void set_energy_decay(SampleType milliseconds) noexcept
Definition particle_percussion_voice.hpp:80
static constexpr std::size_t kMaxBands
Definition particle_percussion_voice.hpp:32
std::size_t retained_bytes() const noexcept
Heap storage retained by the prepared five-mode ModalBankT.
Definition particle_percussion_voice.hpp:136
static constexpr std::size_t kTambourineModes
Definition particle_percussion_voice.hpp:33
Model model() const noexcept
Definition particle_percussion_voice.hpp:128
static constexpr int latency_samples() noexcept
Definition particle_percussion_voice.hpp:132
Model
Definition particle_percussion_voice.hpp:30
@ shaker
Definition particle_percussion_voice.hpp:30
@ tambourine
Definition particle_percussion_voice.hpp:30
@ rattle
Definition particle_percussion_voice.hpp:30
@ maraca
Definition particle_percussion_voice.hpp:30
SampleType process() noexcept
Definition particle_percussion_voice.hpp:98
bool prepared() const noexcept
Definition particle_percussion_voice.hpp:130
void reset() noexcept
Definition particle_percussion_voice.hpp:88
int tail_samples() const noexcept
Conservative render tail: remaining collision-envelope settling plus six T60 intervals for the longes...
Definition particle_percussion_voice.hpp:147
void set_collision_energy_loss(SampleType loss) noexcept
Definition particle_percussion_voice.hpp:84
void set_seed(std::uint64_t seed) noexcept
Definition particle_percussion_voice.hpp:71
void set_particle_count(int count) noexcept
Definition particle_percussion_voice.hpp:78
ParticlePercussionVoiceT()
Definition particle_percussion_voice.hpp:37
void set_model(Model model)
Changes the body recipe and clears the voice's exciter and resonators.
Definition particle_percussion_voice.hpp:56
void excite(SampleType amount01) noexcept
Definition particle_percussion_voice.hpp:96
void set_sustain_floor(SampleType floor) noexcept
Definition particle_percussion_voice.hpp:83
static constexpr double kMinSupportedSampleRate
Definition particle_percussion_voice.hpp:34
void prepare(double sample_rate)
Prepares at 8 kHz or above.
Definition particle_percussion_voice.hpp:45
void set_event_rate(SampleType rate) noexcept
Definition particle_percussion_voice.hpp:79
double sample_rate() const noexcept
Definition particle_percussion_voice.hpp:129
void process(SampleType *output, int num_samples) noexcept
Definition particle_percussion_voice.hpp:121
Definition additive_bank.hpp:218
static constexpr size_t kSimdAlignment
Alignment in bytes for SIMD operations (covers AVX-512).
Definition simd_buffer.hpp:34
ParticlePercussionVoiceT< float > ParticlePercussionVoice
Definition particle_percussion_voice.hpp:253
constexpr std::uint64_t mix64(std::uint64_t z)
splitmix64 finalizer (Steele, Lea & Flood, OOPSLA 2014).
Definition rng.hpp:140
ParticlePercussionVoiceT< double > ParticlePercussionVoice64
Definition particle_percussion_voice.hpp:254
@ u
Definition formant_filter_bank.hpp:20
@ mixed
Definition accessibility.hpp:132