|
Tabula Sonora 0.1.0
A native C++20 implementation of the Roland Sound Canvas VA synth voice
|
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< Voice > | active () 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. | |
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.
|
inline |
Creates a pool with the hardware's polyphony.
| 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.
|
inlinenodiscardnoexcept |
How many slots exist now. A growing pool's capacity rises during a render.
|
inlinenodiscardnoexcept |
Whether this pool grows rather than steals.
|
inlinenodiscardnoexcept |
The largest capacity reached, which is what a growing render actually needed.
|
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.
|
inlinenodiscardnoexcept |
How many times a growing pool allocated another chunk.
|
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.
|
nodiscardnoexcept |
Number of slots currently sounding.
|
inlinenodiscardnoexcept |
Starts a new note group, to be shared by that note's partials.
|
nodiscard |
Allocates one voice, stealing if necessary.
|
noexcept |
Moves every held voice for a note into its release, and reports how many.
|
inlinenoexcept |
Frees a voice whose release has finished.
|
noexcept |
Frees every voice immediately.
|
nodiscard |
The sounding voices.
|
inlinenodiscardnoexcept |
|
inlinenodiscardnoexcept |
|
inlinenodiscardnoexcept |
|
inlinenodiscardnoexcept |
|
inlinenodiscardnoexcept |
|
staticconstexpr |
The hardware's polyphony, and this pool's default.
|
staticconstexpr |
Kept as the name the rest of the engine sizes its scratch by.
|
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.
|
staticconstexpr |
Voices are rendered in groups of this size; the engine's layout is SIMD-shaped.
| 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.