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

The pitch side of a voice: key-follow, transposition, the pitch envelope, and bend. More...

#include <tabulasonora/pitch_chain.hpp>

Classes

struct  KeyFollow
 The effective pitch key and its fractional crossfade weight. More...

Public Member Functions

 PitchChain (const TableSet &tables, const EnvelopeMachine &envelope, EngineNoise *noise=nullptr)
 Creates the chain over a loaded table set and machine, both of which must outlive it.
int base_pitch_milli_semitones (const PartialParameters &partial, int note, int key_center=0x3C) const noexcept
 The note's absolute base pitch in milli-semitones.
int portamento_step (int time) const noexcept
 The portamento glide step for a CC#5 time byte, in milli-semitones per control tick.
std::optional< PitchEnvelopeenvelope_offsets (const PartialParameters &partial, int key, int velocity) const noexcept
 Decodes the pitch envelope's targets and timings, or nothing when it is disabled.
std::optional< std::vector< double > > envelope_ticks (const PartialParameters &partial, int key, int velocity, double hold_seconds, int tick_count) const
 The pitch envelope's offset at each control tick, or nothing when it is inactive.
std::optional< PitchEnvelopeRunnercreate_envelope_runner (const PartialParameters &partial, int key, int velocity) const
 Creates a runner for a partial's pitch envelope, or nothing when it does nothing.

Static Public Member Functions

static KeyFollow key_follow_key (const PartialParameters &partial, int note, int key_center=0x3C) noexcept
 The effective pitch key and its fractional crossfade weight.
static int drum_pitch_milli_semitones (const PartialParameters &partial, int coarse_pitch) noexcept
 A drum key's absolute pitch in milli-semitones, from its coarse-pitch plane value.
static int portamento_offset (int from_key, int target_pitch) noexcept
 The glide offset a note starts with, in milli-semitones, decaying to zero as it completes.
static double bend_offset_milli_semitones (int bend=8192, double semitone_range=2.0) noexcept
 Pitch-bend offset in milli-semitones, from a 14-bit bend with 8192 centre.
static int segment_rate_word (double milliseconds) noexcept
 The segment machine's rate word for a duration.
static int start_jitter_milli_semitones (int depth, std::uint16_t draw) noexcept
 The random start-pitch jitter for one note-on, in milli-semitones.
static double ratio (const PartialParameters &partial, const WaveDescriptor &descriptor, double pitch_milli_semitones) noexcept
 The playback ratio for a partial, from its absolute pitch against the sample's own root.
static double clamp (double milli_semitones) noexcept
 Clamps an absolute pitch the way the engine's accumulator does.

Static Public Attributes

static constexpr int max_pitch_milli_semitones = 0x1F018
 Upper clamp of the pitch accumulator: 127 semitones in milli-semitones.
static constexpr int drum_key_centre = 60
 The key a drum's coarse-pitch plane pivots around: 60 is its natural pitch.

Detailed Description

The pitch side of a voice: key-follow, transposition, the pitch envelope, and bend.

Pitch is absolute, in milli-semitones, and the engine clamps the accumulator to [0, 0x1f018] — 127 semitones x 1000, which is what fixes the unit. Working in offsets from a base cannot express that clamp, and at least one patch sits exactly on it: Jetplane's first partial has a base of 24000 with an envelope starting at -24000, landing on zero.

Constructor & Destructor Documentation

◆ PitchChain()

ts::PitchChain::PitchChain ( const TableSet & tables,
const EnvelopeMachine & envelope,
EngineNoise * noise = nullptr )

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

noise is the engine's shared generator; when null a private one is used, which keeps a standalone chain usable but gives it its own draw sequence.

Member Function Documentation

◆ key_follow_key()

KeyFollow ts::PitchChain::key_follow_key ( const PartialParameters & partial,
int note,
int key_center = 0x3C )
staticnodiscardnoexcept

The effective pitch key and its fractional crossfade weight.

block[0x13] is the follow amount: 0x40 is no follow (the key centre sounds regardless of the note), 0x4a is full follow, and values between scale the note's distance from the centre. A reduced-follow patch like the Seashore effect plays note 33 as key 55, which keeps its noise bright instead of an octave-too-low rumble.

◆ base_pitch_milli_semitones()

int ts::PitchChain::base_pitch_milli_semitones ( const PartialParameters & partial,
int note,
int key_center = 0x3C ) const
nodiscardnoexcept

