23template <
typename SampleType =
float>
25 static_assert(std::is_floating_point_v<SampleType>);
31 static constexpr std::uint64_t
kDefaultSeed = 0x5041525449434C45ull;
40 update_decay_coefficient();
50 event_rng_.prepare(sample_rate_);
51 jitter_rng_.prepare(sample_rate_);
52 update_decay_coefficient();
64 particle_scale_ = std::sqrt(
static_cast<double>(particle_count_));
68 const double value =
static_cast<double>(events_per_second);
69 if (std::isfinite(value))
70 event_rate_ = std::clamp(value, 1.0, 20000.0);
74 const double value =
static_cast<double>(milliseconds);
75 if (!std::isfinite(value))
77 energy_decay_ms_ = std::clamp(value, 5.0, 4000.0);
78 update_decay_coefficient();
82 const double value =
static_cast<double>(floor);
83 if (!std::isfinite(value))
85 sustain_floor_ = std::clamp(value, 0.0, 0.2);
91 const double value =
static_cast<double>(loss);
92 if (std::isfinite(value))
93 collision_energy_loss_ = std::clamp(value, 0.0001, 0.05);
102 void excite(SampleType amount01)
noexcept {
103 const double amount =
static_cast<double>(amount01);
104 if (std::isfinite(amount) && amount > 0.0)
105 energy_ = std::clamp(energy_ + amount, 0.0, 1.0);
108 CollisionEvent
next() noexcept {
109 energy_ *= decay_coefficient_;
113 const double event_draw = 0.5 * (
static_cast<double>(event_rng_.white()) + 1.0);
114 const double lambda_dt = event_rate_ * particle_scale_ * energy_ / sample_rate_;
115 const double probability = -std::expm1(-lambda_dt);
116 if (!(event_draw < probability))
119 const double pre_loss_energy = energy_;
120 const double jitter = 0.5 * (
static_cast<double>(jitter_rng_.white()) + 1.0);
121 const double amplitude = pre_loss_energy * (0.5 + 0.5 * jitter);
122 SampleType converted_amplitude =
static_cast<SampleType
>(amplitude);
123 if (!(
static_cast<double>(converted_amplitude) < pre_loss_energy)) {
124 converted_amplitude = std::nextafter(
125 static_cast<SampleType
>(pre_loss_energy),
126 std::numeric_limits<SampleType>::lowest());
129 energy_ = std::max(0.0, energy_ - collision_energy_loss_);
131 return {
true, converted_amplitude};
134 [[nodiscard]] SampleType
energy() const noexcept {
135 return static_cast<SampleType
>(energy_);
139 return static_cast<SampleType
>(event_rate_);
142 return static_cast<SampleType
>(energy_decay_ms_);
145 return static_cast<SampleType
>(sustain_floor_);
148 return static_cast<SampleType
>(collision_energy_loss_);
150 [[nodiscard]] std::uint64_t
seed() const noexcept {
return seed_; }
151 [[nodiscard]]
double sample_rate() const noexcept {
return sample_rate_; }
161 if (sustain_floor_ <= 0.0 || !(decay_coefficient_ > 0.0 && decay_coefficient_ < 1.0))
163 if (energy_ < sustain_floor_)
165 const double samples = std::ceil(std::log(sustain_floor_ / energy_) /
166 std::log(decay_coefficient_));
167 if (!std::isfinite(samples) ||
168 samples >=
static_cast<double>(std::numeric_limits<int>::max()))
169 return std::numeric_limits<int>::max();
170 return std::max(0,
static_cast<int>(samples) + 1);
174 static std::uint32_t derive_seed(std::uint64_t
seed, std::uint64_t purpose)
noexcept {
175 const std::uint64_t mixed =
mix64(
seed, 0
u, purpose);
176 const auto folded =
static_cast<std::uint32_t
>(mixed ^ (mixed >> 32));
180 void apply_seed() noexcept {
181 event_rng_.
set_seed(derive_seed(seed_, 0x4556454E54ull));
182 jitter_rng_.
set_seed(derive_seed(seed_, 0x4A4954544552ull));
185 void update_decay_coefficient() noexcept {
186 decay_coefficient_ = std::exp(-1.0 / (0.001 * energy_decay_ms_ * sample_rate_));
189 void snap_energy() noexcept {
190 if (energy_ < sustain_floor_)
194 NoiseSourceT<double> event_rng_;
195 NoiseSourceT<double> jitter_rng_;
197 double energy_ = 0.0;
198 double event_rate_ = 400.0;
199 double energy_decay_ms_ = 250.0;
200 double sustain_floor_ = 0.02;
201 double collision_energy_loss_ = 0.004;
202 double particle_scale_ = 8.0;
203 double decay_coefficient_ = 0.0;
204 int particle_count_ = 64;
void set_seed(std::uint32_t seed)
Sets the seed used by reset().
Definition noise_source.hpp:96
static constexpr std::uint32_t default_seed
Default seed.
Definition noise_source.hpp:55
Bounded stochastic collision model for shaken particle instruments.
Definition particle_collision_exciter.hpp:24
static constexpr double kMinSupportedSampleRate
Definition particle_collision_exciter.hpp:30
void reset() noexcept
Definition particle_collision_exciter.hpp:96
void prepare(double sample_rate)
Reconfigures the two NoiseSourceT streams and clears dynamic state.
Definition particle_collision_exciter.hpp:47
void set_sustain_floor(SampleType floor) noexcept
Definition particle_collision_exciter.hpp:81
void set_particle_count(int count) noexcept
Definition particle_collision_exciter.hpp:62
SampleType energy() const noexcept
Definition particle_collision_exciter.hpp:134
double sample_rate() const noexcept
Definition particle_collision_exciter.hpp:151
void set_seed(std::uint64_t seed) noexcept
Definition particle_collision_exciter.hpp:56
SampleType event_rate() const noexcept
Definition particle_collision_exciter.hpp:138
ParticleCollisionExciterT()
Definition particle_collision_exciter.hpp:38
static constexpr double kDefaultSampleRate
Definition particle_collision_exciter.hpp:29
CollisionEvent next() noexcept
Definition particle_collision_exciter.hpp:108
std::uint64_t seed() const noexcept
Definition particle_collision_exciter.hpp:150
SampleType collision_energy_loss() const noexcept
Definition particle_collision_exciter.hpp:147
SampleType energy_decay_ms() const noexcept
Definition particle_collision_exciter.hpp:141
int tail_samples() const noexcept
Conservative samples until the decay envelope reaches its exact floor, assuming no more excite() call...
Definition particle_collision_exciter.hpp:158
void excite(SampleType amount01) noexcept
Definition particle_collision_exciter.hpp:102
SampleType sustain_floor() const noexcept
Definition particle_collision_exciter.hpp:144
static constexpr std::size_t retained_bytes() noexcept
Definition particle_collision_exciter.hpp:154
void set_energy_decay(SampleType milliseconds) noexcept
Definition particle_collision_exciter.hpp:73
void set_collision_energy_loss(SampleType loss) noexcept
Absolute normalized-energy decrement applied after each collision.
Definition particle_collision_exciter.hpp:90
int particle_count() const noexcept
Definition particle_collision_exciter.hpp:137
static constexpr int latency_samples() noexcept
Definition particle_collision_exciter.hpp:153
static constexpr int kMaxParticleCount
Definition particle_collision_exciter.hpp:28
static constexpr std::uint64_t kDefaultSeed
Definition particle_collision_exciter.hpp:31
void set_event_rate(SampleType events_per_second) noexcept
Definition particle_collision_exciter.hpp:67
Definition additive_bank.hpp:218
constexpr std::uint64_t mix64(std::uint64_t z)
splitmix64 finalizer (Steele, Lea & Flood, OOPSLA 2014).
Definition rng.hpp:140
ParticleCollisionExciterT< float > ParticleCollisionExciter
Definition particle_collision_exciter.hpp:208
@ u
Definition formant_filter_bank.hpp:20
ParticleCollisionExciterT< double > ParticleCollisionExciter64
Definition particle_collision_exciter.hpp:209
Deterministic randomness for modulation: a counter-free PRNG, a Gaussian draw, a stateless purpose-ke...
Definition particle_collision_exciter.hpp:33
SampleType amplitude
Definition particle_collision_exciter.hpp:35
bool occurred
Definition particle_collision_exciter.hpp:34