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

The per-voice pitch ramp that glides the sampler's read rate across a control block. More...

#include <tabulasonora/pitch_ramp.hpp>

Public Member Functions

 PitchRamp (std::span< const std::int32_t > ramp_exp) noexcept
 Creates a ramp over the shared exponential table, which must outlive it.
std::int32_t increment_of (int value) const noexcept
 The 16.16 sampler increment a domain value decodes to.
void arm (double from_ratio, double to_ratio) noexcept
 Arms the ramp to glide from one ratio to another across the coming block.
bool is_active () const noexcept
 Whether the ramp still has ground to cover.
std::int32_t next_slot () noexcept
 Advances one slot and returns the increment that serves the next eight samples.

Static Public Member Functions

static int domain_of (double ratio) noexcept
 Converts a playback ratio into the ramp domain.

Static Public Attributes

static constexpr int samples_per_slot = 8
 Samples one increment serves; the engine writes four of these per 32-sample chunk.
static constexpr int unity = 0x38000
 The ramp domain's value for unity rate.
static constexpr int units_per_octave = 0x4000
 Units per octave, and the divisor that puts milli-semitones into them.
static constexpr int milli_semitone_divisor = 375
static constexpr int domain_max = 0x3FFFF
 Largest value the exponential decode accepts.

Detailed Description

The per-voice pitch ramp that glides the sampler's read rate across a control block.

The engine does not step a voice's pitch once per 10 ms tick. voice_pitch_block_init records the pitch entering the block at voice+0xb8 and the pitch leaving it at +0xbc, and ramp_env_step_eval then walks between them while the block renders, writing a fresh sampler increment every 8 samples. Without that glide a voice whose pitch moves far inside one block reads its wave from a measurably different place forever after: the sub-sample residue the glide leaves behind is frozen into the sampler's phase, which selects a different interpolator row for the rest of the note. Drum tone 1946 is the case that exposed it — its pitch envelope drops 6.671 semitones inside block 0, the engine gains 1.693481 samples across that block, and the resulting phase of 45448 costs about 1.1 dB and a visibly brighter top octave.

The ramp domain. Both endpoints are formed as 0x38000 + (milli_semitones << 9) / 375, so 0x38000 is unity and one octave is exactly 16384 units — the same octave size g_ramp_exp_tbl uses for the TVF's f, which is why both stages share the table.

Constructor & Destructor Documentation

◆ PitchRamp()

ts::PitchRamp::PitchRamp ( std::span< const std::int32_t > ramp_exp)
inlineexplicitnoexcept

Creates a ramp over the shared exponential table, which must outlive it.

Member Function Documentation

◆ domain_of()

int ts::PitchRamp::domain_of ( double ratio)
inlinestaticnodiscardnoexcept

Converts a playback ratio into the ramp domain.

The engine arrives here from milli-semitones, but the port computes a ratio, and the two agree exactly because the domain is logarithmic with a known octave size.

◆ increment_of()

std::int32_t ts::PitchRamp::increment_of ( int value) const
inlinenodiscardnoexcept

The 16.16 sampler increment a domain value decodes to.

Transcribed from ramp_env_step_eval: interpolate the table, round toward zero on the negative side, then apply the variable shift that carries the octave.

◆ arm()

void ts::PitchRamp::arm ( double from_ratio,
double to_ratio )
inlinenoexcept

Arms the ramp to glide from one ratio to another across the coming block.

The step is ((to - from) * rate) >> 13 exactly as voice_set_ramp_target_0 forms it, with the engine's maximum rate of 0xfff — which makes the ramp cover the interval in two steps, the behaviour measured on every voice examined. A ramp whose endpoints coincide stays inactive, which is the common case and costs nothing.

◆ is_active()

bool ts::PitchRamp::is_active ( ) const
inlinenodiscardnoexcept

Whether the ramp still has ground to cover.

◆ next_slot()

std::int32_t ts::PitchRamp::next_slot ( )
inlinenodiscardnoexcept

Advances one slot and returns the increment that serves the next eight samples.

Member Data Documentation

◆ samples_per_slot

int ts::PitchRamp::samples_per_slot = 8
staticconstexpr

Samples one increment serves; the engine writes four of these per 32-sample chunk.

◆ unity

int ts::PitchRamp::unity = 0x38000
staticconstexpr

The ramp domain's value for unity rate.

◆ units_per_octave

int ts::PitchRamp::units_per_octave = 0x4000
staticconstexpr

Units per octave, and the divisor that puts milli-semitones into them.

◆ milli_semitone_divisor

int ts::PitchRamp::milli_semitone_divisor = 375
staticconstexpr

◆ domain_max

int ts::PitchRamp::domain_max = 0x3FFFF
staticconstexpr

Largest value the exponential decode accepts.


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