Tabula Sonora 0.1.0
A native C++20 implementation of the Roland Sound Canvas VA synth voice
Loading...
Searching...
No Matches
ts::LfoEngine Class Reference

The two LFO engines that modulate every partial. More...

#include <tabulasonora/lfo_engine.hpp>

Classes

struct  Limits
 Depth clamp and rounding constant for a destination. More...

Public Member Functions

 LfoEngine (const TableSet &tables, EngineNoise *noise=nullptr)
 Creates the engine over a loaded table set, which must outlive it.
EngineNoisenoise () const noexcept
 The generator the random shapes draw from.
int waveform (int phase, int waveform_index) const noexcept
 Evaluates a waveform at a 16-bit phase.
std::pair< LfoConfig, LfoConfigconfigure (int tone_number, const PartialParameters &partial, const PartModifiers &modifiers={}) const
 Reads both LFO configurations for one partial: the tone-common LFO1 and the per-partial LFO2.
LfoRunner create_runner (const LfoConfig &config) const
 Creates a runner that steps one LFO a control tick at a time.
std::pair< LfoRunner, LfoRunnercreate_runners (int tone_number, const PartialParameters &partial, const PartModifiers &modifiers={}) const
 Creates runners for both of a partial's LFOs.
std::vector< double > run (const LfoConfig &config, int tick_count, LfoDestination destination) const
 Runs one LFO for a number of control ticks.
std::vector< double > modulation (int tone_number, const PartialParameters &partial, int tick_count, LfoDestination destination) const
 The combined LFO1 and LFO2 modulation for a partial, per control tick.
std::vector< double > pitch_modulation_with_wheel (int tone_number, const PartialParameters &partial, int tick_count, std::span< const double > mod_depth_per_tick) const
 Combined pitch modulation with a per-tick mod-wheel depth folded into LFO1.

Static Public Member Functions

static constexpr bool is_random (int waveform) noexcept
 Whether a waveform is one of the three random shapes, which the runner drives.
static constexpr Limits destination_limits (LfoDestination destination) noexcept
static int mod_wheel_depth (int controller, int depth=0x0A, int offset=0) noexcept
 The mod wheel's contribution to LFO1 pitch depth, in milli-semitones.

Static Public Attributes

static constexpr double waveform_scale = 32640.0
 Scale that maps a raw waveform value to [-1, 1].
static constexpr int slew_step = 0x50
 How far a slewed random shape moves toward its target in one control tick.

Detailed Description

The two LFO engines that modulate every partial.

A partial is modulated by both. LFO1 is tone-common, with its parameters in the tone header and its per-partial depths in the partial block; LFO2 is per-partial throughout. The asymmetry that matters is in how their rate and delay fields are read: LFO1's are indices into lookup tables, while LFO2's are used raw as per-tick increments. Verified live — LFO1 rate 43 gives increment 2817, LFO2 rate 917 gives 917.

Pitch depth is in milli-semitones, not cents. The pitch accumulator clamps at 0x1f018, which is 127 x 1000, and that is what fixes the unit.

Waveform selectors 1, 2 and 3 are the random shapes. They are not functions of the phase the way every other shape is — they redraw when the phase wraps and hold or walk toward that draw in between — so they live in ts::LfoRunner rather than in waveform below, which asks only where in the cycle the LFO is and cannot express them.

Constructor & Destructor Documentation

◆ LfoEngine()

ts::LfoEngine::LfoEngine ( const TableSet & tables,
EngineNoise * noise = nullptr )
explicit

Creates the engine over a loaded table set, which must outlive it.

noise is the engine's shared generator, which the random shapes draw from; when null a private one is used. Sharing it is not a detail — the module has exactly one generator, and the LFO shapes, the pitch start jitter and the random pan all draw from it, so which voice draws when is part of what the sequence is.

Member Function Documentation

◆ is_random()

constexpr bool ts::LfoEngine::is_random ( int waveform)
inlinestaticnodiscardconstexprnoexcept

Whether a waveform is one of the three random shapes, which the runner drives.

◆ destination_limits()

constexpr Limits ts::LfoEngine::destination_limits ( LfoDestination destination)
inlinestaticnodiscardconstexprnoexcept

◆ noise()

EngineNoise & ts::LfoEngine::noise ( ) const
inlinenodiscardnoexcept

The generator the random shapes draw from.

◆ waveform()

int ts::LfoEngine::waveform ( int phase,
int waveform_index ) const
nodiscardnoexcept

Evaluates a waveform at a 16-bit phase.

The three random shapes are not evaluable this way and return zero here; ts::LfoRunner drives them, because what they depend on is the phase having wrapped rather than the phase.

◆ configure()

std::pair< LfoConfig, LfoConfig > ts::LfoEngine::configure ( int tone_number,
const PartialParameters & partial,
const PartModifiers & modifiers = {} ) const
nodiscard

Reads both LFO configurations for one partial: the tone-common LFO1 and the per-partial LFO2.

modifiers applies the part's vibrato rate, depth and delay. They reach LFO1 alone — LFO2 has no table indices for them to bias.

◆ create_runner()

LfoRunner ts::LfoEngine::create_runner ( const LfoConfig & config) const
nodiscard

Creates a runner that steps one LFO a control tick at a time.

◆ create_runners()

std::pair< LfoRunner, LfoRunner > ts::LfoEngine::create_runners ( int tone_number,
const PartialParameters & partial,
const PartModifiers & modifiers = {} ) const
nodiscard

Creates runners for both of a partial's LFOs.

◆ run()

std::vector< double > ts::LfoEngine::run ( const LfoConfig & config,
int tick_count,
LfoDestination destination ) const
nodiscard

Runs one LFO for a number of control ticks.

◆ modulation()

std::vector< double > ts::LfoEngine::modulation ( int tone_number,
const PartialParameters & partial,
int tick_count,
LfoDestination destination ) const
nodiscard

The combined LFO1 and LFO2 modulation for a partial, per control tick.

The first tick is always zero: the LFO object is created after that tick's control update, so nothing has been applied yet. Aligning to that is what makes the comparison against the engine's own trace line up.

◆ pitch_modulation_with_wheel()

std::vector< double > ts::LfoEngine::pitch_modulation_with_wheel ( int tone_number,
const PartialParameters & partial,
int tick_count,
std::span< const double > mod_depth_per_tick ) const
nodiscard

Combined pitch modulation with a per-tick mod-wheel depth folded into LFO1.

The wheel's contribution is added after the fade-in, then the total is clamped to +-6000 — six semitones, the GS full-scale figure. Only LFO1 takes it; LFO2 is unaffected.

◆ mod_wheel_depth()

int ts::LfoEngine::mod_wheel_depth ( int controller,
int depth = 0x0A,
int offset = 0 )
staticnodiscardnoexcept

The mod wheel's contribution to LFO1 pitch depth, in milli-semitones.

Summed with the tone's own pitch depth after the cents table, and the total is then clamped to +-6000.

Member Data Documentation

◆ waveform_scale

double ts::LfoEngine::waveform_scale = 32640.0
staticconstexpr

Scale that maps a raw waveform value to [-1, 1].

◆ slew_step

int ts::LfoEngine::slew_step = 0x50
staticconstexpr

How far a slewed random shape moves toward its target in one control tick.


The documentation for this class was generated from the following file: