Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
fm_operator_engine.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <algorithm>
8#include <array>
9#include <cmath>
10#include <cstddef>
11#include <cstdint>
12#include <limits>
13#include <numbers>
14#include <type_traits>
15
16namespace pulp::signal {
17
19enum class FmOperatorFrequencyMode : std::uint8_t {
22};
23
34enum class FmOperatorAliasPolicy : std::uint8_t {
37};
38
49template <typename SampleType, std::size_t MaxOperators = 8> class FmOperatorRoutingT {
50 public:
51 static_assert(std::is_floating_point_v<SampleType>);
52 static_assert(MaxOperators > 0 && MaxOperators <= 64);
53
54 static constexpr SampleType kMaxPhaseDeviationRadians =
55 SampleType{8} * std::numbers::pi_v<SampleType>;
56 static constexpr SampleType kMaxFrequencyDeviationHz = SampleType{96000};
57
59 carrier_gains_[0] = SampleType{1};
60 }
61
62 void clear() noexcept {
63 for (auto& row : phase_radians_)
64 row.fill(SampleType{0});
65 for (auto& row : frequency_hz_)
66 row.fill(SampleType{0});
67 carrier_gains_.fill(SampleType{0});
68 }
69
70 void set_phase_modulation_radians(std::size_t destination, std::size_t source,
71 SampleType radians) noexcept {
72 if (!valid_index_(destination) || !valid_index_(source) || !std::isfinite(radians))
73 return;
74 phase_radians_[destination][source] =
76 }
77
78 void set_frequency_modulation_hz(std::size_t destination, std::size_t source,
79 SampleType deviation_hz) noexcept {
80 if (!valid_index_(destination) || !valid_index_(source) || !std::isfinite(deviation_hz))
81 return;
82 frequency_hz_[destination][source] =
83 std::clamp(deviation_hz, -kMaxFrequencyDeviationHz, kMaxFrequencyDeviationHz);
84 }
85
86 void set_carrier_gain(std::size_t op, SampleType linear_gain) noexcept {
87 if (!valid_index_(op) || !std::isfinite(linear_gain))
88 return;
89 carrier_gains_[op] = std::clamp(linear_gain, SampleType{0}, SampleType{1});
90 }
91
92 SampleType phase_modulation_radians(std::size_t destination,
93 std::size_t source) const noexcept {
94 return valid_index_(destination) && valid_index_(source)
95 ? phase_radians_[destination][source]
96 : SampleType{0};
97 }
98
99 SampleType frequency_modulation_hz(std::size_t destination, std::size_t source) const noexcept {
100 return valid_index_(destination) && valid_index_(source)
101 ? frequency_hz_[destination][source]
102 : SampleType{0};
103 }
104
105 SampleType carrier_gain(std::size_t op) const noexcept {
106 return valid_index_(op) ? carrier_gains_[op] : SampleType{0};
107 }
108
109 private:
110 static constexpr bool valid_index_(std::size_t index) noexcept {
111 return index < MaxOperators;
112 }
113
114 std::array<std::array<SampleType, MaxOperators>, MaxOperators> phase_radians_{};
115 std::array<std::array<SampleType, MaxOperators>, MaxOperators> frequency_hz_{};
116 std::array<SampleType, MaxOperators> carrier_gains_{};
117};
118
119namespace detail {
120
121template <typename SampleType>
122inline SampleType fm_bright_band_gain(SampleType base_hz, SampleType sample_rate,
123 FmOperatorAliasPolicy policy) noexcept {
124 if (policy == FmOperatorAliasPolicy::bounded)
125 return SampleType{1};
126 const SampleType nyquist = sample_rate * SampleType{0.5};
127 const SampleType normalized = std::abs(base_hz) / nyquist;
128 constexpr SampleType start = SampleType{0.65};
129 constexpr SampleType end = SampleType{0.90};
130 if (normalized <= start)
131 return SampleType{1};
132 if (normalized >= end)
133 return SampleType{0};
134 const SampleType x = (normalized - start) / (end - start);
135 const SampleType smooth = x * x * (SampleType{3} - SampleType{2} * x);
136 return SampleType{1} - smooth;
137}
138
142template <typename SampleType, std::size_t MaxOperators, typename FrequencyFn,
143 typename PhaseRouteFn, typename FrequencyRouteFn, typename CarrierGainFn,
144 typename OscillatorFn>
146 std::size_t operator_count, SampleType sample_rate, FmOperatorAliasPolicy alias_policy,
147 std::array<SampleType, MaxOperators>& phases, std::array<SampleType, MaxOperators>& previous,
148 const std::array<SampleType, MaxOperators>& amplitudes,
149 const std::array<SampleType, MaxOperators>& feedback_radians, FrequencyFn&& base_frequency,
150 PhaseRouteFn&& phase_route, FrequencyRouteFn&& frequency_route, CarrierGainFn&& carrier_gain,
151 OscillatorFn&& oscillator) noexcept {
152 std::array<SampleType, MaxOperators> current{};
153 const auto fail_closed = [&]() noexcept {
154 phases.fill(SampleType{0});
155 previous.fill(SampleType{0});
156 return SampleType{0};
157 };
158 if (operator_count > MaxOperators || !std::isfinite(sample_rate) ||
159 sample_rate <= SampleType{0})
160 return fail_closed();
161 const SampleType frequency_limit = sample_rate * SampleType{0.49};
162
163 for (std::size_t destination = 0; destination < operator_count; ++destination) {
164 const SampleType base_hz = base_frequency(destination);
165 if (!std::isfinite(base_hz) || !std::isfinite(amplitudes[destination]) ||
166 !std::isfinite(feedback_radians[destination]))
167 return fail_closed();
168 SampleType phase_offset = SampleType{0};
169 SampleType frequency_offset = SampleType{0};
170 for (std::size_t source = 0; source < operator_count; ++source) {
171 if (!std::isfinite(previous[source]))
172 return fail_closed();
173 const SampleType phase_depth = phase_route(destination, source);
174 const SampleType frequency_depth = frequency_route(destination, source);
175 if (!std::isfinite(phase_depth) || !std::isfinite(frequency_depth))
176 return fail_closed();
177 phase_offset += previous[source] * phase_depth;
178 frequency_offset += previous[source] * frequency_depth;
179 if (!std::isfinite(phase_offset) || !std::isfinite(frequency_offset))
180 return fail_closed();
181 }
182 const SampleType modulation_gain = fm_bright_band_gain(
183 std::abs(base_hz) + std::abs(frequency_offset), sample_rate, alias_policy);
184 phase_offset *= modulation_gain;
185 frequency_offset *= modulation_gain;
186 phase_offset += previous[destination] * feedback_radians[destination] *
187 fm_bright_band_gain(base_hz, sample_rate, alias_policy);
188 if (!std::isfinite(phase_offset) || !std::isfinite(frequency_offset))
189 return fail_closed();
190
191 const SampleType instantaneous_hz =
192 std::clamp(base_hz + frequency_offset, -frequency_limit, frequency_limit);
193 phases[destination] += instantaneous_hz / sample_rate;
194 phases[destination] -= std::floor(phases[destination]);
195 if (!std::isfinite(phases[destination]))
196 return fail_closed();
197 current[destination] = oscillator(destination, phases[destination], phase_offset,
198 instantaneous_hz / sample_rate);
199 current[destination] *= amplitudes[destination];
200 if (!std::isfinite(current[destination]))
201 return fail_closed();
202 }
203
204 SampleType output = SampleType{0};
205 SampleType carrier_sum = SampleType{0};
206 for (std::size_t op = 0; op < operator_count; ++op) {
207 const SampleType gain = carrier_gain(op);
208 if (!std::isfinite(gain) || gain < SampleType{0})
209 return fail_closed();
210 output += current[op] * gain;
211 carrier_sum += gain;
212 if (!std::isfinite(output) || !std::isfinite(carrier_sum))
213 return fail_closed();
214 }
215 if (carrier_sum > SampleType{1})
216 output /= carrier_sum;
217
218 for (std::size_t op = 0; op < operator_count; ++op)
219 current[op] = snap_to_zero(current[op]);
220
221 previous = current;
222 return snap_to_zero(output);
223}
224
225} // namespace detail
226
245template <typename SampleType = float, std::size_t MaxOperators = 8> class FmOperatorEngineT {
246 public:
247 static_assert(std::is_floating_point_v<SampleType>);
248 static_assert(MaxOperators > 0 && MaxOperators <= 64);
249
254
255 struct Envelope {
256 double delay_ms = 0.0;
257 double attack_ms = 5.0;
258 double hold_ms = 0.0;
259 double decay_ms = 100.0;
260 SampleType sustain = SampleType{1};
261 double release_ms = 100.0;
262 float attack_curve = 0.0f;
263 float decay_curve = 0.0f;
264 float release_curve = 0.0f;
265 };
266
267 static constexpr std::size_t max_operator_count() noexcept {
268 return MaxOperators;
269 }
270 static constexpr bool supports_realtime_trig_profiles() noexcept {
271 return std::is_same_v<SampleType, float>;
272 }
273 static constexpr SampleType kMinRatio = SampleType{1} / SampleType{64};
274 static constexpr SampleType kMaxRatio = SampleType{64};
275 static constexpr SampleType kMinFrequencyHz = SampleType{0.01};
276 static constexpr SampleType kMaxFrequencyHz = SampleType{96000};
277 static constexpr SampleType kMaxFeedbackRadians =
278 SampleType{8} * std::numbers::pi_v<SampleType>;
279 static constexpr SampleType kMaxKeyScalingDbPerOctave = SampleType{24};
280 static constexpr SampleType kMaxKeyScalingMagnitudeDb = SampleType{60};
281 static constexpr double kMinSampleRate = 1000.0;
282 static constexpr double kMaxSampleRate = 768000.0;
283
285 ratios_.fill(SampleType{1});
286 fixed_hz_.fill(SampleType{440});
287 levels_.fill(SampleType{1});
288 key_reference_hz_.fill(SampleType{440});
289 apply_envelopes_();
290 }
291
292 void prepare(double sample_rate) noexcept {
293 if (std::isfinite(sample_rate) && sample_rate > 0.0)
294 sample_rate_ = std::clamp(sample_rate, kMinSampleRate, kMaxSampleRate);
295 for (auto& envelope : envelopes_)
296 envelope.prepare(sample_rate_);
297 reset();
298 }
299
300 void reset() noexcept {
301 phases_.fill(SampleType{0});
302 previous_.fill(SampleType{0});
303 for (auto& envelope : envelopes_)
304 envelope.reset();
305 }
306
307 void note_on(SampleType velocity = SampleType{1}) noexcept {
308 if (!std::isfinite(velocity))
309 velocity = SampleType{0};
310 velocity = std::clamp(velocity, SampleType{0}, SampleType{1});
311 phases_.fill(SampleType{0});
312 previous_.fill(SampleType{0});
313 for (std::size_t op = 0; op < operator_count_; ++op)
314 envelopes_[op].note_on(velocity);
315 }
316
317 void note_off() noexcept {
318 for (std::size_t op = 0; op < operator_count_; ++op)
319 envelopes_[op].note_off();
320 }
321
322 void set_operator_count(std::size_t count) noexcept {
323 const std::size_t bounded = std::clamp(count, std::size_t{1}, MaxOperators);
324 if (bounded == operator_count_)
325 return;
326 operator_count_ = bounded;
327 // Operator-count changes are setup-time topology changes. Reset the
328 // whole voice so an operator cannot resume a stale envelope or phase
329 // after being disabled and later re-enabled.
330 reset();
331 }
332 std::size_t operator_count() const noexcept {
333 return operator_count_;
334 }
335
336 void set_base_frequency_hz(SampleType hz) noexcept {
337 if (!std::isfinite(hz))
338 return;
339 base_frequency_hz_ = std::clamp(hz, kMinFrequencyHz, kMaxFrequencyHz);
340 }
341 SampleType base_frequency_hz() const noexcept {
342 return base_frequency_hz_;
343 }
344
345 void set_operator_frequency_mode(std::size_t op, FrequencyMode mode) noexcept {
346 if (op >= MaxOperators || (mode != FrequencyMode::ratio && mode != FrequencyMode::fixed_hz))
347 return;
348 frequency_modes_[op] = mode;
349 }
350 FrequencyMode operator_frequency_mode(std::size_t op) const noexcept {
351 return op < MaxOperators ? frequency_modes_[op] : FrequencyMode::ratio;
352 }
353
354 void set_operator_ratio(std::size_t op, SampleType ratio) noexcept {
355 if (op >= MaxOperators || !std::isfinite(ratio))
356 return;
357 ratios_[op] = std::clamp(ratio, kMinRatio, kMaxRatio);
358 }
359 SampleType operator_ratio(std::size_t op) const noexcept {
360 return op < MaxOperators ? ratios_[op] : SampleType{1};
361 }
362
363 void set_operator_fixed_frequency_hz(std::size_t op, SampleType hz) noexcept {
364 if (op >= MaxOperators || !std::isfinite(hz))
365 return;
366 fixed_hz_[op] = std::clamp(hz, kMinFrequencyHz, kMaxFrequencyHz);
367 }
368 SampleType operator_fixed_frequency_hz(std::size_t op) const noexcept {
369 return op < MaxOperators ? fixed_hz_[op] : SampleType{0};
370 }
371
372 void set_operator_level(std::size_t op, SampleType linear_level) noexcept {
373 if (op >= MaxOperators || !std::isfinite(linear_level))
374 return;
375 levels_[op] = std::clamp(linear_level, SampleType{0}, SampleType{1});
376 }
377 SampleType operator_level(std::size_t op) const noexcept {
378 return op < MaxOperators ? levels_[op] : SampleType{0};
379 }
380
381 void set_operator_feedback_radians(std::size_t op, SampleType radians) noexcept {
382 if (op >= MaxOperators || !std::isfinite(radians))
383 return;
384 feedback_radians_[op] = std::clamp(radians, -kMaxFeedbackRadians, kMaxFeedbackRadians);
385 }
386 SampleType operator_feedback_radians(std::size_t op) const noexcept {
387 return op < MaxOperators ? feedback_radians_[op] : SampleType{0};
388 }
389
390 void set_operator_key_scaling(std::size_t op, SampleType db_per_octave,
391 SampleType reference_hz = SampleType{440}) noexcept {
392 if (op >= MaxOperators || !std::isfinite(db_per_octave) || !std::isfinite(reference_hz))
393 return;
394 key_scaling_db_per_octave_[op] =
395 std::clamp(db_per_octave, -kMaxKeyScalingDbPerOctave, kMaxKeyScalingDbPerOctave);
396 key_reference_hz_[op] = std::clamp(reference_hz, kMinFrequencyHz, kMaxFrequencyHz);
397 }
398
399 void set_operator_envelope(std::size_t op, Envelope envelope) noexcept {
400 if (op >= MaxOperators)
401 return;
402 sanitize_envelope_(envelope);
403 envelope_specs_[op] = envelope;
404 apply_envelope_(op);
405 }
406 const Envelope& operator_envelope(std::size_t op) const noexcept {
407 return envelope_specs_[std::min(op, MaxOperators - 1)];
408 }
409
410 void set_routing(const Routing& routing) noexcept {
411 routing_ = routing;
412 }
413 Routing& routing() noexcept {
414 return routing_;
415 }
416 const Routing& routing() const noexcept {
417 return routing_;
418 }
419
420 void set_alias_policy(AliasPolicy policy) noexcept {
421 if (policy != AliasPolicy::bounded && policy != AliasPolicy::bright_band_safe)
422 return;
423 alias_policy_ = policy;
424 }
425 AliasPolicy alias_policy() const noexcept {
426 return alias_policy_;
427 }
428
437 bool set_trig_profile(TrigProfile profile) noexcept {
438 if (profile != TrigProfile::reference && profile != TrigProfile::realtime_efficient &&
439 profile != TrigProfile::realtime_precise)
440 return false;
441 if constexpr (!supports_realtime_trig_profiles()) {
442 if (profile != TrigProfile::reference)
443 return false;
444 }
445 trig_profile_ = profile;
446 return true;
447 }
448 TrigProfile trig_profile() const noexcept {
449 return trig_profile_;
450 }
451
452 bool is_active() const noexcept {
453 for (std::size_t op = 0; op < operator_count_; ++op) {
454 if (envelopes_[op].active())
455 return true;
456 }
457 return false;
458 }
459
460 int latency_samples() const noexcept {
461 return 0;
462 }
463
464 int tail_samples() const noexcept {
465 long double longest = 0.0L;
466 for (std::size_t op = 0; op < operator_count_; ++op) {
467 const long double samples = static_cast<long double>(envelope_specs_[op].release_ms) *
468 0.001L * static_cast<long double>(sample_rate_);
469 longest = std::max(longest, std::round(samples));
470 }
471 return longest > static_cast<long double>(std::numeric_limits<int>::max())
472 ? std::numeric_limits<int>::max()
473 : static_cast<int>(longest);
474 }
475
476 SampleType process() noexcept {
477 switch (trig_profile_) {
478 case TrigProfile::realtime_efficient:
479 if constexpr (supports_realtime_trig_profiles())
480 return process_one_<TrigProfile::realtime_efficient>();
481 break;
482 case TrigProfile::realtime_precise:
483 if constexpr (supports_realtime_trig_profiles())
484 return process_one_<TrigProfile::realtime_precise>();
485 break;
486 case TrigProfile::reference: break;
487 }
488 return process_one_<TrigProfile::reference>();
489 }
490
491 void process(SampleType* output, std::size_t sample_count) noexcept {
492 if (output == nullptr)
493 return;
494 switch (trig_profile_) {
495 case TrigProfile::realtime_efficient:
496 if constexpr (supports_realtime_trig_profiles()) {
497 process_block_<TrigProfile::realtime_efficient>(output, sample_count);
498 return;
499 }
500 break;
501 case TrigProfile::realtime_precise:
502 if constexpr (supports_realtime_trig_profiles()) {
503 process_block_<TrigProfile::realtime_precise>(output, sample_count);
504 return;
505 }
506 break;
507 case TrigProfile::reference: break;
508 }
509 process_block_<TrigProfile::reference>(output, sample_count);
510 }
511
512 private:
513 template <TrigProfile Profile> SampleType process_one_() noexcept {
514 if (!is_active())
515 return SampleType{0};
516
517 std::array<SampleType, MaxOperators> amplitudes{};
518 for (std::size_t op = 0; op < operator_count_; ++op) {
519 const SampleType key_gain = key_gain_(op);
520 amplitudes[op] = envelopes_[op].next() * levels_[op] * key_gain;
521 }
522
523 const auto frequency = [this](std::size_t op) noexcept {
524 return frequency_modes_[op] == FrequencyMode::ratio ? base_frequency_hz_ * ratios_[op]
525 : fixed_hz_[op];
526 };
527 const auto phase_route = [this](std::size_t destination, std::size_t source) noexcept {
528 return routing_.phase_modulation_radians(destination, source);
529 };
530 const auto frequency_route = [this](std::size_t destination, std::size_t source) noexcept {
531 return routing_.frequency_modulation_hz(destination, source);
532 };
533 const auto carrier = [this](std::size_t op) noexcept { return routing_.carrier_gain(op); };
534 const auto sine = [](std::size_t, SampleType phase_cycles, SampleType phase_offset_radians,
535 SampleType) noexcept {
536 if constexpr (std::is_same_v<SampleType, float> &&
537 Profile != TrigProfile::reference) {
538 constexpr float inverse_two_pi = 0.15915494309189533577f;
539 float cycles = phase_cycles + phase_offset_radians * inverse_two_pi;
540 cycles -= std::floor(cycles);
541 return FastMath::sin_cycles<Profile>(cycles);
542 } else {
543 constexpr SampleType two_pi = SampleType{2} * std::numbers::pi_v<SampleType>;
544 return std::sin(two_pi * phase_cycles + phase_offset_radians);
545 }
546 };
547
549 operator_count_, static_cast<SampleType>(sample_rate_), alias_policy_, phases_,
550 previous_, amplitudes, feedback_radians_, frequency, phase_route, frequency_route,
551 carrier, sine);
552 }
553
554 template <TrigProfile Profile>
555 void process_block_(SampleType* output, std::size_t sample_count) noexcept {
556 for (std::size_t sample = 0; sample < sample_count; ++sample)
557 output[sample] = process_one_<Profile>();
558 }
559 static void sanitize_envelope_(Envelope& envelope) noexcept {
560 if (!std::isfinite(envelope.delay_ms))
561 envelope.delay_ms = 0.0;
562 if (!std::isfinite(envelope.attack_ms))
563 envelope.attack_ms = 0.0;
564 if (!std::isfinite(envelope.hold_ms))
565 envelope.hold_ms = 0.0;
566 if (!std::isfinite(envelope.decay_ms))
567 envelope.decay_ms = 0.0;
568 if (!std::isfinite(envelope.release_ms))
569 envelope.release_ms = 0.0;
570 if (!std::isfinite(envelope.sustain))
571 envelope.sustain = SampleType{0};
572 if (!std::isfinite(envelope.attack_curve))
573 envelope.attack_curve = 0.0f;
574 if (!std::isfinite(envelope.decay_curve))
575 envelope.decay_curve = 0.0f;
576 if (!std::isfinite(envelope.release_curve))
577 envelope.release_curve = 0.0f;
578 envelope.delay_ms = std::clamp(envelope.delay_ms, 0.0, 60000.0);
579 envelope.attack_ms = std::clamp(envelope.attack_ms, 0.0, 60000.0);
580 envelope.hold_ms = std::clamp(envelope.hold_ms, 0.0, 60000.0);
581 envelope.decay_ms = std::clamp(envelope.decay_ms, 0.0, 60000.0);
582 envelope.release_ms = std::clamp(envelope.release_ms, 0.0, 60000.0);
583 envelope.sustain = std::clamp(envelope.sustain, SampleType{0}, SampleType{1});
584 envelope.attack_curve = std::clamp(envelope.attack_curve, -1.0f, 1.0f);
585 envelope.decay_curve = std::clamp(envelope.decay_curve, -1.0f, 1.0f);
586 envelope.release_curve = std::clamp(envelope.release_curve, -1.0f, 1.0f);
587 }
588
589 void apply_envelope_(std::size_t op) noexcept {
590 auto& envelope = envelopes_[op];
591 const auto& spec = envelope_specs_[op];
592 envelope.set_delay_ms(spec.delay_ms);
593 envelope.set_attack_ms(spec.attack_ms);
594 envelope.set_hold_ms(spec.hold_ms);
595 envelope.set_decay_ms(spec.decay_ms);
596 envelope.set_sustain(spec.sustain);
597 envelope.set_release_ms(spec.release_ms);
598 envelope.set_attack_curve(spec.attack_curve);
599 envelope.set_decay_curve(spec.decay_curve);
600 envelope.set_release_curve(spec.release_curve);
601 }
602
603 void apply_envelopes_() noexcept {
604 for (std::size_t op = 0; op < MaxOperators; ++op)
605 apply_envelope_(op);
606 }
607
608 SampleType key_gain_(std::size_t op) const noexcept {
609 const SampleType octaves = std::log2(base_frequency_hz_ / key_reference_hz_[op]);
610 const SampleType db = std::clamp(key_scaling_db_per_octave_[op] * octaves,
612 return std::pow(SampleType{10}, db / SampleType{20});
613 }
614
615 double sample_rate_ = 48000.0;
616 std::size_t operator_count_ = 1;
617 SampleType base_frequency_hz_ = SampleType{440};
618 AliasPolicy alias_policy_ = AliasPolicy::bright_band_safe;
619 TrigProfile trig_profile_ = TrigProfile::reference;
620 Routing routing_{};
621
622 std::array<FrequencyMode, MaxOperators> frequency_modes_{};
623 std::array<SampleType, MaxOperators> ratios_{};
624 std::array<SampleType, MaxOperators> fixed_hz_{};
625 std::array<SampleType, MaxOperators> levels_{};
626 std::array<SampleType, MaxOperators> feedback_radians_{};
627 std::array<SampleType, MaxOperators> key_scaling_db_per_octave_{};
628 std::array<SampleType, MaxOperators> key_reference_hz_{};
629 std::array<Envelope, MaxOperators> envelope_specs_{};
630 std::array<DahdsrT<SampleType>, MaxOperators> envelopes_{};
631 std::array<SampleType, MaxOperators> phases_{};
632 std::array<SampleType, MaxOperators> previous_{};
633};
634
639
640} // namespace pulp::signal
Bounded melodic FM/PM operator engine.
Definition fm_operator_engine.hpp:245
void set_base_frequency_hz(SampleType hz) noexcept
Definition fm_operator_engine.hpp:336
static constexpr float kMaxFeedbackRadians
Definition fm_operator_engine.hpp:277
FmOperatorEngineT()
Definition fm_operator_engine.hpp:284
SampleType operator_fixed_frequency_hz(std::size_t op) const noexcept
Definition fm_operator_engine.hpp:368
bool is_active() const noexcept
Definition fm_operator_engine.hpp:452
void note_off() noexcept
Definition fm_operator_engine.hpp:317
FmOperatorFrequencyMode FrequencyMode
Definition fm_operator_engine.hpp:251
static constexpr float kMaxKeyScalingDbPerOctave
Definition fm_operator_engine.hpp:279
int latency_samples() const noexcept
Definition fm_operator_engine.hpp:460
static constexpr float kMaxFrequencyHz
Definition fm_operator_engine.hpp:276
void note_on(SampleType velocity=SampleType{1}) noexcept
Definition fm_operator_engine.hpp:307
static constexpr double kMinSampleRate
Definition fm_operator_engine.hpp:281
void prepare(double sample_rate) noexcept
Definition fm_operator_engine.hpp:292
FmOperatorAliasPolicy AliasPolicy
Definition fm_operator_engine.hpp:252
SampleType base_frequency_hz() const noexcept
Definition fm_operator_engine.hpp:341
void set_operator_feedback_radians(std::size_t op, SampleType radians) noexcept
Definition fm_operator_engine.hpp:381
int tail_samples() const noexcept
Definition fm_operator_engine.hpp:464
static constexpr float kMaxKeyScalingMagnitudeDb
Definition fm_operator_engine.hpp:280
static constexpr std::size_t max_operator_count() noexcept
Definition fm_operator_engine.hpp:267
bool set_trig_profile(TrigProfile profile) noexcept
Selects the sine implementation used by this engine instance.
Definition fm_operator_engine.hpp:437
static constexpr float kMaxRatio
Definition fm_operator_engine.hpp:274
void set_operator_fixed_frequency_hz(std::size_t op, SampleType hz) noexcept
Definition fm_operator_engine.hpp:363
const Envelope & operator_envelope(std::size_t op) const noexcept
Definition fm_operator_engine.hpp:406
std::size_t operator_count() const noexcept
Definition fm_operator_engine.hpp:332
AliasPolicy alias_policy() const noexcept
Definition fm_operator_engine.hpp:425
static constexpr float kMinRatio
Definition fm_operator_engine.hpp:273
void set_operator_ratio(std::size_t op, SampleType ratio) noexcept
Definition fm_operator_engine.hpp:354
FrequencyMode operator_frequency_mode(std::size_t op) const noexcept
Definition fm_operator_engine.hpp:350
void set_operator_level(std::size_t op, SampleType linear_level) noexcept
Definition fm_operator_engine.hpp:372
void set_operator_frequency_mode(std::size_t op, FrequencyMode mode) noexcept
Definition fm_operator_engine.hpp:345
void set_routing(const Routing &routing) noexcept
Definition fm_operator_engine.hpp:410
Routing & routing() noexcept
Definition fm_operator_engine.hpp:413
TrigProfile trig_profile() const noexcept
Definition fm_operator_engine.hpp:448
SampleType process() noexcept
Definition fm_operator_engine.hpp:476
static constexpr double kMaxSampleRate
Definition fm_operator_engine.hpp:282
static constexpr bool supports_realtime_trig_profiles() noexcept
Definition fm_operator_engine.hpp:270
SampleType operator_level(std::size_t op) const noexcept
Definition fm_operator_engine.hpp:377
SampleType operator_feedback_radians(std::size_t op) const noexcept
Definition fm_operator_engine.hpp:386
FastTrigProfile TrigProfile
Definition fm_operator_engine.hpp:253
void set_operator_key_scaling(std::size_t op, SampleType db_per_octave, SampleType reference_hz=SampleType{440}) noexcept
Definition fm_operator_engine.hpp:390
void set_alias_policy(AliasPolicy policy) noexcept
Definition fm_operator_engine.hpp:420
void set_operator_count(std::size_t count) noexcept
Definition fm_operator_engine.hpp:322
const Routing & routing() const noexcept
Definition fm_operator_engine.hpp:416
void reset() noexcept
Definition fm_operator_engine.hpp:300
static constexpr float kMinFrequencyHz
Definition fm_operator_engine.hpp:275
void process(SampleType *output, std::size_t sample_count) noexcept
Definition fm_operator_engine.hpp:491
FmOperatorRoutingT< SampleType, MaxOperators > Routing
Definition fm_operator_engine.hpp:250
SampleType operator_ratio(std::size_t op) const noexcept
Definition fm_operator_engine.hpp:359
void set_operator_envelope(std::size_t op, Envelope envelope) noexcept
Definition fm_operator_engine.hpp:399
Fixed-capacity routing for an operator network.
Definition fm_operator_engine.hpp:49
SampleType phase_modulation_radians(std::size_t destination, std::size_t source) const noexcept
Definition fm_operator_engine.hpp:92
static constexpr SampleType kMaxFrequencyDeviationHz
Definition fm_operator_engine.hpp:56
void set_phase_modulation_radians(std::size_t destination, std::size_t source, SampleType radians) noexcept
Definition fm_operator_engine.hpp:70
FmOperatorRoutingT()
Definition fm_operator_engine.hpp:58
void set_carrier_gain(std::size_t op, SampleType linear_gain) noexcept
Definition fm_operator_engine.hpp:86
void set_frequency_modulation_hz(std::size_t destination, std::size_t source, SampleType deviation_hz) noexcept
Definition fm_operator_engine.hpp:78
SampleType frequency_modulation_hz(std::size_t destination, std::size_t source) const noexcept
Definition fm_operator_engine.hpp:99
SampleType carrier_gain(std::size_t op) const noexcept
Definition fm_operator_engine.hpp:105
static constexpr SampleType kMaxPhaseDeviationRadians
Definition fm_operator_engine.hpp:54
void clear() noexcept
Definition fm_operator_engine.hpp:62
The envelope family — AR, AD, AHD, DAHDSR, and a trigger-driven modulation envelope — plus a level-in...
Scalar math helpers for real-time audio DSP.
@ envelope
Definition plugin_state_io.hpp:26
@ Profile
Definition control_grants.hpp:71
Round-2 effect-wave vocabulary.
Definition analysis_frontends.hpp:50
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
SampleType fm_bright_band_gain(SampleType base_hz, SampleType sample_rate, FmOperatorAliasPolicy policy) noexcept
Definition fm_operator_engine.hpp:122
Definition additive_bank.hpp:218
FmOperatorEngineT< double > FmOperatorEngine64
Definition fm_operator_engine.hpp:638
FmOperatorAliasPolicy
Alias behavior for the modulation network.
Definition fm_operator_engine.hpp:34
@ bounded
Definition fm_operator_engine.hpp:35
@ bright_band_safe
Definition fm_operator_engine.hpp:36
FmOperatorRoutingT< float > FmOperatorRouting
Definition fm_operator_engine.hpp:635
FmOperatorRoutingT< double > FmOperatorRouting64
Definition fm_operator_engine.hpp:636
FastTrigProfile
Semantic accuracy choices for bounded-cycle real-time trigonometry.
Definition fast_math.hpp:38
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
FmOperatorFrequencyMode
How an operator derives its unmodulated frequency.
Definition fm_operator_engine.hpp:19
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
@ fixed_hz
An absolute frequency in Hz, independent of the played key.
Definition fm_operator_engine.hpp:21
FmOperatorEngineT< float > FmOperatorEngine
Definition fm_operator_engine.hpp:637
@ sine
Definition lfo_sampling.hpp:54
static float sin_cycles(float phase_cycles) noexcept
Computes sine for a normalized phase in [0, 1).
Definition fast_math.hpp:102
Definition fm_operator_engine.hpp:255
float decay_curve
Definition fm_operator_engine.hpp:263
double release_ms
Definition fm_operator_engine.hpp:261
double hold_ms
Definition fm_operator_engine.hpp:258
SampleType sustain
Definition fm_operator_engine.hpp:260
double delay_ms
Definition fm_operator_engine.hpp:256
float attack_curve
Definition fm_operator_engine.hpp:262
double decay_ms
Definition fm_operator_engine.hpp:259
float release_curve
Definition fm_operator_engine.hpp:264
double attack_ms
Definition fm_operator_engine.hpp:257