51 static_assert(std::is_floating_point_v<SampleType>);
52 static_assert(MaxOperators > 0 && MaxOperators <= 64);
55 SampleType{8} * std::numbers::pi_v<SampleType>;
59 carrier_gains_[0] = SampleType{1};
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});
71 SampleType radians)
noexcept {
72 if (!valid_index_(destination) || !valid_index_(source) || !std::isfinite(radians))
74 phase_radians_[destination][source] =
79 SampleType deviation_hz)
noexcept {
80 if (!valid_index_(destination) || !valid_index_(source) || !std::isfinite(deviation_hz))
82 frequency_hz_[destination][source] =
87 if (!valid_index_(op) || !std::isfinite(linear_gain))
89 carrier_gains_[op] = std::clamp(linear_gain, SampleType{0}, SampleType{1});
93 std::size_t source)
const noexcept {
94 return valid_index_(destination) && valid_index_(source)
95 ? phase_radians_[destination][source]
100 return valid_index_(destination) && valid_index_(source)
101 ? frequency_hz_[destination][source]
106 return valid_index_(op) ? carrier_gains_[op] : SampleType{0};
110 static constexpr bool valid_index_(std::size_t index)
noexcept {
111 return index < MaxOperators;
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_{};
121template <
typename SampleType>
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;
142template <
typename SampleType, std::size_t MaxOperators,
typename FrequencyFn,
143 typename PhaseRouteFn,
typename FrequencyRouteFn,
typename CarrierGainFn,
144 typename OscillatorFn>
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};
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};
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();
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] *
188 if (!std::isfinite(phase_offset) || !std::isfinite(frequency_offset))
189 return fail_closed();
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();
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;
212 if (!std::isfinite(output) || !std::isfinite(carrier_sum))
213 return fail_closed();
215 if (carrier_sum > SampleType{1})
216 output /= carrier_sum;
218 for (std::size_t op = 0; op < operator_count; ++op)
247 static_assert(std::is_floating_point_v<SampleType>);
248 static_assert(MaxOperators > 0 && MaxOperators <= 64);
271 return std::is_same_v<SampleType, float>;
273 static constexpr SampleType
kMinRatio = SampleType{1} / SampleType{64};
278 SampleType{8} * std::numbers::pi_v<SampleType>;
285 ratios_.fill(SampleType{1});
286 fixed_hz_.fill(SampleType{440});
287 levels_.fill(SampleType{1});
288 key_reference_hz_.fill(SampleType{440});
293 if (std::isfinite(sample_rate) && sample_rate > 0.0)
295 for (
auto& envelope : envelopes_)
296 envelope.prepare(sample_rate_);
301 phases_.fill(SampleType{0});
302 previous_.fill(SampleType{0});
303 for (
auto& envelope : envelopes_)
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);
318 for (std::size_t op = 0; op < operator_count_; ++op)
323 const std::size_t
bounded = std::clamp(count, std::size_t{1}, MaxOperators);
324 if (
bounded == operator_count_)
333 return operator_count_;
337 if (!std::isfinite(hz))
342 return base_frequency_hz_;
346 if (op >= MaxOperators || (mode != FrequencyMode::ratio && mode != FrequencyMode::fixed_hz))
348 frequency_modes_[op] = mode;
351 return op < MaxOperators ? frequency_modes_[op] : FrequencyMode::ratio;
355 if (op >= MaxOperators || !std::isfinite(
ratio))
360 return op < MaxOperators ? ratios_[op] : SampleType{1};
364 if (op >= MaxOperators || !std::isfinite(hz))
369 return op < MaxOperators ? fixed_hz_[op] : SampleType{0};
373 if (op >= MaxOperators || !std::isfinite(linear_level))
375 levels_[op] = std::clamp(linear_level, SampleType{0}, SampleType{1});
378 return op < MaxOperators ? levels_[op] : SampleType{0};
382 if (op >= MaxOperators || !std::isfinite(radians))
387 return op < MaxOperators ? feedback_radians_[op] : SampleType{0};
391 SampleType reference_hz = SampleType{440})
noexcept {
392 if (op >= MaxOperators || !std::isfinite(db_per_octave) || !std::isfinite(reference_hz))
394 key_scaling_db_per_octave_[op] =
400 if (op >= MaxOperators)
402 sanitize_envelope_(envelope);
403 envelope_specs_[op] = envelope;
407 return envelope_specs_[std::min(op, MaxOperators - 1)];
421 if (policy != AliasPolicy::bounded && policy != AliasPolicy::bright_band_safe)
423 alias_policy_ = policy;
426 return alias_policy_;
438 if (profile != TrigProfile::reference && profile != TrigProfile::realtime_efficient &&
439 profile != TrigProfile::realtime_precise)
442 if (profile != TrigProfile::reference)
445 trig_profile_ = profile;
449 return trig_profile_;
453 for (std::size_t op = 0; op < operator_count_; ++op) {
454 if (envelopes_[op].active())
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));
471 return longest >
static_cast<long double>(std::numeric_limits<int>::max())
472 ? std::numeric_limits<int>::max()
473 :
static_cast<int>(longest);
477 switch (trig_profile_) {
478 case TrigProfile::realtime_efficient:
480 return process_one_<TrigProfile::realtime_efficient>();
482 case TrigProfile::realtime_precise:
484 return process_one_<TrigProfile::realtime_precise>();
486 case TrigProfile::reference:
break;
488 return process_one_<TrigProfile::reference>();
491 void process(SampleType* output, std::size_t sample_count)
noexcept {
492 if (output ==
nullptr)
494 switch (trig_profile_) {
495 case TrigProfile::realtime_efficient:
497 process_block_<TrigProfile::realtime_efficient>(output, sample_count);
501 case TrigProfile::realtime_precise:
503 process_block_<TrigProfile::realtime_precise>(output, sample_count);
507 case TrigProfile::reference:
break;
509 process_block_<TrigProfile::reference>(output, sample_count);
513 template <TrigProfile Profile> SampleType process_one_() noexcept {
515 return SampleType{0};
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;
523 const auto frequency = [
this](std::size_t op)
noexcept {
524 return frequency_modes_[op] == FrequencyMode::ratio ? base_frequency_hz_ * ratios_[op]
527 const auto phase_route = [
this](std::size_t destination, std::size_t source)
noexcept {
528 return routing_.phase_modulation_radians(destination, source);
530 const auto frequency_route = [
this](std::size_t destination, std::size_t source)
noexcept {
531 return routing_.frequency_modulation_hz(destination, source);
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);
543 constexpr SampleType two_pi = SampleType{2} * std::numbers::pi_v<SampleType>;
544 return std::sin(two_pi * phase_cycles + phase_offset_radians);
549 operator_count_,
static_cast<SampleType
>(sample_rate_), alias_policy_, phases_,
550 previous_, amplitudes, feedback_radians_, frequency, phase_route, frequency_route,
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>();
559 static void sanitize_envelope_(
Envelope& envelope)
noexcept {
560 if (!std::isfinite(
envelope.delay_ms))
562 if (!std::isfinite(
envelope.attack_ms))
564 if (!std::isfinite(
envelope.hold_ms))
566 if (!std::isfinite(
envelope.decay_ms))
568 if (!std::isfinite(
envelope.release_ms))
570 if (!std::isfinite(
envelope.sustain))
572 if (!std::isfinite(
envelope.attack_curve))
574 if (!std::isfinite(
envelope.decay_curve))
576 if (!std::isfinite(
envelope.release_curve))
583 envelope.sustain = std::clamp(
envelope.sustain, SampleType{0}, SampleType{1});
589 void apply_envelope_(std::size_t op)
noexcept {
591 const auto& spec = envelope_specs_[op];
592 envelope.set_delay_ms(spec.delay_ms);
593 envelope.set_attack_ms(spec.attack_ms);
595 envelope.set_decay_ms(spec.decay_ms);
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);
603 void apply_envelopes_() noexcept {
604 for (std::size_t op = 0; op < MaxOperators; ++op)
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});
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;
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_{};
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.
@ 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 > &litudes, 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