An eight-operator FM percussion voice with a fixed algorithm table. More...
#include <fm.hpp>
Classes | |
| struct | Algorithm |
| One routing: for each operator, a bitmask of the operators that modulate it, and whether it is heard directly. More... | |
Public Member Functions | |
| Fm8DrumVoice () | |
| void | set_algorithm (int index) |
| int | algorithm () const |
| void | set_tune_hz (double hz) |
| void | set_operator_ratio (int op, double ratio) |
| Frequency ratio of one operator to the voice's tuning. | |
| void | set_operator_level (int op, double level) |
| Output level of one operator, before the algorithm's routing. | |
| void | set_operator_decay_ms (int op, double ms) |
| Decay of one operator's own envelope, as a T60. | |
| void | set_operator_feedback (int op, double amount) |
| Self-feedback on one operator. | |
| void | set_operator_wave (int op, int wave) |
| void | set_depth (double depth) |
| Overall modulation depth, scaling every routed connection at once. | |
| void | set_formant_hz (double hz) |
| A formant bandpass on the summed carriers. | |
| void | set_formant_q (double q) |
| void | set_transient (int index) |
| void | set_noise_level (double level) |
| void | set_noise_decay_ms (double ms) |
| void | set_noise_color (NoiseColor color) |
| void | set_click_level (double level) |
| OutputStage & | output () |
| const OutputStage & | output () const |
| OutputStage * | output_stage () noexcept override |
| The terminal saturation and degradation stage, at the generic voice boundary. | |
| int | latency_samples () const noexcept override |
| Fixed host-rate delay introduced by this voice's selected output quality. | |
| OutputOversampling | output_oversampling () const noexcept override |
| Output quality at the generic voice boundary. | |
| void | set_output_oversampling (OutputOversampling factor) override |
| void | prepare (double sample_rate) |
| Fixes the sample rate and clears all state. | |
| void | reset () |
| Silences the voice immediately, without a fade. | |
| void | note_on (float velocity) |
| Starts a hit. velocity is in [0, 1]; values outside are clamped. | |
| void | choke (float fade_ms=4.0f) |
| Fades the voice out over fade_ms and then silences it. | |
| bool | is_active () const |
| Whether the voice still has something to render. | |
| VoiceState | state () const |
| float | velocity () const |
| double | sample_rate () const |
| const OutputStage * | output_stage () const noexcept |
| void | set_velocity_response (const VelocityResponse &r) |
| const VelocityResponse & | velocity_response () const |
| void | process (float *out, int num_samples) |
| Adds num_samples of this voice's output into out. | |
| void | process_stereo (float *left, float *right, int num_samples) |
| Adds a stereo rendering to left and right. | |
Static Public Attributes | |
| static constexpr int | operator_count = 8 |
| static constexpr int | algorithm_count = 16 |
| static constexpr Algorithm | algorithms [algorithm_count] |
| The sixteen routings, ordered roughly from most parallel to most serial: 0 is eight independent carriers (additive), 15 is a single eight-deep stack (the most extreme spectrum the voice can make). | |
Protected Member Functions | |
| void | on_prepare (double sample_rate) override |
| Sizes buffers and fixes coefficients. | |
| void | on_reset () override |
| Clears every piece of state the voice carries, so is_active() becomes false and the next render starts from silence. | |
| void | on_note_on (float velocity) override |
| Starts a hit at the given velocity, already clamped to [0, 1]. | |
| bool | on_is_active () const override |
| void | render_add (float *out, int num_samples) override |
| Adds the voice's output into out. Must add, never assign. | |
| virtual void | render_add_stereo (float *left, float *right, int num_samples) |
| Optional stereo realization. | |
An eight-operator FM percussion voice with a fixed algorithm table.
Two operators reach bells and clanks; eight reach the dense, evolving spectra that a single modulator cannot, because each operator can modulate several others and the resulting sidebands modulate each other in turn.
Every operator reads every modulator's output one sample late. That single decision is what makes an arbitrary routing matrix computable: with delayed reads there is no ordering constraint at all, so operators can be evaluated in any order and an algorithm may contain cycles that a topological sort would reject outright. The cost is one sample of phase error per modulation hop, which at audio rates is far below what the modulation itself is doing. It is applied uniformly rather than only to paths that would otherwise be circular, so the routing table needs no analysis and adding an algorithm cannot introduce an ordering bug.
An algorithm is a bitmask per operator saying which others feed it, plus a flag saying whether it reaches the output. The sixteen below are chosen to span the useful shapes — deep serial stacks for clangorous metal, wide parallel banks for additive-like bodies, and mixed trees for everything between — rather than to reproduce any particular instrument's set. Each operator independently selects one of the same 26 clean harmonic-table waves as FM2, and the shared procedural transient table can add a tinted noise/contact strike without routing a ninth operator.
Lineage: Chowning, JAES 21(7), 1973, as for the two-operator voice.
RT contract: prepare() allocates nothing; every other method allocates nothing and takes no locks.
|
inline |
|
inline |
|
inlineinherited |
Fades the voice out over fade_ms and then silences it.
Used by choke groups; a shorter fade is more abrupt but never clicks the way an instant cut does.
|
inlineinherited |
Whether the voice still has something to render.
A kit uses this to skip voices entirely rather than summing silence.
|
inlineoverridevirtualnoexcept |
Fixed host-rate delay introduced by this voice's selected output quality.
Generic registry consumers can report it without recovering the concrete voice type.
Reimplemented from pulp::signal::drum::Voice.
|
inlineinherited |
Starts a hit. velocity is in [0, 1]; values outside are clamped.
|
inlineoverrideprotectedvirtual |
Implements pulp::signal::drum::Voice.
|
inlineoverrideprotectedvirtual |
Starts a hit at the given velocity, already clamped to [0, 1].
Implements pulp::signal::drum::Voice.
|
inlineoverrideprotectedvirtual |
Sizes buffers and fixes coefficients.
The only hook allowed to allocate.
Implements pulp::signal::drum::Voice.
|
inlineoverrideprotectedvirtual |
Clears every piece of state the voice carries, so is_active() becomes false and the next render starts from silence.
Implements pulp::signal::drum::Voice.
|
inline |
|
inline |
|
inlineoverridevirtualnoexcept |
Output quality at the generic voice boundary.
Kits use this surface to keep every registered path on one latency contract.
Reimplemented from pulp::signal::drum::Voice.
|
inlineoverridevirtualnoexcept |
The terminal saturation and degradation stage, at the generic voice boundary.
A registry consumer reaches it without recovering the concrete voice type, so adding an engine cannot desynchronise a downcast from the engine it was selected for. A voice that ends without one returns nullptr.
Reimplemented from pulp::signal::drum::Voice.
|
inlinenoexceptinherited |
|
inlineinherited |
Fixes the sample rate and clears all state.
The only method a derived voice may allocate in.
|
inlineinherited |
Adds num_samples of this voice's output into out.
|
inlineinherited |
Adds a stereo rendering to left and right.
Voices that do not provide a stereo realization are placed in the centre with half their mono signal in each channel, so summing the two channels reproduces process() exactly. A stereo-capable voice may override render_add_stereo, but the shared choke fade remains here.
|
inlineoverrideprotectedvirtual |
Adds the voice's output into out. Must add, never assign.
Implements pulp::signal::drum::Voice.
|
inlineprotectedvirtualinherited |
Optional stereo realization.
The default is centre-panned and preserves exact mono-sum equivalence.
Reimplemented in pulp::signal::drum::ClapVoice.
|
inlineinherited |
Silences the voice immediately, without a fade.
For transport stops and buffer-size changes, where a click cannot be heard because nothing is playing either side of it.
|
inlineinherited |
|
inline |
|
inline |
|
inline |
Overall modulation depth, scaling every routed connection at once.
|
inline |
A formant bandpass on the summed carriers.
|
inline |
|
inline |
|
inline |
|
inline |
|
inline |
Decay of one operator's own envelope, as a T60.
Operators decaying at different rates is what makes the spectrum evolve rather than simply fade – a modulator that dies first takes its sidebands with it.
|
inline |
Self-feedback on one operator.
|
inline |
Output level of one operator, before the algorithm's routing.
|
inline |
Frequency ratio of one operator to the voice's tuning.
|
inline |
|
inlineoverridevirtual |
Reimplemented from pulp::signal::drum::Voice.
|
inline |
|
inline |
|
inlineinherited |
|
inlineinherited |
|
inlineinherited |
|
inlineinherited |
|
staticconstexpr |
|
staticconstexpr |
The sixteen routings, ordered roughly from most parallel to most serial: 0 is eight independent carriers (additive), 15 is a single eight-deep stack (the most extreme spectrum the voice can make).
|
staticconstexpr |