71 2.2954, 2.6531, 2.9173};
78 r.bend_octaves = 0.15f;
79 r.brightness_octaves = 1.6f;
84 void set_tune_hz(
double hz) { tune_hz_ = std::clamp(hz, 20.0, 2000.0); }
87 void set_structure(
double amount) { structure_ = std::clamp(amount, 0.0, 1.0); }
90 void set_stretch(
double amount) { stretch_ = std::clamp(amount, 0.0, 1.0); }
93 void set_decay_ms(
double ms) { decay_ms_ = std::clamp(ms, 20.0, 8000.0); }
97 void set_damping(
double amount) { damping_ = std::clamp(amount, 0.0, 1.0); }
100 void set_brightness(
double amount) { brightness_ = std::clamp(amount, 0.0, 1.0); }
106 void set_position(
double position) { position_ = std::clamp(position, 0.02, 0.5); }
110 void set_spread(
double amount) { spread_ = std::clamp(amount, 0.0, 1.0); }
116 exciter_cutoff_hz_ = std::clamp(hz, 100.0, 18000.0);
128 click_decay_ms_ = std::clamp(ms, 0.1, 50.0);
142 return output_.latency_samples();
145 return output_.oversampling();
148 output_.set_oversampling(factor);
160 exciter_filter_.prepare(
static_cast<float>(
sample_rate));
161 air_filter_.prepare(
static_cast<float>(
sample_rate));
162 click_filter_.prepare(
static_cast<float>(
sample_rate));
165 scratch_.assign(kBlock, 0.0f);
171 exciter_env_.reset();
176 exciter_filter_.reset();
178 click_filter_.reset();
185 click_noise_.reset();
186 pluck_pending_ =
false;
188 applied_fundamental_hz_ = tune_hz_;
197 velocity_gain_ = response.gain(
velocity);
198 const double tension = std::exp2(response.bend(
velocity));
199 applied_fundamental_hz_ = tune_hz_ * tension;
200 const double exciter_scale = response.brightness_scale(
velocity);
204 click_noise_.reset();
205 build_modes(tension);
206 bank_.set_modes(std::span<const ModalMode>(modes_.data(),
mode_count));
208 exciter_filter_.reset();
209 exciter_filter_.set_cutoff(
static_cast<float>(
210 std::min(exciter_cutoff_hz_ * exciter_scale, 0.49 *
sample_rate())));
214 exciter_env_.set_attack_ms(0.0);
215 exciter_env_.set_decay_time_constant_ms(exciter_ms_);
216 exciter_env_.trigger();
218 exciter_env_.reset();
223 sub_env_.set_attack_ms(0.0);
224 sub_env_.set_decay_t60_ms(decay_ms_);
225 if (sub_level_ > 0.0) sub_env_.trigger();
228 air_filter_.set_cutoff(
static_cast<float>(
229 std::min(4000.0 * exciter_scale, 0.49 *
sample_rate())));
231 air_env_.set_attack_ms(0.0);
232 air_env_.set_decay_time_constant_ms(air_decay_ms_);
233 if (air_level_ > 0.0) air_env_.trigger();
235 click_filter_.reset();
236 click_filter_.set_cutoff(
static_cast<float>(
237 std::min(8000.0 * exciter_scale, 0.49 *
sample_rate())));
239 click_env_.set_attack_ms(0.0);
240 click_env_.set_decay_time_constant_ms(click_decay_ms_);
241 if (click_level_ > 0.0) click_env_.trigger();
246 gate_env_.set_attack_ms(0.5);
247 gate_env_.set_decay_t60_ms(decay_ms_ * 0.5);
253 return pluck_pending_ || exciter_env_.is_active() || gate_env_.is_active() ||
254 sub_env_.is_active() || air_env_.is_active() ||
255 click_env_.is_active() ||
256 level_ > kSilenceLevel || output_.has_tail();
261 while (done < num_samples) {
262 const int n = std::min<int>(kBlock, num_samples - done);
266 for (
int i = 0;
i < n; ++
i) {
268 if (pluck_pending_) {
270 pluck_pending_ =
false;
271 }
else if (exciter_env_.is_active()) {
272 burst =
static_cast<double>(noise_.white()) *
273 exciter_env_.process();
275 scratch_[
static_cast<std::size_t
>(
i)] =
276 exciter_filter_.process_lowpass(
static_cast<float>(burst));
279 std::fill(body_.begin(), body_.begin() + n, 0.0f);
280 bank_.process_add(scratch_.data(), body_.data(), n);
282 for (
int i = 0;
i < n; ++
i) {
283 const double gated = gate_.process(body_[
static_cast<std::size_t
>(
i)],
284 gate_env_.process());
286 if (sub_env_.is_active()) {
289 if (sub_phase_ >= 1.0) sub_phase_ -= std::floor(sub_phase_);
290 layers += std::sin(2.0 * 3.14159265358979323846 * sub_phase_) *
291 sub_env_.process() * sub_level_;
293 if (air_env_.is_active()) {
294 layers +=
static_cast<double>(air_filter_.process_highpass(
295 air_noise_.white())) *
296 air_env_.process() * air_level_;
298 if (click_env_.is_active()) {
299 layers +=
static_cast<double>(click_filter_.process_lowpass(
300 click_noise_.white())) *
301 click_env_.process() * click_level_;
303 const double shaped =
304 output_.process(
static_cast<float>(
gated + layers)) * velocity_gain_;
305 out[done +
i] +=
static_cast<float>(shaped);
306 level_ = std::max(std::fabs(shaped), level_ * kLevelDecay);
313 static constexpr int kBlock = 128;
314 static constexpr double kSilenceLevel = 1
e-5;
315 static constexpr double kLevelDecay = 0.9995;
317 void build_modes(
double tension) {
320 double raw_gain_energy = 0.0;
322 const double harmonic =
static_cast<double>(m + 1);
326 const double stiffened =
327 ratio * (1.0 + stretch_ * 0.02 *
static_cast<double>(m * m));
328 const double detune = 1.0 + spread_ * 0.03 *
static_cast<double>(noise_.
white());
329 const double f = tune_hz_ * tension * stiffened *
detune;
333 const double decay_scale =
334 std::pow(0.55, damping_ *
static_cast<double>(m));
338 const double comb = std::fabs(
339 std::sin(3.14159265358979323846 *
static_cast<double>(m + 1) * position_));
340 const double rolloff =
341 std::pow(0.6 + 0.4 * brightness_,
static_cast<double>(m));
343 const double raw_gain = comb * rolloff;
344 raw_gain_energy += raw_gain * raw_gain;
345 modes_[
static_cast<std::size_t
>(m)] =
ModalMode{
346 static_cast<float>(std::min(f, 0.49 *
sample_rate())),
347 static_cast<float>(0.001 * decay_ms_ * decay_scale),
348 static_cast<float>(raw_gain),
354 const double target_energy = 1.0 /
static_cast<double>(
mode_count);
356 raw_gain_energy > 0.0 ? std::sqrt(target_energy / raw_gain_energy) : 0.0;
357 mode_gain_energy_ = 0.0;
358 for (
auto& mode : modes_) {
359 mode.gain =
static_cast<float>(
static_cast<double>(mode.gain) * scale);
361 static_cast<double>(mode.gain) *
static_cast<double>(mode.gain);
365 double tune_hz_ = 90.0;
366 double structure_ = 1.0;
367 double stretch_ = 0.0;
368 double decay_ms_ = 700.0;
369 double damping_ = 0.5;
370 double brightness_ = 0.5;
371 double position_ = 0.28;
372 double spread_ = 0.1;
373 double exciter_ms_ = 1.5;
374 double exciter_cutoff_hz_ = 6000.0;
376 double sub_level_ = 0.0;
377 double air_level_ = 0.0;
378 double air_decay_ms_ = 20.0;
379 double click_level_ = 0.0;
380 double click_decay_ms_ = 2.0;
397 std::array<ModalMode, mode_count> modes_{};
398 std::vector<float> scratch_;
399 std::array<float, kBlock> body_{};
401 double velocity_gain_ = 1.0;
402 double mode_gain_energy_ = 0.0;
403 double sub_phase_ = 0.0;
404 double applied_fundamental_hz_ = 100.0;
405 bool pluck_pending_ =
false;
SampleType white()
Raw uniform white sample in [-1, 1), bypassing the colour filter and the normalisation.
Definition noise_source.hpp:123
static constexpr std::uint32_t default_seed
Definition noise_source.hpp:55
const OutputStage & output() const
Definition membrane.hpp:139
void on_prepare(double sample_rate) override
Sizes buffers and fixes coefficients.
Definition membrane.hpp:152
OutputStage * output_stage() noexcept override
The terminal saturation and degradation stage, at the generic voice boundary.
Definition membrane.hpp:140
void render_add(float *out, int num_samples) override
Adds the voice's output into out. Must add, never assign.
Definition membrane.hpp:259
int latency_samples() const noexcept override
Fixed host-rate delay introduced by this voice's selected output quality.
Definition membrane.hpp:141
void set_stretch(double amount)
Extra progressive sharpening of the upper modes, as a stiff plate has.
Definition membrane.hpp:90
void set_spread(double amount)
Random detuning of the modes, drawn once per hit.
Definition membrane.hpp:110
void set_click_decay_ms(double ms)
Definition membrane.hpp:127
MembraneVoice()
Definition membrane.hpp:73
void set_tune_hz(double hz)
Fundamental of the body, in Hz.
Definition membrane.hpp:84
void set_sub_level(double level)
Optional layers around the modal body.
Definition membrane.hpp:123
void set_exciter_cutoff_hz(double hz)
Definition membrane.hpp:115
static constexpr int mode_count
Number of modes the body carries.
Definition membrane.hpp:66
OutputStage & output()
Definition membrane.hpp:138
void set_damping(double amount)
How much faster the upper modes die than the fundamental.
Definition membrane.hpp:97
void set_exciter(MembraneExciter exciter)
Definition membrane.hpp:118
void set_decay_ms(double ms)
How long the fundamental rings, as a T60 in milliseconds.
Definition membrane.hpp:93
void set_exciter_ms(double ms)
Length of the exciter burst and the corner it is filtered at.
Definition membrane.hpp:114
void set_output_oversampling(OutputOversampling factor) override
Definition membrane.hpp:147
void set_structure(double amount)
Blend from a harmonic series (0) to ideal-membrane ratios (1).
Definition membrane.hpp:87
void on_reset() override
Clears every piece of state the voice carries, so is_active() becomes false and the next render start...
Definition membrane.hpp:169
void set_position(double position)
Where the head is struck, 0 (edge) to 0.5 (midpoint).
Definition membrane.hpp:106
void set_air_level(double level)
Definition membrane.hpp:124
void set_brightness(double amount)
Relative level of the upper modes in the strike.
Definition membrane.hpp:100
LowpassGate & gate()
The lowpass gate that couples the tail's level to its brightness.
Definition membrane.hpp:137
OutputOversampling output_oversampling() const noexcept override
Output quality at the generic voice boundary.
Definition membrane.hpp:144
static constexpr double membrane_ratios[mode_count]
Frequency ratios of an ideal circular membrane's first six modes, relative to its fundamental.
Definition membrane.hpp:70
double mode_gain_energy() const
Sum of squared mode gains after the constant-energy normalization.
Definition membrane.hpp:134
void set_air_decay_ms(double ms)
Definition membrane.hpp:125
void set_click_level(double level)
Definition membrane.hpp:126
bool on_is_active() const override
Definition membrane.hpp:252
void on_note_on(float velocity) override
Starts a hit at the given velocity, already clamped to [0, 1].
Definition membrane.hpp:192
double sample_rate() const
Definition voice.hpp:105
void set_velocity_response(const VelocityResponse &r)
Definition voice.hpp:129
float velocity() const
Definition voice.hpp:104
const VelocityResponse & velocity_response() const
Definition voice.hpp:130
MembraneExciter
What strikes the modal membrane.
Definition membrane.hpp:18
@ pluck
One filtered sample: a true impulse that rings the body without a continuing noise texture.
Definition membrane.hpp:23
@ noise_burst
A short filtered-noise burst: stick or hand depending on its length.
Definition membrane.hpp:20
OutputOversampling
Realtime quality choices for the shared drum output nonlinearity.
Definition output_stage.hpp:20
OutputStageT< float > OutputStage
Definition output_stage.hpp:384
TptFilterT< float > TptFilter
Definition tpt_filter.hpp:172
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
@ gated
Flat body, hard cut.
Definition nonlin_ambience_design.hpp:245
DecayEnvelopeT< double > DecayEnvelope64
Definition decay_envelope.hpp:201
ModalBankT< float > ModalBank
Definition modal_bank.hpp:329
NoiseSourceT< float > NoiseSource
Definition noise_source.hpp:279
@ detune
Sub-semitone offset scaled by the pedal. Dry on.
Definition pitch_shifter.hpp:171
LowpassGateT< float > LowpassGate
Definition lowpass_gate.hpp:138
One resonant mode of a vibrating object: frequency, decay time, and impulse-response amplitude.
Definition modal_bank.hpp:16
How hard a hit changes the sound, beyond changing how loud it is.
Definition velocity.hpp:33
First-order topology-preserving transform (TPT) filter.