The note's absolute base pitch in milli-semitones.

◆ drum_pitch_milli_semitones()

int ts::PitchChain::drum_pitch_milli_semitones ( const PartialParameters & partial,
int coarse_pitch )
staticnodiscardnoexcept

A drum key's absolute pitch in milli-semitones, from its coarse-pitch plane value.

A drum note does not transpose its sample; the coarse-pitch plane supplies the key instead, pivoting on drum_key_centre. What a step of that plane is worth is then the tone's own business: it runs through the same block[0x13] key-follow ladder as a melodic note, so a 100%-follow tone moves a whole semitone per unit and a 50%-follow tone half of one.

Unlike the melodic chain this omits the g_kf_pitch key-follow curve: the engine gates that term off for drums, and the measured pitches are exact multiples of 1000 where the curve would have contributed -11 or +3.

◆ portamento_step()

int ts::PitchChain::portamento_step ( int time) const
nodiscardnoexcept

The portamento glide step for a CC#5 time byte, in milli-semitones per control tick.

◆ portamento_offset()

int ts::PitchChain::portamento_offset ( int from_key,
int target_pitch )
staticnodiscardnoexcept

The glide offset a note starts with, in milli-semitones, decaying to zero as it completes.

The engine takes the source key at a flat key * 1000 rather than running it back through the key-follow chain, so a glide always departs from equal temperament.

◆ bend_offset_milli_semitones()

double ts::PitchChain::bend_offset_milli_semitones ( int bend = 8192,
double semitone_range = 2.0 )
staticnodiscardnoexcept

Pitch-bend offset in milli-semitones, from a 14-bit bend with 8192 centre.

◆ envelope_offsets()

std::optional< PitchEnvelope > ts::PitchChain::envelope_offsets ( const PartialParameters & partial,
int key,
int velocity ) const
nodiscardnoexcept

Decodes the pitch envelope's targets and timings, or nothing when it is disabled.

◆ segment_rate_word()

int ts::PitchChain::segment_rate_word ( double milliseconds)
staticnodiscardnoexcept

The segment machine's rate word for a duration.

0xffff wraps the 16-bit phase in exactly one control tick, so even a zero-length segment still occupies one tick.

◆ envelope_ticks()

std::optional< std::vector< double > > ts::PitchChain::envelope_ticks ( const PartialParameters & partial,
int key,
int velocity,
double hold_seconds,
int tick_count ) const
nodiscard

The pitch envelope's offset at each control tick, or nothing when it is inactive.

Tick i is the state after that tick's update.

◆ start_jitter_milli_semitones()

int ts::PitchChain::start_jitter_milli_semitones ( int depth,
std::uint16_t draw )
staticnodiscardnoexcept

The random start-pitch jitter for one note-on, in milli-semitones.

Bit 14 of the draw picks the sign. The magnitude slice is asymmetric — 7 bits on the positive side, 8 on the negative — so the range is about [-10*d, +5*d], and that asymmetry is the hardware's, not an artefact.

◆ create_envelope_runner()

std::optional< PitchEnvelopeRunner > ts::PitchChain::create_envelope_runner ( const PartialParameters & partial,
int key,
int velocity ) const
nodiscard

Creates a runner for a partial's pitch envelope, or nothing when it does nothing.

An envelope whose start, four targets and release are all zero is not merely flat — it is absent, and skipping it saves the pitch chain a per-tick update on most patches.

A non-zero block byte 0x1a draws start jitter here, one draw per partial voice, matching the engine (which skips the draw entirely when the byte is zero, so unaffected patches consume nothing from the generator).

◆ ratio()

double ts::PitchChain::ratio ( const PartialParameters & partial,
const WaveDescriptor & descriptor,
double pitch_milli_semitones )
staticnodiscardnoexcept

The playback ratio for a partial, from its absolute pitch against the sample's own root.

◆ clamp()

double ts::PitchChain::clamp ( double milli_semitones)
staticnodiscardnoexcept

Clamps an absolute pitch the way the engine's accumulator does.

Member Data Documentation

◆ max_pitch_milli_semitones

int ts::PitchChain::max_pitch_milli_semitones = 0x1F018
staticconstexpr

Upper clamp of the pitch accumulator: 127 semitones in milli-semitones.

◆ drum_key_centre

int ts::PitchChain::drum_key_centre = 60
staticconstexpr

The key a drum's coarse-pitch plane pivots around: 60 is its natural pitch.


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