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

The amplitude side of a voice: the level chain that turns static bytes into a gain, and the four-segment envelope that moves it. More...

#include <tabulasonora/tva_chain.hpp>

Public Member Functions

 TvaChain (const TableSet &tables, const EnvelopeMachine &envelope)
 Creates the chain over a loaded table set and the shared machine, both of which must outlive it.
const EnvelopeMachineenvelope () const noexcept
 The shared segment-rate machine.
double amp_of (int level16) const noexcept
 Converts a 16-bit logarithmic level to a linear gain in [0, 1].
std::optional< int > partial_level (const PartialParameters &partial, int velocity) const noexcept
 The partial's velocity-crossfaded level — the byte the engine keeps at voice+0x164.
int base_level (const PartialParameters &partial, int partial_level, int key, int zone_level, int tone_level) const noexcept
 The 16-bit logarithmic base level for a voice, before the envelope moves it.
SegmentEnvelope create_envelope (const PartialParameters &partial, int velocity, int key, int zone_level=127, int tone_level=127, int sample_rate=32000, double attack_milliseconds=0.0, std::optional< int > rate_key=std::nullopt, const PartModifiers &modifiers={}) const
 Builds the amplitude envelope for one note, ready to be evaluated at any sample position.
std::vector< float > render (const PartialParameters &partial, int velocity, int key, double hold_seconds, double tail_seconds, int zone_level=127, int tone_level=127, int sample_rate=32000, double attack_milliseconds=0.0, std::optional< int > rate_key=std::nullopt) const
 Renders the amplitude envelope for one note, or an empty vector when it cannot sound.

Static Public Member Functions

static double part_volume_scale (int volume=127, int expression=127, int master=127) noexcept
 The part-level volume multiplier, relative to everything at 127.

Static Public Attributes

static constexpr double amp_scale = 2.0
 The engine hands the sampler twice the amplitude the level chain yields.
static constexpr int control_tick_hz = 100
 The control-tick rate, which is the grid note-off is acted on.

Detailed Description

The amplitude side of a voice: the level chain that turns static bytes into a gain, and the four-segment envelope that moves it.

Levels are logarithmic and, composed through the amplitude curves, work out to amplitude squaredg_level_curve through g_amp_curve is (l / 127)^2 to within 6e-05. The chain subtracts four attenuations from a base, clamping to 1 after each: the partial level, the crossfaded velocity level, the multisample zone level, and the tone's master level.

Dropping the last two is what once left a 1.15–2.06x per-voice residual against the engine's own gain word. With all four applied the reproduction is exact to the harness's print precision.

Constructor & Destructor Documentation

◆ TvaChain()

ts::TvaChain::TvaChain ( const TableSet & tables,
const EnvelopeMachine & envelope )

Creates the chain over a loaded table set and the shared machine, both of which must outlive it.

Member Function Documentation

◆ envelope()

const EnvelopeMachine & ts::TvaChain::envelope ( ) const
inlinenodiscardnoexcept

The shared segment-rate machine.

◆ amp_of()

double ts::TvaChain::amp_of ( int level16) const
nodiscardnoexcept

Converts a 16-bit logarithmic level to a linear gain in [0, 1].

The two table entries multiply to more than 32 bits before the shift, so the product must be widened. Note also that g_amp_curve_hi[0] is 4 rather than 0, so this returns 4.6e-05 at level zero — a level that has decayed past the floor must be forced to true silence rather than clamped into the table.

◆ partial_level()

std::optional< int > ts::TvaChain::partial_level ( const PartialParameters & partial,
int velocity ) const
nodiscardnoexcept

The partial's velocity-crossfaded level — the byte the engine keeps at voice+0x164.

Returns nothing when the velocity window gates the partial off. This is not the MIDI velocity, which lives at voice+0x166. Everything downstream — the second attenuation in base_level and both envelope rate multipliers — reads this instead.

◆ base_level()

int ts::TvaChain::base_level ( const PartialParameters & partial,
int partial_level,
int key,
int zone_level,
int tone_level ) const
nodiscardnoexcept

The 16-bit logarithmic base level for a voice, before the envelope moves it.

◆ create_envelope()

SegmentEnvelope ts::TvaChain::create_envelope ( const PartialParameters & partial,
int velocity,
int key,
int zone_level = 127,
int tone_level = 127,
int sample_rate = 32000,
double attack_milliseconds = 0.0,
std::optional< int > rate_key = std::nullopt,
const PartModifiers & modifiers = {} ) const
nodiscard

Builds the amplitude envelope for one note, ready to be evaluated at any sample position.

The envelope does not need to know when the note ends: call SegmentEnvelope::note_off when it does. That is what lets the real-time voice loop and the offline renderer share one implementation.

attack_milliseconds is optional attack softening; zero is faithful, since the engine's amplitude attack is instant.

rate_key indexes the two rate key-follow tables when it differs from key. The engine keeps it at voice+0x161, which voice_trigger_partials fills from the note plus a transpose; on a drum part that works out to the kit's coarse-pitch plane, so it is not the MIDI key. Nothing uses key, which is what a melodic note wants.

modifiers carries the part's envelope offsets. The amplitude envelope takes them unconditionally — unlike the filter envelope, which the partial can opt out of.

◆ render()

std::vector< float > ts::TvaChain::render ( const PartialParameters & partial,
int velocity,
int key,
double hold_seconds,
double tail_seconds,
int zone_level = 127,
int tone_level = 127,
int sample_rate = 32000,
double attack_milliseconds = 0.0,
std::optional< int > rate_key = std::nullopt ) const
nodiscard

Renders the amplitude envelope for one note, or an empty vector when it cannot sound.

The gain is deliberately left smooth per sample rather than stepped per control tick. A stepwise model looks more faithful to a control-rate gain word, but it measures worse against the engine — consistent with the envelope block's own anti-zipper ramp interpolating across the block.

◆ part_volume_scale()

double ts::TvaChain::part_volume_scale ( int volume = 127,
int expression = 127,
int master = 127 )
staticnodiscardnoexcept

The part-level volume multiplier, relative to everything at 127.

Volume and expression enter symmetrically as their product, and the result is squared. The intermediate exceeds 32 bits, so it is computed widened.

Member Data Documentation

◆ amp_scale

double ts::TvaChain::amp_scale = 2.0
staticconstexpr

The engine hands the sampler twice the amplitude the level chain yields.

◆ control_tick_hz

int ts::TvaChain::control_tick_hz = 100
staticconstexpr

The control-tick rate, which is the grid note-off is acted on.


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