Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
fm.hpp
Go to the documentation of this file.
1#pragma once
2
9#include <pulp/signal/svf.hpp>
10
11#include <algorithm>
12#include <array>
13#include <cmath>
14#include <cstdint>
15
16namespace pulp::signal::drum {
17
45class FmDrumVoice : public Voice {
46public:
49 r.level_db = 15.0f;
50 r.bend_octaves = 0.1f;
51 // Velocity opening the index is the whole point: a harder hit is a
52 // brighter, more complex spectrum, not the same spectrum turned up.
53 r.brightness_octaves = 1.0f;
55
56 click_.set_level(0.2);
57 click_.set_cutoff_hz(6000.0);
58 click_.set_decay_ms(1.5);
59 }
60
62 void set_tune_hz(double hz) { tune_hz_ = std::clamp(hz, 20.0, 4000.0); }
63
66 void set_ratio(double ratio) { ratio_ = std::clamp(ratio, 0.1, 24.0); }
67
70 void set_index(double index) { index_ = std::clamp(index, 0.0, 24.0); }
71 void set_index_ms(double ms) { index_ms_ = std::clamp(ms, 0.5, 2000.0); }
72
74 void set_decay_ms(double ms) { decay_ms_ = std::clamp(ms, 10.0, 4000.0); }
75
77 void set_pitch_sweep_octaves(double octaves) {
78 pitch_sweep_oct_ = std::clamp(octaves, 0.0, 6.0);
79 }
80 void set_pitch_sweep_ms(double ms) { pitch_sweep_ms_ = std::clamp(ms, 0.5, 500.0); }
81
84 void set_feedback(double amount) { feedback_ = std::clamp(amount, 0.0, 1.0); }
85
88 void set_carrier_wave(int wave) {
89 carrier_wave_ = std::clamp(wave, 0, FmWaveTable::wave_count - 1);
90 }
91 void set_modulator_wave(int wave) {
92 modulator_wave_ = std::clamp(wave, 0, FmWaveTable::wave_count - 1);
93 }
94 void set_carrier_warp(double amount) {
95 carrier_warp_ = std::clamp(amount, 0.0, 1.0);
96 }
97 void set_modulator_warp(double amount) {
98 modulator_warp_ = std::clamp(amount, 0.0, 1.0);
99 }
100 void set_carrier_warp_ms(double ms) {
101 carrier_warp_ms_ = std::clamp(ms, 0.5, 2000.0);
102 }
103 void set_modulator_warp_ms(double ms) {
104 modulator_warp_ms_ = std::clamp(ms, 0.5, 2000.0);
105 }
106
109 void set_lfo_rate_hz(double hz) { lfo_rate_hz_ = std::clamp(hz, 0.05, 40.0); }
110 void set_lfo_depth_octaves(double octaves) {
111 lfo_depth_octaves_ = std::clamp(octaves, 0.0, 2.0);
112 }
113 void set_lfo_delay_ms(double ms) { lfo_delay_ms_ = std::clamp(ms, 0.0, 4000.0); }
114 void set_lfo_fade_ms(double ms) { lfo_fade_ms_ = std::clamp(ms, 0.0, 4000.0); }
115
117 void set_hard_sync(bool sync) { hard_sync_ = sync; }
118
121 void set_transient(int index) {
122 transient_ = std::clamp(index, -1,
123 static_cast<int>(kFmTransients.size()) - 1);
124 if (transient_ >= 0) apply_transient();
125 }
126 void set_noise_level(double level) {
127 transient_ = -1;
128 noise_level_ = std::max(level, 0.0);
129 }
130 void set_noise_decay_ms(double ms) {
131 transient_ = -1;
132 noise_decay_ms_ = std::clamp(ms, 0.5, 500.0);
133 }
135 transient_ = -1;
136 noise_color_ = color;
137 if (noise_.color() != color) noise_.set_color(color);
138 }
139
141 void set_cutoff_hz(double hz) { cutoff_hz_ = std::clamp(hz, 40.0, 18000.0); }
142 void set_resonance(double q) { resonance_ = std::clamp(q, 0.5, 12.0); }
143 void set_bandpass(bool bandpass) { bandpass_ = bandpass; }
144
145 void set_click_level(double level) {
146 transient_ = -1;
147 click_.set_level(level);
148 }
149 void set_click_cutoff_hz(double hz) {
150 transient_ = -1;
151 click_.set_cutoff_hz(hz);
152 }
153
154 OutputStage& output() { return output_; }
155 const OutputStage& output() const { return output_; }
156 OutputStage* output_stage() noexcept override { return &output_; }
157 int latency_samples() const noexcept override {
158 return output_.latency_samples();
159 }
160 OutputOversampling output_oversampling() const noexcept override {
161 return output_.oversampling();
162 }
164 output_.set_oversampling(factor);
165 }
166
167protected:
168 void on_prepare(double sample_rate) override {
169 noise_.prepare(sample_rate);
170 amp_env_.set_sample_rate(sample_rate);
171 index_env_.set_sample_rate(sample_rate);
172 pitch_env_.set_sample_rate(sample_rate);
173 carrier_warp_env_.set_sample_rate(sample_rate);
174 modulator_warp_env_.set_sample_rate(sample_rate);
175 noise_env_.set_sample_rate(sample_rate);
176 click_.prepare(sample_rate);
177 filter_.set_sample_rate(static_cast<float>(sample_rate));
178 output_.prepare(sample_rate);
179 }
180
181 void on_reset() override {
182 amp_env_.reset();
183 index_env_.reset();
184 pitch_env_.reset();
185 carrier_warp_env_.reset();
186 modulator_warp_env_.reset();
187 noise_env_.reset();
188 click_.reset();
189 filter_.reset();
190 output_.reset();
191 noise_.reset();
192 carrier_phase_ = 0.0;
193 modulator_phase_ = 0.0;
194 modulator_z_ = 0.0;
195 lfo_phase_ = 0.0;
196 samples_since_note_ = 0;
197 }
198
199 void on_note_on(float velocity) override {
200 output_.reset_nonlinear_state();
201 output_.trigger();
202 const auto& response = velocity_response();
203 velocity_gain_ = response.gain(velocity);
204 applied_bend_ = pitch_sweep_oct_ + response.bend(velocity);
205 applied_index_ =
206 index_ * static_cast<double>(response.brightness_scale(velocity));
207
208 noise_.reset();
209 carrier_phase_ = 0.0;
210 modulator_phase_ = 0.0;
211 modulator_z_ = 0.0;
212 lfo_phase_ = 0.0;
213 samples_since_note_ = 0;
214 lfo_delay_samples_ =
215 static_cast<int>(0.001 * lfo_delay_ms_ * sample_rate());
216 lfo_fade_samples_ =
217 std::max(1, static_cast<int>(0.001 * lfo_fade_ms_ * sample_rate()));
218
219 filter_.set_mode(bandpass_ ? Svf::Mode::bandpass : Svf::Mode::lowpass);
220 filter_.set_frequency(
221 static_cast<float>(std::min(cutoff_hz_, 0.49 * sample_rate())));
222 filter_.set_resonance(static_cast<float>(resonance_));
223
224 pitch_env_.set_attack_ms(0.0);
225 pitch_env_.set_decay_time_constant_ms(pitch_sweep_ms_);
226 pitch_env_.trigger();
227
228 index_env_.set_attack_ms(0.0);
229 index_env_.set_decay_time_constant_ms(index_ms_);
230 index_env_.trigger();
231
232 carrier_warp_env_.set_attack_ms(0.0);
233 carrier_warp_env_.set_decay_time_constant_ms(carrier_warp_ms_);
234 carrier_warp_env_.trigger();
235 modulator_warp_env_.set_attack_ms(0.0);
236 modulator_warp_env_.set_decay_time_constant_ms(modulator_warp_ms_);
237 modulator_warp_env_.trigger();
238
239 amp_env_.set_attack_ms(0.3);
240 amp_env_.set_decay_t60_ms(decay_ms_);
241 amp_env_.trigger();
242
243 noise_env_.set_attack_ms(0.0);
244 noise_env_.set_decay_time_constant_ms(noise_decay_ms_);
245 if (noise_level_ > 0.0) noise_env_.trigger();
246 click_.trigger();
247 }
248
249 bool on_is_active() const override {
250 return amp_env_.is_active() || noise_env_.is_active() ||
251 click_.is_active() || output_.has_tail();
252 }
253
254 void render_add(float* out, int num_samples) override {
255 for (int i = 0; i < num_samples; ++i) {
256 const double pitch = pitch_env_.process();
257 const double index = applied_index_ * index_env_.process();
258 const double amplitude = amp_env_.process();
259 const double carrier_warp =
260 carrier_warp_ * carrier_warp_env_.process();
261 const double modulator_warp =
262 modulator_warp_ * modulator_warp_env_.process();
263
264 double lfo = 0.0;
265 if (lfo_depth_octaves_ > 0.0 &&
266 samples_since_note_ >= lfo_delay_samples_) {
267 const double fade = std::min(
268 static_cast<double>(samples_since_note_ - lfo_delay_samples_) /
269 static_cast<double>(lfo_fade_samples_),
270 1.0);
271 lfo = lfo_depth_octaves_ * fade *
272 std::sin(2.0 * 3.14159265358979323846 * lfo_phase_);
273 }
274 lfo_phase_ += lfo_rate_hz_ / sample_rate();
275 if (lfo_phase_ >= 1.0) lfo_phase_ -= std::floor(lfo_phase_);
276 ++samples_since_note_;
277
278 const double carrier_hz =
279 std::min(tune_hz_ * std::exp2(applied_bend_ * pitch + lfo),
280 0.49 * sample_rate());
281 const double modulator_hz = std::min(carrier_hz * ratio_, 0.49 * sample_rate());
282
283 carrier_phase_ += carrier_hz / sample_rate();
284 const bool carrier_wrapped = carrier_phase_ >= 1.0;
285 if (carrier_wrapped) carrier_phase_ -= std::floor(carrier_phase_);
286
287 if (hard_sync_ && carrier_wrapped) {
288 modulator_phase_ = 0.0;
289 } else {
290 modulator_phase_ += modulator_hz / sample_rate();
291 }
292 if (modulator_phase_ >= 1.0) modulator_phase_ -= std::floor(modulator_phase_);
293 // The modulator reads its own previous output. One sample of delay
294 // is what makes the loop computable at all; without it the operator
295 // would need its own output to produce its output.
296 const double modulator_phase =
298 modulator_phase_ +
299 feedback_ * kFeedbackDepth * modulator_z_ /
300 (2.0 * 3.14159265358979323846),
301 modulator_warp);
302 const double modulator =
303 FmWaveTable::read(modulator_wave_, modulator_phase,
304 modulator_hz / sample_rate());
305 modulator_z_ = modulator;
306
307 const double carrier_phase =
309 carrier_phase_ + index * modulator /
310 (2.0 * 3.14159265358979323846),
311 carrier_warp);
312 const double carrier =
313 FmWaveTable::read(carrier_wave_, carrier_phase,
314 carrier_hz / sample_rate());
315
316 const double body = filter_.process(static_cast<float>(carrier)) * amplitude;
317 const double strike = click_.process(static_cast<double>(noise_.white()));
318 const double noise_envelope =
319 noise_env_.is_active() ? noise_env_.process() : 0.0;
320 const double noise =
321 noise_level_ > 0.0
322 ? static_cast<double>(noise_.process()) *
323 noise_envelope * noise_level_
324 : 0.0;
325
326 out[i] += static_cast<float>(
327 output_.process(static_cast<float>(body + strike + noise)) *
328 velocity_gain_);
329 }
330 }
331
332private:
333 void apply_transient() {
334 const auto& recipe =
335 kFmTransients[static_cast<std::size_t>(transient_)];
336 noise_color_ = recipe.color;
337 noise_level_ = recipe.noise_level;
338 noise_decay_ms_ = recipe.noise_decay_ms;
339 click_.set_level(recipe.click_level);
340 click_.set_cutoff_hz(recipe.click_cutoff_hz);
341 click_.set_decay_ms(recipe.click_decay_ms);
342 if (noise_.color() != noise_color_)
343 noise_.set_color(noise_color_);
344 }
345
346 // How far full feedback drives the modulator. Beyond roughly this the
347 // operator is already producing noise, so more range would only add
348 // settings that all sound the same.
349 static constexpr double kFeedbackDepth = 6.0;
350
351 double tune_hz_ = 110.0;
352 double ratio_ = 1.41;
353 double index_ = 6.0;
354 double index_ms_ = 40.0;
355 double decay_ms_ = 400.0;
356 double pitch_sweep_oct_ = 0.0;
357 double pitch_sweep_ms_ = 30.0;
358 double feedback_ = 0.0;
359 double cutoff_hz_ = 12000.0;
360 double resonance_ = 0.8;
361 bool bandpass_ = false;
362 int carrier_wave_ = 0;
363 int modulator_wave_ = 0;
364 double carrier_warp_ = 0.0;
365 double modulator_warp_ = 0.0;
366 double carrier_warp_ms_ = 40.0;
367 double modulator_warp_ms_ = 40.0;
368 double lfo_rate_hz_ = 5.0;
369 double lfo_depth_octaves_ = 0.0;
370 double lfo_delay_ms_ = 0.0;
371 double lfo_fade_ms_ = 0.0;
372 bool hard_sync_ = false;
373 int transient_ = -1;
374 NoiseColor noise_color_ = NoiseColor::white;
375 double noise_level_ = 0.0;
376 double noise_decay_ms_ = 10.0;
377
378 NoiseSource noise_;
379 DecayEnvelope64 amp_env_;
380 DecayEnvelope64 index_env_;
381 DecayEnvelope64 pitch_env_;
382 DecayEnvelope64 carrier_warp_env_;
383 DecayEnvelope64 modulator_warp_env_;
384 DecayEnvelope64 noise_env_;
385 ClickLayer click_;
386 Svf filter_;
387 OutputStage output_;
388
389 double velocity_gain_ = 1.0;
390 double applied_bend_ = 0.0;
391 double applied_index_ = 6.0;
392 double carrier_phase_ = 0.0;
393 double modulator_phase_ = 0.0;
394 double modulator_z_ = 0.0;
395 double lfo_phase_ = 0.0;
396 int samples_since_note_ = 0;
397 int lfo_delay_samples_ = 0;
398 int lfo_fade_samples_ = 1;
399};
400
430class Fm8DrumVoice : public Voice {
431public:
432 static constexpr int operator_count = 8;
433 static constexpr int algorithm_count = 16;
434
437 struct Algorithm {
439 std::uint8_t carriers;
440 };
441
446 {{0, 0, 0, 0, 0, 0, 0, 0}, 0xFF}, // 0 additive
447 {{0x02, 0, 0, 0, 0, 0, 0, 0}, 0xFD}, // 1 one pair
448 {{0x02, 0, 0x08, 0, 0, 0, 0, 0}, 0xF5}, // 2 two pairs
449 {{0x02, 0, 0x08, 0, 0x20, 0, 0, 0}, 0xD5}, // 3 three pairs
450 {{0x02, 0, 0x08, 0, 0x20, 0, 0x80, 0}, 0x55}, // 4 four pairs
451 {{0x06, 0, 0, 0, 0, 0, 0, 0}, 0xF9}, // 5 two into one
452 {{0x0E, 0, 0, 0, 0, 0, 0, 0}, 0xF1}, // 6 three into one
453 {{0x1E, 0, 0, 0, 0, 0, 0, 0}, 0xE1}, // 7 four into one
454 {{0x02, 0x04, 0, 0, 0, 0, 0, 0}, 0xF9}, // 8 three-deep stack
455 {{0x02, 0x04, 0x08, 0, 0, 0, 0, 0}, 0xF1}, // 9 four-deep stack
456 {{0x02, 0x04, 0, 0x20, 0, 0, 0, 0}, 0xD9}, // 10 stack plus pair
457 {{0x06, 0x18, 0, 0, 0, 0, 0, 0}, 0xE1}, // 11 tree
458 {{0x02, 0x0C, 0x10, 0, 0, 0, 0, 0}, 0xE1}, // 12 branching stack
459 {{0x02, 0x04, 0x08, 0x10, 0, 0, 0, 0}, 0xE1}, // 13 five-deep stack
460 {{0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0, 0}, 0x81}, // 14 seven-deep stack
461 {{0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0}, 0x01}, // 15 eight-deep stack
462 };
463
466 r.level_db = 15.0f;
467 r.brightness_octaves = 1.0f;
469 }
470
471 void set_algorithm(int index) {
472 algorithm_ = std::clamp(index, 0, algorithm_count - 1);
473 }
474 int algorithm() const { return algorithm_; }
475
476 void set_tune_hz(double hz) { tune_hz_ = std::clamp(hz, 20.0, 4000.0); }
477
479 void set_operator_ratio(int op, double ratio) {
480 if (op < 0 || op >= operator_count) return;
481 ratios_[static_cast<std::size_t>(op)] = std::clamp(ratio, 0.1, 32.0);
482 }
483
485 void set_operator_level(int op, double level) {
486 if (op < 0 || op >= operator_count) return;
487 levels_[static_cast<std::size_t>(op)] = std::clamp(level, 0.0, 1.0);
488 }
489
493 void set_operator_decay_ms(int op, double ms) {
494 if (op < 0 || op >= operator_count) return;
495 decays_[static_cast<std::size_t>(op)] = std::clamp(ms, 1.0, 4000.0);
496 }
497
499 void set_operator_feedback(int op, double amount) {
500 if (op < 0 || op >= operator_count) return;
501 feedbacks_[static_cast<std::size_t>(op)] = std::clamp(amount, 0.0, 1.0);
502 }
503
504 void set_operator_wave(int op, int wave) {
505 if (op < 0 || op >= operator_count) return;
506 waves_[static_cast<std::size_t>(op)] =
507 std::clamp(wave, 0, FmWaveTable::wave_count - 1);
508 }
509
511 void set_depth(double depth) { depth_ = std::clamp(depth, 0.0, 12.0); }
512
514 void set_formant_hz(double hz) { formant_hz_ = std::clamp(hz, 40.0, 18000.0); }
515 void set_formant_q(double q) { formant_q_ = std::clamp(q, 0.5, 12.0); }
516
517 void set_transient(int index) {
518 transient_ = std::clamp(index, -1,
519 static_cast<int>(kFmTransients.size()) - 1);
520 if (transient_ >= 0) apply_transient();
521 }
522 void set_noise_level(double level) {
523 transient_ = -1;
524 noise_level_ = std::max(level, 0.0);
525 }
526 void set_noise_decay_ms(double ms) {
527 transient_ = -1;
528 noise_decay_ms_ = std::clamp(ms, 0.5, 500.0);
529 }
531 transient_ = -1;
532 noise_color_ = color;
533 if (noise_.color() != color) noise_.set_color(color);
534 }
535 void set_click_level(double level) {
536 transient_ = -1;
537 click_.set_level(level);
538 }
539
540 OutputStage& output() { return output_; }
541 const OutputStage& output() const { return output_; }
542 OutputStage* output_stage() noexcept override { return &output_; }
543 int latency_samples() const noexcept override {
544 return output_.latency_samples();
545 }
546 OutputOversampling output_oversampling() const noexcept override {
547 return output_.oversampling();
548 }
550 output_.set_oversampling(factor);
551 }
552
553protected:
554 void on_prepare(double sample_rate) override {
555 noise_.prepare(sample_rate);
556 for (auto& env : envelopes_) env.set_sample_rate(sample_rate);
557 noise_env_.set_sample_rate(sample_rate);
558 click_.prepare(sample_rate);
559 formant_.set_sample_rate(static_cast<float>(sample_rate));
560 formant_.set_mode(Svf::Mode::bandpass);
561 output_.prepare(sample_rate);
562 }
563
564 void on_reset() override {
565 for (auto& env : envelopes_) env.reset();
566 noise_env_.reset();
567 click_.reset();
568 noise_.reset();
569 phases_.fill(0.0);
570 previous_.fill(0.0);
571 noise_.reset();
572 formant_.reset();
573 output_.reset();
574 }
575
576 void on_note_on(float velocity) override {
577 output_.reset_nonlinear_state();
578 output_.trigger();
579 const auto& response = velocity_response();
580 velocity_gain_ = response.gain(velocity);
581 applied_depth_ =
582 depth_ * static_cast<double>(response.brightness_scale(velocity));
583
584 noise_.reset();
585 phases_.fill(0.0);
586 previous_.fill(0.0);
587 formant_.set_frequency(
588 static_cast<float>(std::min(formant_hz_, 0.49 * sample_rate())));
589 formant_.set_resonance(static_cast<float>(formant_q_));
590
591 for (std::size_t op = 0; op < operator_count; ++op) {
592 envelopes_[op].set_attack_ms(0.2);
593 envelopes_[op].set_decay_t60_ms(decays_[op]);
594 envelopes_[op].trigger();
595 }
596
597 noise_env_.set_attack_ms(0.0);
598 noise_env_.set_decay_time_constant_ms(noise_decay_ms_);
599 if (noise_level_ > 0.0) noise_env_.trigger();
600 click_.trigger();
601 }
602
603 bool on_is_active() const override {
604 for (const auto& env : envelopes_) {
605 if (env.is_active()) return true;
606 }
607 return noise_env_.is_active() || click_.is_active() ||
608 output_.has_tail();
609 }
610
611 void render_add(float* out, int num_samples) override {
612 const Algorithm& alg = algorithms[static_cast<std::size_t>(algorithm_)];
613
614 for (int i = 0; i < num_samples; ++i) {
615 std::array<double, operator_count> amplitudes{};
616 std::array<double, operator_count> feedback{};
617 for (std::size_t op = 0; op < operator_count; ++op) {
618 amplitudes[op] = envelopes_[op].process() * levels_[op];
619 feedback[op] = feedbacks_[op] * kFeedbackDepth;
620 }
621
622 const auto frequency = [&](std::size_t op) {
623 return tune_hz_ * ratios_[op];
624 };
625 const auto phase_route = [&](std::size_t destination,
626 std::size_t source) {
627 return (alg.modulated_by[destination] & (1u << source))
628 ? applied_depth_
629 : 0.0;
630 };
631 const auto no_frequency_route = [](std::size_t, std::size_t) {
632 return 0.0;
633 };
634 const auto carrier = [&](std::size_t op) {
635 return (alg.carriers & (1u << op)) ? 1.0 : 0.0;
636 };
637 const auto wave = [&](std::size_t op, double phase_cycles,
638 double phase_offset_radians,
639 double phase_increment) {
640 return FmWaveTable::read(
641 waves_[op],
642 phase_cycles +
643 phase_offset_radians /
644 (2.0 * 3.14159265358979323846),
645 phase_increment);
646 };
647 const double summed = detail::render_fm_operator_sample(
648 static_cast<std::size_t>(operator_count), sample_rate(),
649 FmOperatorAliasPolicy::bounded, phases_, previous_, amplitudes,
650 feedback, frequency, phase_route, no_frequency_route, carrier,
651 wave);
652
653 const double shaped = formant_.process(static_cast<float>(summed));
654 const double noise_envelope =
655 noise_env_.is_active() ? noise_env_.process() : 0.0;
656 const double noise =
657 noise_level_ > 0.0
658 ? static_cast<double>(noise_.process()) *
659 noise_envelope * noise_level_
660 : 0.0;
661 const double click =
662 click_.process(static_cast<double>(noise_.white()));
663 out[i] += static_cast<float>(
664 output_.process(static_cast<float>(shaped + noise + click)) *
665 velocity_gain_);
666 }
667 }
668
669private:
670 void apply_transient() {
671 const auto& recipe =
672 kFmTransients[static_cast<std::size_t>(transient_)];
673 noise_color_ = recipe.color;
674 noise_level_ = recipe.noise_level;
675 noise_decay_ms_ = recipe.noise_decay_ms;
676 click_.set_level(recipe.click_level);
677 click_.set_cutoff_hz(recipe.click_cutoff_hz);
678 click_.set_decay_ms(recipe.click_decay_ms);
679 if (noise_.color() != noise_color_)
680 noise_.set_color(noise_color_);
681 }
682
683 static constexpr double kFeedbackDepth = 6.0;
684
685 int algorithm_ = 9;
686 double tune_hz_ = 110.0;
687 double depth_ = 3.0;
688 double formant_hz_ = 2000.0;
689 double formant_q_ = 1.0;
690 int transient_ = -1;
691 NoiseColor noise_color_ = NoiseColor::white;
692 double noise_level_ = 0.0;
693 double noise_decay_ms_ = 10.0;
694
695 std::array<double, operator_count> ratios_ = {1.0, 1.41, 2.0, 3.17,
696 4.0, 5.63, 7.0, 9.41};
697 std::array<double, operator_count> levels_ = {1.0, 0.8, 0.7, 0.6,
698 0.5, 0.4, 0.35, 0.3};
699 std::array<double, operator_count> decays_ = {400.0, 220.0, 160.0, 110.0,
700 80.0, 60.0, 45.0, 30.0};
701 std::array<double, operator_count> feedbacks_{};
702 std::array<int, operator_count> waves_{};
703
704 NoiseSource noise_;
705 std::array<DecayEnvelope64, operator_count> envelopes_;
706 DecayEnvelope64 noise_env_;
707 ClickLayer click_;
708 std::array<double, operator_count> phases_{};
709 std::array<double, operator_count> previous_{};
710 Svf formant_;
711 OutputStage output_;
712
713 double velocity_gain_ = 1.0;
714 double applied_depth_ = 3.0;
715};
716
717} // namespace pulp::signal::drum
void set_color(NoiseColor c)
Selects the spectral tilt.
Definition noise_source.hpp:83
NoiseColor color() const
Definition noise_source.hpp:90
void set_cutoff_hz(double hz)
Definition layers.hpp:50
void set_level(double level)
Definition layers.hpp:46
void set_decay_ms(double ms)
Definition layers.hpp:54
void set_operator_wave(int op, int wave)
Definition fm.hpp:504
const OutputStage & output() const
Definition fm.hpp:541
void render_add(float *out, int num_samples) override
Adds the voice's output into out. Must add, never assign.
Definition fm.hpp:611
void on_prepare(double sample_rate) override
Sizes buffers and fixes coefficients.
Definition fm.hpp:554
bool on_is_active() const override
Definition fm.hpp:603
void set_noise_level(double level)
Definition fm.hpp:522
static constexpr int algorithm_count
Definition fm.hpp:433
void set_transient(int index)
Definition fm.hpp:517
void set_noise_color(NoiseColor color)
Definition fm.hpp:530
OutputStage * output_stage() noexcept override
The terminal saturation and degradation stage, at the generic voice boundary.
Definition fm.hpp:542
void set_operator_ratio(int op, double ratio)
Frequency ratio of one operator to the voice's tuning.
Definition fm.hpp:479
OutputOversampling output_oversampling() const noexcept override
Output quality at the generic voice boundary.
Definition fm.hpp:546
void set_click_level(double level)
Definition fm.hpp:535
void on_note_on(float velocity) override
Starts a hit at the given velocity, already clamped to [0, 1].
Definition fm.hpp:576
void set_operator_level(int op, double level)
Output level of one operator, before the algorithm's routing.
Definition fm.hpp:485
int latency_samples() const noexcept override
Fixed host-rate delay introduced by this voice's selected output quality.
Definition fm.hpp:543
void set_noise_decay_ms(double ms)
Definition fm.hpp:526
int algorithm() const
Definition fm.hpp:474
OutputStage & output()
Definition fm.hpp:540
void set_operator_decay_ms(int op, double ms)
Decay of one operator's own envelope, as a T60.
Definition fm.hpp:493
static constexpr int operator_count
Definition fm.hpp:432
Fm8DrumVoice()
Definition fm.hpp:464
void set_tune_hz(double hz)
Definition fm.hpp:476
void set_formant_q(double q)
Definition fm.hpp:515
static constexpr Algorithm algorithms[algorithm_count]
The sixteen routings, ordered roughly from most parallel to most serial: 0 is eight independent carri...
Definition fm.hpp:445
void set_depth(double depth)
Overall modulation depth, scaling every routed connection at once.
Definition fm.hpp:511
void set_algorithm(int index)
Definition fm.hpp:471
void set_output_oversampling(OutputOversampling factor) override
Definition fm.hpp:549
void set_operator_feedback(int op, double amount)
Self-feedback on one operator.
Definition fm.hpp:499
void set_formant_hz(double hz)
A formant bandpass on the summed carriers.
Definition fm.hpp:514
void on_reset() override
Clears every piece of state the voice carries, so is_active() becomes false and the next render start...
Definition fm.hpp:564
void set_decay_ms(double ms)
How long the note lasts, as a T60.
Definition fm.hpp:74
void set_lfo_depth_octaves(double octaves)
Definition fm.hpp:110
void set_bandpass(bool bandpass)
Definition fm.hpp:143
void set_transient(int index)
Select one of the shared 24 procedural strike recipes.
Definition fm.hpp:121
void set_index_ms(double ms)
Definition fm.hpp:71
void set_lfo_rate_hz(double hz)
Delayed, faded pitch LFO.
Definition fm.hpp:109
void set_pitch_sweep_octaves(double octaves)
Pitch envelope depth in octaves and its time constant.
Definition fm.hpp:77
void set_modulator_warp_ms(double ms)
Definition fm.hpp:103
void render_add(float *out, int num_samples) override
Adds the voice's output into out. Must add, never assign.
Definition fm.hpp:254
int latency_samples() const noexcept override
Fixed host-rate delay introduced by this voice's selected output quality.
Definition fm.hpp:157
void set_pitch_sweep_ms(double ms)
Definition fm.hpp:80
void set_lfo_delay_ms(double ms)
Definition fm.hpp:113
OutputStage * output_stage() noexcept override
The terminal saturation and degradation stage, at the generic voice boundary.
Definition fm.hpp:156
void set_noise_color(NoiseColor color)
Definition fm.hpp:134
void set_feedback(double amount)
Modulator self-feedback, 0 to 1.
Definition fm.hpp:84
void set_resonance(double q)
Definition fm.hpp:142
FmDrumVoice()
Definition fm.hpp:47
void set_noise_level(double level)
Definition fm.hpp:126
void set_click_cutoff_hz(double hz)
Definition fm.hpp:149
bool on_is_active() const override
Definition fm.hpp:249
void set_ratio(double ratio)
Modulator frequency as a ratio of the carrier.
Definition fm.hpp:66
void set_carrier_warp(double amount)
Definition fm.hpp:94
const OutputStage & output() const
Definition fm.hpp:155
void set_lfo_fade_ms(double ms)
Definition fm.hpp:114
void set_cutoff_hz(double hz)
A bandpass on the output, for picking one formant out of the sidebands.
Definition fm.hpp:141
void set_tune_hz(double hz)
Carrier frequency, in Hz.
Definition fm.hpp:62
void set_click_level(double level)
Definition fm.hpp:145
void set_noise_decay_ms(double ms)
Definition fm.hpp:130
void set_output_oversampling(OutputOversampling factor) override
Definition fm.hpp:163
void on_prepare(double sample_rate) override
Sizes buffers and fixes coefficients.
Definition fm.hpp:168
void on_note_on(float velocity) override
Starts a hit at the given velocity, already clamped to [0, 1].
Definition fm.hpp:199
void set_modulator_warp(double amount)
Definition fm.hpp:97
OutputOversampling output_oversampling() const noexcept override
Output quality at the generic voice boundary.
Definition fm.hpp:160
void set_index(double index)
Modulation index at the strike, and how fast it collapses.
Definition fm.hpp:70
void set_carrier_wave(int wave)
The 26 shared phase-readable waves and each operator's decaying Casio-family phase warp.
Definition fm.hpp:88
void set_hard_sync(bool sync)
Reset the modulator phase whenever the carrier wraps.
Definition fm.hpp:117
void set_carrier_warp_ms(double ms)
Definition fm.hpp:100
void on_reset() override
Clears every piece of state the voice carries, so is_active() becomes false and the next render start...
Definition fm.hpp:181
OutputStage & output()
Definition fm.hpp:154
void set_modulator_wave(int wave)
Definition fm.hpp:91
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
SampleType render_fm_operator_sample(std::size_t operator_count, SampleType sample_rate, FmOperatorAliasPolicy alias_policy, std::array< SampleType, MaxOperators > &phases, std::array< SampleType, MaxOperators > &previous, const std::array< SampleType, MaxOperators > &amplitudes, const std::array< SampleType, MaxOperators > &feedback_radians, FrequencyFn &&base_frequency, PhaseRouteFn &&phase_route, FrequencyRouteFn &&frequency_route, CarrierGainFn &&carrier_gain, OscillatorFn &&oscillator) noexcept
Canonical one-sample-delayed operator evaluator shared by melodic and drum engines.
Definition fm_operator_engine.hpp:145
Definition clap.hpp:13
@ sync
Definition string.hpp:19
OutputOversampling
Realtime quality choices for the shared drum output nonlinearity.
Definition output_stage.hpp:20
constexpr std::array< FmTransient, 24 > kFmTransients
Twenty-four original procedural strike recipes.
Definition fm_tables.hpp:104
OutputStageT< float > OutputStage
Definition output_stage.hpp:384
@ feedback
Definition comb_filter.hpp:18
SvfT< float > Svf
Definition svf.hpp:148
@ bounded
Definition fm_operator_engine.hpp:35
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ bandpass
Definition filter_morph.hpp:19
NoiseColor
Spectral tilt of a NoiseSourceT, named by the usual colour convention.
Definition noise_source.hpp:17
@ white
0 dB/octave — the raw generator output.
Definition noise_source.hpp:18
DecayEnvelopeT< double > DecayEnvelope64
Definition decay_envelope.hpp:201
NoiseSourceT< float > NoiseSource
Definition noise_source.hpp:279
One routing: for each operator, a bitmask of the operators that modulate it, and whether it is heard ...
Definition fm.hpp:437
std::uint8_t modulated_by[operator_count]
Definition fm.hpp:438
std::uint8_t carriers
bit per operator
Definition fm.hpp:439
static double warp(double phase, double amount)
Casio-family phase warp: amount zero is identity; one moves the bend point close to the start of the ...
Definition fm_tables.hpp:85
static constexpr int wave_count
Definition fm_tables.hpp:17
static double read(int wave, double phase, double phase_increment=0.0)
Definition fm_tables.hpp:50
How hard a hit changes the sound, beyond changing how loud it is.
Definition velocity.hpp:33