27 static_assert(std::is_floating_point_v<SampleType>);
48 exciter_.prepare(sample_rate_);
73 exciter_.set_seed(seed_);
79 void set_event_rate(SampleType rate)
noexcept { exciter_.set_event_rate(rate); }
81 exciter_.set_energy_decay(milliseconds);
85 exciter_.set_collision_energy_loss(loss);
91 for (
auto& resonator : resonators_)
96 void excite(SampleType amount01)
noexcept { exciter_.excite(amount01); }
99 const auto event = exciter_.next();
101 const SampleType input =
event.occurred ?
event.amplitude : SampleType{};
103 modal_bank_.process_add(&input, &output, 1);
104 return std::isfinite(
static_cast<double>(output)) ? output : SampleType{};
109 selected = select_band();
112 for (std::size_t band = 0; band <
kMaxBands; ++band) {
113 const SampleType input = selected == band
114 ?
event.amplitude * bands_[band].gain
116 output += resonators_[band].process(input);
118 return std::isfinite(
static_cast<double>(output)) ? output : SampleType{};
121 void process(SampleType* output,
int num_samples)
noexcept {
122 if (output ==
nullptr || num_samples <= 0)
124 for (
int sample = 0; sample < num_samples; ++sample)
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_; }
139 constexpr std::size_t bytes_per_buffer =
142 return 7u * bytes_per_buffer;
148 const int exciter_tail = exciter_.tail_samples();
149 if (exciter_tail < 0)
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);
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},
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},
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},
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},
193 void configure_model() {
195 modal_bank_.set_modes(std::span<const ModalMode>(kTambourine));
196 longest_body_t60_seconds_ = 0.180;
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);
221 std::size_t select_band() noexcept {
222 const double draw = 0.5 * (
static_cast<double>(routing_rng_.white()) + 1.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)
235 void apply_routing_seed() noexcept {
236 const std::uint64_t
mixed =
mix64(seed_, 0
u, 0x524F5554494E47ull);
237 const auto folded =
static_cast<std::uint32_t
>(
mixed ^ (
mixed >> 32));
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;
250 bool prepared_ =
false;
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
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