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

The sampler's 4-tap FIR resampler — the single most timbre-defining element of the engine. More...

#include <tabulasonora/interpolator.hpp>

Public Member Functions

 Interpolator (const TableSet &tables)
 Creates the resampler over a loaded table set, which must outlive it.
float sample (std::span< const float > buffer, double position) const noexcept
 Reads one sample at a fractional position.
float sample_ring (std::span< const float > ring, std::int64_t index, double fraction) const noexcept
 Reads one sample from a power-of-two ring buffer at an absolute index.
void resample (std::span< const float > buffer, std::span< const double > positions, std::span< float > destination) const noexcept
 Resamples a buffer at a series of fractional positions.

Static Public Attributes

static constexpr int phase_count = 128
 Number of phase rows in the kernel.
static constexpr int tap_count = 4
 Taps per phase row.

Detailed Description

The sampler's 4-tap FIR resampler — the single most timbre-defining element of the engine.

128 phase rows of four coefficients, indexed by the top seven bits of the fractional phase, with no interpolation between rows. Every row sums to 1.0 — to within 1e-5, measured: the sums span 0.999999999970896 to 1.000009999999747, so a flat input comes back flat to about five decimal places and not to float epsilon.

The row at zero fractional phase is [0.174, 0.653, 0.173, 1e-5] — not a passthrough. That mild lowpass is applied at every sample regardless of pitch, and it is why linear interpolation sounds measurably brighter: at a quarter of the sample rate this kernel passes 0.638 where linear passes 0.707.

Deliberately scalar. Vectorising the tap sum would reassociate float addition, and float addition is not associative — the result would be close, and not the same.

Constructor & Destructor Documentation

◆ Interpolator()

ts::Interpolator::Interpolator ( const TableSet & tables)
inlineexplicit

Creates the resampler over a loaded table set, which must outlive it.

Member Function Documentation

◆ sample()

float ts::Interpolator::sample ( std::span< const float > buffer,
double position ) const
nodiscardnoexcept

Reads one sample at a fractional position.

The window reaches from i-1 to i+2, so the index is clamped to leave room for all four taps. Near a loop boundary the caller must supply the samples the loop wraps to, not whatever follows it in the buffer.

◆ sample_ring()

float ts::Interpolator::sample_ring ( std::span< const float > ring,
std::int64_t index,
double fraction ) const
nodiscardnoexcept

Reads one sample from a power-of-two ring buffer at an absolute index.

For a stream generated as it is read rather than held whole — a ping-pong traversal, whose predictor keeps accumulating in both directions and so never repeats. The window still reaches from i-1 to i+2, so the caller must have generated two samples ahead.

◆ resample()

void ts::Interpolator::resample ( std::span< const float > buffer,
std::span< const double > positions,
std::span< float > destination ) const
noexcept

Resamples a buffer at a series of fractional positions.

Member Data Documentation

◆ phase_count

int ts::Interpolator::phase_count = 128
staticconstexpr

Number of phase rows in the kernel.

◆ tap_count

int ts::Interpolator::tap_count = 4
staticconstexpr

Taps per phase row.


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