52 r.bend_octaves = 0.05f;
53 r.brightness_octaves = 2.0f;
58 void set_tune_hz(
double hz) { tune_hz_ = std::clamp(hz, 30.0, 4000.0); }
65 void set_damping(
double amount) { damping_ = std::clamp(amount, 0.0, 1.0); }
69 void set_stiffness(
double amount) { stiffness_ = std::clamp(amount, 0.0, 1.0); }
74 pluck_position_ = std::clamp(position, 0.0, 0.5);
108 fm_path_active_ =
false;
113 modulation_mix_ = std::clamp(mix, 0.0, 1.0);
116 modulation_ratio_ = std::clamp(
ratio, 0.125, 16.0);
119 fm_depth_octaves_ = std::clamp(octaves, 0.0, 2.0);
120 if (fm_depth_octaves_ == 0.0)
121 fm_string_.set_frequency(base_frequency_);
126 void set_lpg_amount(
double amount) { lpg_amount_ = std::clamp(amount, 0.0, 1.0); }
135 return output_.latency_samples();
138 return output_.oversampling();
141 output_.set_oversampling(factor);
161 fm_path_active_ =
false;
162 exciter_env_.reset();
168 modulation_phase_ = 0.0;
174 if (restart_on_hit_) output_.reset_nonlinear_state();
177 velocity_gain_ = response.gain(
velocity);
180 base_frequency_ = tune_hz_ * std::exp2(response.bend(
velocity));
181 string_.set_frequency(base_frequency_);
182 string_.set_decay_seconds(decay_s_);
183 string_.set_damping(damping_);
184 string_.set_stiffness(stiffness_);
185 string_.set_pluck_position(pluck_position_);
186 string_.set_pick_direction(pick_direction_);
188 string_.set_dynamic_bandwidth_hz(
189 brightness_hz_ *
static_cast<double>(response.brightness_scale(
velocity)));
190 string_.pluck(restart_on_hit_);
191 const bool was_fm_path_active = fm_path_active_;
193 if (fm_path_active_) {
194 if (!was_fm_path_active) fm_string_.reset();
195 fm_string_.set_frequency(base_frequency_);
196 fm_string_.set_decay_seconds(decay_s_);
197 fm_string_.set_damping(damping_);
198 fm_string_.set_stiffness(stiffness_);
199 fm_string_.set_pluck_position(pluck_position_);
200 fm_string_.set_pick_direction(pick_direction_);
201 fm_string_.set_dynamic_bandwidth_hz(
203 static_cast<double>(response.brightness_scale(
velocity)));
204 fm_string_.pluck(restart_on_hit_);
205 }
else if (was_fm_path_active) {
209 exciter_env_.set_attack_ms(0.0);
210 exciter_env_.set_decay_time_constant_ms(exciter_ms_);
211 exciter_env_.trigger();
214 gate_env_.set_attack_ms(0.5);
215 gate_env_.set_decay_t60_ms(decay_s_ * 1000.0);
218 sync_env_.set_attack_ms(0.0);
219 sync_env_.set_decay_t60_ms(decay_s_ * 1000.0);
221 modulation_phase_ = 0.0;
227 return string_.is_active() ||
228 (fm_path_active_ && fm_string_.is_active()) ||
233 for (
int i = 0;
i < num_samples; ++
i) {
235 base_frequency_ * modulation_ratio_ /
sample_rate();
236 if (modulation_phase_ >= 1.0) {
237 modulation_phase_ -= std::floor(modulation_phase_);
239 const double modulator =
240 std::sin(2.0 * 3.14159265358979323846 * modulation_phase_);
242 fm_depth_octaves_ > 0.0) {
243 fm_string_.set_modulated_frequency(
245 std::exp2(fm_depth_octaves_ * modulator));
253 const double burst = exciter_env_.is_active()
254 ?
static_cast<double>(noise_.process()) *
255 exciter_env_.process()
257 const double dry =
static_cast<double>(
258 string_.process(
static_cast<float>(burst)));
261 ?
static_cast<double>(
262 fm_string_.process(
static_cast<float>(burst)))
264 const double sync_level =
265 sync_env_.is_active() ? sync_env_.process() : 0.0;
267 double effected = dry;
271 effected = dry * modulator;
274 const bool wrapped = master_phase_ >= 1.0;
276 master_phase_ -= std::floor(master_phase_);
280 base_frequency_ * modulation_ratio_ /
sample_rate();
281 if (sync_phase_ >= 1.0) {
282 sync_phase_ -= std::floor(sync_phase_);
286 (2.0 * sync_phase_ - 1.0) * sync_level;
288 const double modulated =
289 dry + modulation_mix_ * (effected - dry);
290 const double gate_target =
291 gate_env_.is_active() ? gate_env_.process() : 0.0;
293 static_cast<double>(gate_.process(
static_cast<float>(modulated),
296 modulated + lpg_amount_ * (
gated - modulated);
297 out[
i] +=
static_cast<float>(
298 output_.process(
static_cast<float>(lpg)) * velocity_gain_);
303 double tune_hz_ = 220.0;
304 double decay_s_ = 2.0;
305 double damping_ = 0.3;
306 double stiffness_ = 0.0;
307 double pluck_position_ = 0.25;
308 double exciter_ms_ = 1.0;
309 double brightness_hz_ = 1800.0;
310 double pick_direction_ = 0.0;
311 bool restart_on_hit_ =
false;
312 bool fm_path_active_ =
false;
314 double modulation_mix_ = 0.0;
315 double modulation_ratio_ = 2.0;
316 double fm_depth_octaves_ = 0.25;
317 double lpg_amount_ = 0.0;
328 double velocity_gain_ = 1.0;
329 double base_frequency_ = 220.0;
330 double modulation_phase_ = 0.0;
331 double master_phase_ = 0.0;
332 double sync_phase_ = 0.0;
bool on_is_active() const override
Definition string.hpp:226
void set_tune_hz(double hz)
Pitch of the string, in Hz.
Definition string.hpp:58
LowpassGate & gate()
Definition string.hpp:127
const OutputStage & output() const
Definition string.hpp:132
void set_modulation(StringModulation mode)
Optional post-body modulation.
Definition string.hpp:104
void set_decay_seconds(double seconds)
Time for the note to fall by 60 dB, in seconds.
Definition string.hpp:62
OutputStage * output_stage() noexcept override
The terminal saturation and degradation stage, at the generic voice boundary.
Definition string.hpp:133
void set_output_oversampling(OutputOversampling factor) override
Definition string.hpp:140
void set_pick_direction(double amount)
Darkens the attack without retuning the string.
Definition string.hpp:92
void set_exciter_ms(double ms)
Length of the excitation burst in milliseconds.
Definition string.hpp:79
void set_pluck_position(double position)
Where along the string it is excited, 0 (at the end) to 0.5 (the middle).
Definition string.hpp:73
void set_modulation_mix(double mix)
Definition string.hpp:112
void on_note_on(float velocity) override
Starts a hit at the given velocity, already clamped to [0, 1].
Definition string.hpp:173
int latency_samples() const noexcept override
Fixed host-rate delay introduced by this voice's selected output quality.
Definition string.hpp:134
void set_stiffness(double amount)
Inharmonicity from string stiffness, 0 (a true harmonic series) to 1.
Definition string.hpp:69
void render_add(float *out, int num_samples) override
Adds the voice's output into out. Must add, never assign.
Definition string.hpp:232
void set_restart_on_hit(bool restart)
Whether a new hit restarts the string or adds to what is still ringing.
Definition string.hpp:97
OutputOversampling output_oversampling() const noexcept override
Output quality at the generic voice boundary.
Definition string.hpp:137
void set_lpg_amount(double amount)
Amount of the lowpass-gate stage after modulation.
Definition string.hpp:126
void set_fm_depth_octaves(double octaves)
Definition string.hpp:118
OutputStage & output()
Definition string.hpp:131
void on_prepare(double sample_rate) override
Sizes buffers and fixes coefficients.
Definition string.hpp:145
StringVoice()
Definition string.hpp:47
void set_modulation_ratio(double ratio)
Definition string.hpp:115
void set_brightness_hz(double hz)
Bandwidth of the excitation in Hz at zero velocity, before the velocity response opens it.
Definition string.hpp:89
void set_damping(double amount)
How much faster the upper partials die than the fundamental.
Definition string.hpp:65
void on_reset() override
Clears every piece of state the voice carries, so is_active() becomes false and the next render start...
Definition string.hpp:158
void set_noise_color(NoiseColor color)
Definition string.hpp:129
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
StringModulation
Definition string.hpp:15
@ fm
Definition string.hpp:17
@ ring
Definition string.hpp:18
@ none
Definition string.hpp:16
@ sync
Definition string.hpp:19
OutputOversampling
Realtime quality choices for the shared drum output nonlinearity.
Definition output_stage.hpp:20
OutputStageT< float > OutputStage
Definition output_stage.hpp:384
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
KarplusStrongT< float > KarplusStrong
Definition karplus_strong.hpp:314
@ i
Definition formant_filter_bank.hpp:20
@ gated
Flat body, hard cut.
Definition nonlin_ambience_design.hpp:245
NoiseColor
Spectral tilt of a NoiseSourceT, named by the usual colour convention.
Definition noise_source.hpp:17
DecayEnvelopeT< double > DecayEnvelope64
Definition decay_envelope.hpp:201
NoiseSourceT< float > NoiseSource
Definition noise_source.hpp:279
LowpassGateT< float > LowpassGate
Definition lowpass_gate.hpp:138
How hard a hit changes the sound, beyond changing how loud it is.
Definition velocity.hpp:33