Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
fm6.hpp
Go to the documentation of this file.
1#pragma once
2
6#include <pulp/signal/svf.hpp>
7
8#include <algorithm>
9#include <array>
10#include <cmath>
11#include <cstdint>
12
13namespace pulp::signal::drum {
14
61class Fm6DrumVoice : public Voice {
62public:
63 static constexpr int operator_count = 6;
64 static constexpr int algorithm_count = 32;
65
69 struct Algorithm {
71 std::uint8_t carriers;
72 std::uint8_t feedback_op;
73 };
74
75 // Bit i is operator i, zero-indexed (bit 0 is the instrument's OP1).
76 static constexpr Algorithm algorithms[algorithm_count] = {
77 {{0x02, 0, 0x08, 0x10, 0x20, 0}, 0x05, 5}, // 1 two stacks
78 {{0x02, 0, 0x08, 0x10, 0x20, 0}, 0x05, 1}, // 2 as 1, feedback moved
79 {{0x02, 0x04, 0, 0x10, 0x20, 0}, 0x09, 5}, // 3 three plus three
80 {{0x02, 0x04, 0, 0x10, 0x20, 0}, 0x09, 3}, // 4 as 3, feedback moved
81 {{0x02, 0, 0x08, 0, 0x20, 0}, 0x15, 5}, // 5 three pairs
82 {{0x02, 0, 0x08, 0, 0x20, 0}, 0x15, 4}, // 6 as 5, feedback moved
83 {{0x02, 0, 0x18, 0, 0x20, 0}, 0x05, 5}, // 7 pair plus branch
84 {{0x02, 0, 0x18, 0, 0x20, 0}, 0x05, 3}, // 8 as 7, feedback moved
85 {{0x02, 0, 0x18, 0, 0x20, 0}, 0x05, 1}, // 9 as 7, feedback moved
86 {{0x02, 0x04, 0, 0x30, 0, 0}, 0x09, 2}, // 10 stack plus fan-in
87 {{0x02, 0x04, 0, 0x30, 0, 0}, 0x09, 5}, // 11 as 10, feedback moved
88 {{0x02, 0, 0x38, 0, 0, 0}, 0x05, 1}, // 12 pair plus triple fan-in
89 {{0x02, 0, 0x38, 0, 0, 0}, 0x05, 5}, // 13 as 12, feedback moved
90 {{0x02, 0, 0x18, 0x20, 0, 0}, 0x05, 5}, // 14 pair plus chain
91 {{0x02, 0, 0x18, 0x20, 0, 0}, 0x05, 1}, // 15 as 14, feedback moved
92 {{0x16, 0, 0x08, 0, 0x20, 0}, 0x01, 5}, // 16 single carrier, three arms
93 {{0x16, 0, 0x08, 0, 0x20, 0}, 0x01, 1}, // 17 as 16, feedback moved
94 {{0x0E, 0, 0, 0x10, 0x20, 0}, 0x01, 2}, // 18 single carrier, deep arm
95 {{0x02, 0x04, 0, 0x20, 0x20, 0}, 0x19, 5}, // 19 stack plus shared modulator
96 {{0x04, 0x04, 0, 0x30, 0, 0}, 0x0B, 2}, // 20 shared modulator, two carriers
97 {{0x04, 0x04, 0, 0x20, 0x20, 0}, 0x1B, 2}, // 21 shared modulators, four carriers
98 {{0x02, 0, 0x20, 0x20, 0x20, 0}, 0x1D, 5}, // 22 one modulator into three
99 {{0x02, 0x04, 0x20, 0x20, 0x20, 0}, 0x1B, 5}, // 23 as 22, one arm folded in
100 {{0x02, 0, 0x20, 0x20, 0x20, 0}, 0x1F, 5}, // 24 five carriers, one arm
101 {{0, 0, 0x20, 0x20, 0x20, 0}, 0x1F, 5}, // 25 five carriers, one modulator
102 {{0x02, 0x04, 0x30, 0x20, 0x20, 0}, 0x0B, 5}, // 26 chain plus fan-in
103 {{0x02, 0x04, 0x30, 0x20, 0x20, 0}, 0x0B, 2}, // 27 as 26, feedback moved
104 {{0x02, 0, 0x18, 0x20, 0, 0}, 0x25, 4}, // 28 chain with high carrier
105 {{0x02, 0, 0x08, 0x20, 0x20, 0}, 0x17, 5}, // 29 four carriers
106 {{0x02, 0, 0x18, 0x20, 0, 0}, 0x27, 4}, // 30 chain, four carriers
107 {{0x02, 0, 0, 0, 0x20, 0}, 0x1F, 5}, // 31 five carriers, one pair
108 {{0, 0, 0, 0, 0, 0}, 0x3F, 5}, // 32 fully additive
109 };
110
113 r.level_db = 15.0f;
114 r.brightness_octaves = 1.0f;
116 }
117
118 void set_algorithm(int index) {
119 algorithm_ = std::clamp(index, 0, algorithm_count - 1);
120 }
121 int algorithm() const { return algorithm_; }
122
123 void set_tune_hz(double hz) { tune_hz_ = std::clamp(hz, 20.0, 4000.0); }
124
125 void set_operator_ratio(int op, double ratio) {
126 if (op < 0 || op >= operator_count) return;
127 ratios_[static_cast<std::size_t>(op)] = std::clamp(ratio, 0.1, 32.0);
128 }
129
130 void set_operator_level(int op, double level) {
131 if (op < 0 || op >= operator_count) return;
132 levels_[static_cast<std::size_t>(op)] = std::clamp(level, 0.0, 1.0);
133 }
134
135 void set_operator_decay_ms(int op, double ms) {
136 if (op < 0 || op >= operator_count) return;
137 decays_[static_cast<std::size_t>(op)] = std::clamp(ms, 1.0, 4000.0);
138 }
139
141 void set_feedback(double amount) { feedback_ = std::clamp(amount, 0.0, 1.0); }
142
144 void set_depth(double depth) { depth_ = std::clamp(depth, 0.0, 12.0); }
145
148 void set_pitch_sweep_octaves(double octaves) {
149 pitch_sweep_oct_ = std::clamp(octaves, -6.0, 6.0);
150 }
151 void set_pitch_sweep_ms(double ms) { pitch_sweep_ms_ = std::clamp(ms, 0.5, 1000.0); }
152
153 void set_formant_hz(double hz) { formant_hz_ = std::clamp(hz, 40.0, 18000.0); }
154 void set_formant_q(double q) { formant_q_ = std::clamp(q, 0.5, 12.0); }
155
156 OutputStage& output() { return output_; }
157 const OutputStage& output() const { return output_; }
158 OutputStage* output_stage() noexcept override { return &output_; }
159 int latency_samples() const noexcept override {
160 return output_.latency_samples();
161 }
162 OutputOversampling output_oversampling() const noexcept override {
163 return output_.oversampling();
164 }
166 output_.set_oversampling(factor);
167 }
168
169protected:
170 void on_prepare(double sample_rate) override {
171 for (auto& env : envelopes_) env.set_sample_rate(sample_rate);
172 pitch_env_.set_sample_rate(sample_rate);
173 formant_.set_sample_rate(static_cast<float>(sample_rate));
174 formant_.set_mode(Svf::Mode::bandpass);
175 output_.prepare(sample_rate);
176 }
177
178 void on_reset() override {
179 for (auto& env : envelopes_) env.reset();
180 pitch_env_.reset();
181 phases_.fill(0.0);
182 previous_.fill(0.0);
183 formant_.reset();
184 output_.reset();
185 }
186
187 void on_note_on(float velocity) override {
188 output_.reset_nonlinear_state();
189 output_.trigger();
190 const auto& response = velocity_response();
191 velocity_gain_ = response.gain(velocity);
192 applied_depth_ =
193 depth_ * static_cast<double>(response.brightness_scale(velocity));
194
195 phases_.fill(0.0);
196 previous_.fill(0.0);
197 formant_.set_frequency(
198 static_cast<float>(std::min(formant_hz_, 0.49 * sample_rate())));
199 formant_.set_resonance(static_cast<float>(formant_q_));
200
201 pitch_env_.set_attack_ms(0.0);
202 pitch_env_.set_decay_time_constant_ms(pitch_sweep_ms_);
203 pitch_env_.trigger();
204
205 for (std::size_t op = 0; op < operator_count; ++op) {
206 envelopes_[op].set_attack_ms(0.2);
207 envelopes_[op].set_decay_t60_ms(decays_[op]);
208 envelopes_[op].trigger();
209 }
210 }
211
212 bool on_is_active() const override {
213 for (const auto& env : envelopes_) {
214 if (env.is_active()) return true;
215 }
216 return output_.has_tail();
217 }
218
219 void render_add(float* out, int num_samples) override {
220 const Algorithm& alg = algorithms[static_cast<std::size_t>(algorithm_)];
221 const auto feedback_op = static_cast<std::size_t>(alg.feedback_op);
222
223 for (int i = 0; i < num_samples; ++i) {
224 const double sweep = std::exp2(pitch_sweep_oct_ * pitch_env_.process());
225 std::array<double, operator_count> amplitudes{};
226 std::array<double, operator_count> feedback{};
227 for (std::size_t op = 0; op < operator_count; ++op) {
228 amplitudes[op] = envelopes_[op].process() * levels_[op];
229 if (op == feedback_op)
230 feedback[op] = feedback_ * kFeedbackDepth;
231 }
232
233 const auto frequency = [&](std::size_t op) {
234 return tune_hz_ * ratios_[op] * sweep;
235 };
236 const auto phase_route = [&](std::size_t destination,
237 std::size_t source) {
238 return (alg.modulated_by[destination] & (1u << source))
239 ? applied_depth_
240 : 0.0;
241 };
242 const auto no_frequency_route = [](std::size_t, std::size_t) {
243 return 0.0;
244 };
245 const auto carrier = [&](std::size_t op) {
246 return (alg.carriers & (1u << op)) ? 1.0 : 0.0;
247 };
248 const auto sine = [](std::size_t, double phase_cycles,
249 double phase_offset_radians, double) {
250 return std::sin(2.0 * 3.14159265358979323846 * phase_cycles +
251 phase_offset_radians);
252 };
253 const double summed = detail::render_fm_operator_sample(
254 static_cast<std::size_t>(operator_count), sample_rate(),
255 FmOperatorAliasPolicy::bounded, phases_, previous_, amplitudes,
256 feedback, frequency, phase_route, no_frequency_route, carrier,
257 sine);
258
259 const double shaped = formant_.process(static_cast<float>(summed));
260 out[i] += static_cast<float>(
261 output_.process(static_cast<float>(shaped)) * velocity_gain_);
262 }
263 }
264
265private:
266 static constexpr double kFeedbackDepth = 6.0;
267
268 int algorithm_ = 4;
269 double tune_hz_ = 110.0;
270 double depth_ = 3.0;
271 double feedback_ = 0.0;
272 double pitch_sweep_oct_ = 0.0;
273 double pitch_sweep_ms_ = 40.0;
274 double formant_hz_ = 3000.0;
275 double formant_q_ = 0.9;
276
277 std::array<double, operator_count> ratios_ = {1.0, 1.0, 2.0, 3.0, 4.0, 7.0};
278 std::array<double, operator_count> levels_ = {1.0, 0.8, 0.7, 0.6, 0.5, 0.4};
279 std::array<double, operator_count> decays_ = {400.0, 260.0, 180.0,
280 120.0, 80.0, 50.0};
281
282 std::array<DecayEnvelope64, operator_count> envelopes_;
283 DecayEnvelope64 pitch_env_;
284 std::array<double, operator_count> phases_{};
285 std::array<double, operator_count> previous_{};
286 Svf formant_;
287 OutputStage output_;
288
289 double velocity_gain_ = 1.0;
290 double applied_depth_ = 3.0;
291};
292
293} // namespace pulp::signal::drum
void set_feedback(double amount)
Depth of the routing's designated feedback operator, 0 to 1.
Definition fm6.hpp:141
void set_output_oversampling(OutputOversampling factor) override
Definition fm6.hpp:165
bool on_is_active() const override
Definition fm6.hpp:212
void set_pitch_sweep_ms(double ms)
Definition fm6.hpp:151
void set_tune_hz(double hz)
Definition fm6.hpp:123
void on_prepare(double sample_rate) override
Sizes buffers and fixes coefficients.
Definition fm6.hpp:170
static constexpr Algorithm algorithms[algorithm_count]
Definition fm6.hpp:76
Fm6DrumVoice()
Definition fm6.hpp:111
void render_add(float *out, int num_samples) override
Adds the voice's output into out. Must add, never assign.
Definition fm6.hpp:219
void set_pitch_sweep_octaves(double octaves)
Global pitch envelope: every operator's frequency is scaled together, so the spectrum keeps its shape...
Definition fm6.hpp:148
void set_operator_level(int op, double level)
Definition fm6.hpp:130
void set_algorithm(int index)
Definition fm6.hpp:118
OutputOversampling output_oversampling() const noexcept override
Output quality at the generic voice boundary.
Definition fm6.hpp:162
static constexpr int algorithm_count
Definition fm6.hpp:64
OutputStage * output_stage() noexcept override
The terminal saturation and degradation stage, at the generic voice boundary.
Definition fm6.hpp:158
void on_reset() override
Clears every piece of state the voice carries, so is_active() becomes false and the next render start...
Definition fm6.hpp:178
OutputStage & output()
Definition fm6.hpp:156
const OutputStage & output() const
Definition fm6.hpp:157
void set_operator_ratio(int op, double ratio)
Definition fm6.hpp:125
void set_operator_decay_ms(int op, double ms)
Definition fm6.hpp:135
void on_note_on(float velocity) override
Starts a hit at the given velocity, already clamped to [0, 1].
Definition fm6.hpp:187
void set_formant_q(double q)
Definition fm6.hpp:154
int latency_samples() const noexcept override
Fixed host-rate delay introduced by this voice's selected output quality.
Definition fm6.hpp:159
int algorithm() const
Definition fm6.hpp:121
void set_formant_hz(double hz)
Definition fm6.hpp:153
void set_depth(double depth)
Overall modulation depth, scaling every routed connection.
Definition fm6.hpp:144
static constexpr int operator_count
Definition fm6.hpp:63
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
OutputOversampling
Realtime quality choices for the shared drum output nonlinearity.
Definition output_stage.hpp:20
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
@ sine
Definition lfo_sampling.hpp:54
DecayEnvelopeT< double > DecayEnvelope64
Definition decay_envelope.hpp:201
One routing.
Definition fm6.hpp:69
std::uint8_t carriers
Definition fm6.hpp:71
std::uint8_t feedback_op
Definition fm6.hpp:72
std::uint8_t modulated_by[operator_count]
Definition fm6.hpp:70
How hard a hit changes the sound, beyond changing how loud it is.
Definition velocity.hpp:33