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

The voice allocator. More...

#include <tabulasonora/voice_pool.hpp>

Public Member Functions

 VoicePool ()
 Creates a pool with the hardware's polyphony.
 VoicePool (int polyphony, bool growing)
 Creates a pool with a given polyphony, optionally allowed to grow past it.
int capacity () const noexcept
 How many slots exist now. A growing pool's capacity rises during a render.
bool grows () const noexcept
 Whether this pool grows rather than steals.
int high_water () const noexcept
 The largest capacity reached, which is what a growing render actually needed.
int steal_count () const noexcept
 How many times a sounding voice was taken to make room.
int growth_count () const noexcept
 How many times a growing pool allocated another chunk.
bool limit_was_reached () const noexcept
 Whether this render's output depends on the polyphony setting.
int active_count () const noexcept
 Number of slots currently sounding.
int begin_note_group () noexcept
 Starts a new note group, to be shared by that note's partials.
Voice allocate (int channel, int note, int velocity, int note_group)
 Allocates one voice, stealing if necessary.
int release (int channel, int note) noexcept
 Moves every held voice for a note into its release, and reports how many.
void free_slot (int index) noexcept
 Frees a voice whose release has finished.
void reset () noexcept
 Frees every voice immediately.
std::vector< Voiceactive () const
 The sounding voices.
VoiceState state_of (int index) const noexcept
int channel_of (int index) const noexcept
int note_of (int index) const noexcept
int velocity_of (int index) const noexcept
int note_group_of (int index) const noexcept

Public Attributes

std::function< void(int)> stealing
 Called with the index of each slot taken from a sounding note, before it is reassigned.

Static Public Attributes

static constexpr int default_polyphony = 64
 The hardware's polyphony, and this pool's default.
static constexpr int max_voices = default_polyphony
 Kept as the name the rest of the engine sizes its scratch by.
static constexpr int growth_chunk = 64
 How many slots a growing pool adds each time it would otherwise steal.
static constexpr int group_size = 4
 Voices are rendered in groups of this size; the engine's layout is SIMD-shaped.

Detailed Description

The voice allocator.

Polyphony is 64, and a note consumes one voice per sounding partial — so a two-partial patch halves the effective note count. Partials of one note share a note group so that stealing removes a whole note rather than leaving a half-sounding remnant, which is what the engine's pair-aware stealing achieves.

The stealing policy here — prefer a free slot, then the oldest releasing note, then the oldest held note — is a documented approximation. The engine's own allocator (note_group_alloc, voice_steal_run, voice_steal_oldest) was located and named during the reverse engineering but its selection rules were never traced, so this is not claimed to be faithful. It is isolated here so it can be replaced without touching any DSP.

Constructor & Destructor Documentation

◆ VoicePool() [1/2]

ts::VoicePool::VoicePool ( )
inline

Creates a pool with the hardware's polyphony.

◆ VoicePool() [2/2]

ts::VoicePool::VoicePool ( int polyphony,
bool growing )

Creates a pool with a given polyphony, optionally allowed to grow past it.

A growing pool never steals: when it runs out it allocates more slots instead. That trades a hard voice limit for an unbounded one, which is right for an offline render whose only job is to be correct, and wrong on an audio thread — growing allocates, and allocating inside the block loop is exactly what a real-time thread must not do. Nothing selects it by accident: it is off unless asked for.

Member Function Documentation

◆ capacity()

int ts::VoicePool::capacity ( ) const
inlinenodiscardnoexcept

How many slots exist now. A growing pool's capacity rises during a render.

◆ grows()

bool ts::VoicePool::grows ( ) const
inlinenodiscardnoexcept

Whether this pool grows rather than steals.

◆ high_water()

int ts::VoicePool::high_water ( ) const
inlinenodiscardnoexcept

The largest capacity reached, which is what a growing render actually needed.

◆ steal_count()

int ts::VoicePool::steal_count ( ) const
inlinenodiscardnoexcept

How many times a sounding voice was taken to make room.

The reason a caller wants this: a render that never stole is one a larger pool would not have changed, so a test can say whether its result depends on the limit at all. A render that did steal is one where 64 and 256 genuinely differ, and a digest taken at one of them says nothing about the other.

◆ growth_count()

int ts::VoicePool::growth_count ( ) const
inlinenodiscardnoexcept

How many times a growing pool allocated another chunk.

◆ limit_was_reached()

bool ts::VoicePool::limit_was_reached ( ) const
inlinenodiscardnoexcept

Whether this render's output depends on the polyphony setting.

False means the pool never ran out, so every larger limit – and the growing mode – would have produced the same audio.

◆ active_count()

int ts::VoicePool::active_count ( ) const
nodiscardnoexcept

Number of slots currently sounding.

◆ begin_note_group()

int ts::VoicePool::begin_note_group ( )
inlinenodiscardnoexcept

Starts a new note group, to be shared by that note's partials.

◆ allocate()

Voice ts::VoicePool::allocate ( int channel,
int note,
int velocity,
int note_group )
nodiscard

Allocates one voice, stealing if necessary.

◆ release()

int ts::VoicePool::release ( int channel,
int note )
noexcept

Moves every held voice for a note into its release, and reports how many.

◆ free_slot()

void ts::VoicePool::free_slot ( int index)
inlinenoexcept

Frees a voice whose release has finished.

◆ reset()

void ts::VoicePool::reset ( )
noexcept

Frees every voice immediately.

◆ active()

std::vector< Voice > ts::VoicePool::active ( ) const
nodiscard

The sounding voices.

◆ state_of()

VoiceState ts::VoicePool::state_of ( int index) const
inlinenodiscardnoexcept

◆ channel_of()

int ts::VoicePool::channel_of ( int index) const
inlinenodiscardnoexcept

◆ note_of()

int ts::VoicePool::note_of ( int index) const
inlinenodiscardnoexcept

◆ velocity_of()

int ts::VoicePool::velocity_of ( int index) const
inlinenodiscardnoexcept

◆ note_group_of()

int ts::VoicePool::note_group_of ( int index) const
inlinenodiscardnoexcept

Member Data Documentation

◆ default_polyphony

int ts::VoicePool::default_polyphony = 64
staticconstexpr

The hardware's polyphony, and this pool's default.

◆ max_voices

int ts::VoicePool::max_voices = default_polyphony
staticconstexpr

Kept as the name the rest of the engine sizes its scratch by.

◆ growth_chunk

int ts::VoicePool::growth_chunk = 64
staticconstexpr

How many slots a growing pool adds each time it would otherwise steal.

A chunk rather than one slot, because growing reallocates and a note that needs four partials should not pay for four reallocations.

◆ group_size

int ts::VoicePool::group_size = 4
staticconstexpr

Voices are rendered in groups of this size; the engine's layout is SIMD-shaped.

◆ stealing

std::function<void(int)> ts::VoicePool::stealing

Called with the index of each slot taken from a sounding note, before it is reassigned.

A host that renders voices needs this: the slot is about to be overwritten, so whatever was sounding in it has to be moved somewhere it can fade out. Cutting it dead instead clicks.


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