arduino-audio-tools
Loading...
Searching...
No Matches
Public Member Functions | Protected Member Functions | Protected Attributes | List of all members
AdaptiveResamplingBuffer Class Reference

A BaseBuffer<uint8_t> that wraps a raw (unresampled) backing buffer and transparently resamples on read to correct jitter and compensate for the slightly different clock rates between an audio source and audio target. Use separate tasks to write and read the data. Also make sure that you protect the access with a mutex or provide a thread-safe backing buffer! More...

#include <AdaptiveResamplingBuffer.h>

Inheritance diagram for AdaptiveResamplingBuffer:
BaseBuffer< uint8_t > AudioInfoSupport

Public Member Functions

 AdaptiveResamplingBuffer ()=default
 Construct a new AdaptiveResamplingBuffer object You need to call setBuffer() and setStepRangePercent() before begin()
 
 AdaptiveResamplingBuffer (BaseBuffer< uint8_t > &buffer, float stepRangePercent=5.0f)
 Construct a new AdaptiveResamplingBuffer object.
 
 AdaptiveResamplingBuffer (const AdaptiveResamplingBuffer &)=delete
 
uint8_taddress () override
 Not supported: resampled data has no contiguous physical representation.
 
AudioInfo audioInfo () override
 provides the actual input AudioInfo
 
virtual AudioInfo audioInfoOut ()
 
int available () override
 
int availableForWrite () override
 Number of raw bytes that can still be written to the backing buffer.
 
float averageStepSize ()
 Long-run average step size implied by cumulative bytes written vs. bytes read, used as the feedforward center for recalculate(). This converges to the true average clock-drift ratio between producer and consumer and gets less noisy the longer the stream runs, unlike the instantaneous buffer fill level. Returns 1.0 until enough bytes have flowed to make the ratio meaningful (see setMinBytesForDriftEstimate()).
 
bool begin ()
 Initialize the buffer and internal components.
 
virtual int bufferCountEmpty ()
 Provides the number of entries that are available to write: -1 does not apply.
 
virtual int bufferCountFilled ()
 Provides the number of entries that are available to read: -1 does not apply.
 
void clear ()
 same as reset
 
virtual int clearArray (int len)
 Removes the next len entries.
 
void end ()
 End the buffer and release resources.
 
virtual void flush ()
 
bool isEmpty ()
 
virtual bool isFull ()
 checks if the buffer is full
 
bool isPrimed ()
 
float levelPercent () override
 Current actual fill level of the backing buffer in percent (0-100).
 
float levelPercentSmoothed ()
 Get the Kalman-smoothed fill level from the last recalculate() call, in percent.
 
AdaptiveResamplingBufferoperator= (const AdaptiveResamplingBuffer &)=delete
 
bool peek (uint8_t &result) override
 
bool read (uint8_t &result) override
 Reads a single resampled byte.
 
int readArray (uint8_t data[], int len) override
 
float recalculate ()
 Recalculate the resampling step size based on buffer fill level. Called automatically by writeArray().
 
void reset () override
 
virtual bool resize (size_t bytes)
 Resizes the buffer if supported: returns false if not supported.
 
void setAudioInfo (AudioInfo info) override
 Defines the audio format: needed to correctly interpret and resample the sample frames stored in the backing buffer.
 
void setBuffer (BaseBuffer< uint8_t > &buffer)
 Set the raw (unresampled) backing buffer.
 
void setKalmanParameters (float process_noise, float measurement_noise)
 Set the Kalman filter parameters.
 
void setMinBytesForDriftEstimate (uint32_t bytes)
 Minimum cumulative output bytes that must have been read before averageStepSize() is trusted; below this it returns 1.0. Avoids acting on a ratio computed from a handful of bytes right after begin(). Default is 4096.
 
void setPIDParameters (float p_value, float i_value, float d_value)
 Set the PID controller parameters.
 
void setStartFillPercent (float percent)
 Defines the fill level (in percent of the backing buffer's capacity) that must be reached once, right after begin(), before any data is provided to the reader. This avoids starting playback only to immediately underrun again. Defaults to 50%. Priming only happens once at the start of a session (i.e. after begin()/reset()); it does not re-arm on a later underflow.
 
void setStepRangePercent (float rangePercent)
 Set the allowed resampling range as a percent.
 
size_t size () override
 Capacity (in bytes) of the backing buffer.
 
float stepSize ()
 
uint64_t totalBytesRead ()
 
uint64_t totalBytesWritten ()
 
bool write (uint8_t data) override
 Writes a single byte to the backing buffer.
 
int writeArray (const uint8_t data[], int len) override
 
virtual int writeArrayOverwrite (const uint8_t data[], int len)
 Fills the buffer data and overwrites the oldest data if the buffer is full.
 

Protected Member Functions

bool checkPrimed ()
 
void resetDriftCounters ()
 Resets the cumulative counters used by averageStepSize().
 

Protected Attributes

AudioInfo audio_info
 
float d = 0.0001
 
bool has_peek = false
 
float i = 0.00005
 
bool is_active = false
 
bool is_primed = false
 
KalmanFilter kalman_filter {0.01f, 0.1f}
 
uint32_t last_time_ms = 0
 
float level_percent_smoothed = 0.0
 
uint32_t min_bytes_for_drift_estimate = 4096
 
float p = 0.005
 
BaseBuffer< uint8_t > * p_buffer = nullptr
 
uint8_t peek_byte = 0
 
PIDController pid
 
QueueStream< uint8_tqueue_stream
 
uint32_t recalc_count = 0
 
float resample_range = 0
 
ResampleStream resample_stream
 
float start_fill_percent = 50.0f
 
std::atomic< floatstep_size {1.0f}
 
uint64_t total_bytes_read = 0
 
uint64_t total_bytes_written = 0
 

Detailed Description

A BaseBuffer<uint8_t> that wraps a raw (unresampled) backing buffer and transparently resamples on read to correct jitter and compensate for the slightly different clock rates between an audio source and audio target. Use separate tasks to write and read the data. Also make sure that you protect the access with a mutex or provide a thread-safe backing buffer!

The resampling step size combines a feedforward term - the long-run average step size implied by cumulative bytes written vs. bytes read, see averageStepSize() - with feedback from a PID controller reacting to the (Kalman-filter-smoothed) instantaneous buffer fill level. The feedforward term absorbs genuine long-term clock drift; the PID only needs to correct short-term jitter around it, which is considerably more stable than relying on the PID alone.

The calculated step size (this class's own copy, read via stepSize() and used by available()) is a std::atomic<float>, since it's written by the writer task (in writeArray()/recalculate()) and read by the reader task (in available()) - this rules out torn reads on any platform, including 8-bit targets like AVR where a 32-bit float load/store isn't a single bus transaction.

Note this only covers this class's own copy of the value. The copy actually consumed on the hot path - ResampleStream's internal step size, read on every interpolation iteration inside readArray() - is a plain (non-atomic) float, since ResampleStream is a general-purpose, usually single-threaded class and making its hot-path field atomic would add a memory-barrier cost for all its other users too. In practice this is a low-risk, deliberate trade-off: on 32-bit targets (ESP32, RP2040, ARM) an aligned float load/store is already a single atomic bus transaction in hardware, so a torn read there is not possible; only 8-bit targets are theoretically exposed, and a torn read there would at worst cause one glitchy resample step, never a crash.

Author
Phil Schatzmann

Constructor & Destructor Documentation

◆ AdaptiveResamplingBuffer() [1/3]

Construct a new AdaptiveResamplingBuffer object You need to call setBuffer() and setStepRangePercent() before begin()

◆ AdaptiveResamplingBuffer() [2/3]

AdaptiveResamplingBuffer ( BaseBuffer< uint8_t > &  buffer,
float  stepRangePercent = 5.0f 
)
inline

Construct a new AdaptiveResamplingBuffer object.

Parameters
bufferReference to the raw (unresampled) backing buffer
stepRangePercentAllowed resampling range in percent (default: 5.0)

◆ AdaptiveResamplingBuffer() [3/3]

Member Function Documentation

◆ address()

uint8_t * address ( )
inlineoverridevirtual

Not supported: resampled data has no contiguous physical representation.

Implements BaseBuffer< uint8_t >.

◆ audioInfo()

AudioInfo audioInfo ( )
inlineoverridevirtual

provides the actual input AudioInfo

Implements AudioInfoSupport.

◆ audioInfoOut()

virtual AudioInfo audioInfoOut ( )
inlinevirtualinherited

◆ available()

int available ( )
inlineoverridevirtual

Estimated number of resampled bytes currently available to read: the raw backing buffer's fill scaled by the current step size. This is an estimate, not an exact promise - actual interpolation/frame alignment in readArray() can return slightly more or less - but it's a cheap, side-effect-free query, unlike actually running the resampler ahead of time just to count bytes (which would itself drain the backing buffer and distort the PID's fill-level feedback - see recalculate()).

Implements BaseBuffer< uint8_t >.

◆ availableForWrite()

int availableForWrite ( )
inlineoverridevirtual

Number of raw bytes that can still be written to the backing buffer.

Implements BaseBuffer< uint8_t >.

◆ averageStepSize()

float averageStepSize ( )
inline

Long-run average step size implied by cumulative bytes written vs. bytes read, used as the feedforward center for recalculate(). This converges to the true average clock-drift ratio between producer and consumer and gets less noisy the longer the stream runs, unlike the instantaneous buffer fill level. Returns 1.0 until enough bytes have flowed to make the ratio meaningful (see setMinBytesForDriftEstimate()).

Returns
float Estimated average step size

◆ begin()

bool begin ( )
inline

Initialize the buffer and internal components.

Returns
true if initialization was successful, false otherwise

◆ bufferCountEmpty()

virtual int bufferCountEmpty ( )
inlinevirtualinherited

Provides the number of entries that are available to write: -1 does not apply.

◆ bufferCountFilled()

virtual int bufferCountFilled ( )
inlinevirtualinherited

Provides the number of entries that are available to read: -1 does not apply.

◆ checkPrimed()

bool checkPrimed ( )
inlineprotected

Returns true once priming is complete; latches permanently once the fill threshold has been reached (does not re-arm on later underflow).

◆ clear()

void clear ( )
inlineinherited

same as reset

◆ clearArray()

virtual int clearArray ( int  len)
inlinevirtualinherited

Removes the next len entries.

◆ end()

void end ( )
inline

End the buffer and release resources.

◆ flush()

virtual void flush ( )
inlinevirtualinherited

Submit any partially-filled write buffer so the reader can access it. Only meaningful for NBuffer-style block pools; no-op for ring buffers.

◆ isEmpty()

bool isEmpty ( )
inlineinherited

◆ isFull()

virtual bool isFull ( )
inlinevirtualinherited

checks if the buffer is full

◆ isPrimed()

bool isPrimed ( )
inline

True once the buffer has reached setStartFillPercent() (or always true if priming is disabled).

◆ levelPercent()

float levelPercent ( )
inlineoverridevirtual

Current actual fill level of the backing buffer in percent (0-100).

Reimplemented from BaseBuffer< uint8_t >.

◆ levelPercentSmoothed()

float levelPercentSmoothed ( )
inline

Get the Kalman-smoothed fill level from the last recalculate() call, in percent.

Returns
float Last calculated (smoothed) fill level (0-100)

◆ operator=()

◆ peek()

bool peek ( uint8_t result)
inlineoverridevirtual

Peeks the next resampled byte without consuming it. Returns false while priming (see setStartFillPercent()).

Implements BaseBuffer< uint8_t >.

◆ read()

bool read ( uint8_t result)
inlineoverridevirtual

Reads a single resampled byte.

Implements BaseBuffer< uint8_t >.

◆ readArray()

int readArray ( uint8_t  data[],
int  len 
)
inlineoverridevirtual

Reads multiple resampled bytes. Returns 0 while priming (see setStartFillPercent()) even if the backing buffer already has data.

Reimplemented from BaseBuffer< uint8_t >.

◆ recalculate()

float recalculate ( )
inline

Recalculate the resampling step size based on buffer fill level. Called automatically by writeArray().

Returns
float The new step size

◆ reset()

void reset ( )
inlineoverridevirtual

Clears the backing buffer and reinitializes the resampling pipeline (interpolation history, queued/lookahead bytes, PID and Kalman filter state, step size) so nothing from before the reset leaks into the next session.

Implements BaseBuffer< uint8_t >.

◆ resetDriftCounters()

void resetDriftCounters ( )
inlineprotected

Resets the cumulative counters used by averageStepSize().

◆ resize()

virtual bool resize ( size_t  bytes)
inlinevirtualinherited

Resizes the buffer if supported: returns false if not supported.

◆ setAudioInfo()

void setAudioInfo ( AudioInfo  info)
inlineoverridevirtual

Defines the audio format: needed to correctly interpret and resample the sample frames stored in the backing buffer.

Implements AudioInfoSupport.

◆ setBuffer()

void setBuffer ( BaseBuffer< uint8_t > &  buffer)
inline

Set the raw (unresampled) backing buffer.

Parameters
bufferReference to the raw (unresampled) backing buffer

◆ setKalmanParameters()

void setKalmanParameters ( float  process_noise,
float  measurement_noise 
)
inline

Set the Kalman filter parameters.

Parameters
process_noiseProcess noise covariance (Q)
measurement_noiseMeasurement noise covariance (R)

◆ setMinBytesForDriftEstimate()

void setMinBytesForDriftEstimate ( uint32_t  bytes)
inline

Minimum cumulative output bytes that must have been read before averageStepSize() is trusted; below this it returns 1.0. Avoids acting on a ratio computed from a handful of bytes right after begin(). Default is 4096.

◆ setPIDParameters()

void setPIDParameters ( float  p_value,
float  i_value,
float  d_value 
)
inline

Set the PID controller parameters.

Parameters
p_valueProportional gain
i_valueIntegral gain
d_valueDerivative gain

◆ setStartFillPercent()

void setStartFillPercent ( float  percent)
inline

Defines the fill level (in percent of the backing buffer's capacity) that must be reached once, right after begin(), before any data is provided to the reader. This avoids starting playback only to immediately underrun again. Defaults to 50%. Priming only happens once at the start of a session (i.e. after begin()/reset()); it does not re-arm on a later underflow.

Parameters
percentFill level in percent (e.g. 50.0 for 50%); 0 disables priming.

◆ setStepRangePercent()

void setStepRangePercent ( float  rangePercent)
inline

Set the allowed resampling range as a percent.

Parameters
rangePercentAllowed range in percent (e.g., 5.0 for ± 5%)

◆ size()

size_t size ( )
inlineoverridevirtual

Capacity (in bytes) of the backing buffer.

Implements BaseBuffer< uint8_t >.

◆ stepSize()

float stepSize ( )
inline

Get the resampling step size calculated by the last recalculate() call, without triggering a new calculation.

◆ totalBytesRead()

uint64_t totalBytesRead ( )
inline

Cumulative resampled bytes delivered via readArray() since the drift counters were last reset (begin(), reset(), or priming completion).

◆ totalBytesWritten()

uint64_t totalBytesWritten ( )
inline

Cumulative raw bytes written to the backing buffer since the drift counters were last reset (begin(), reset(), or priming completion).

◆ write()

bool write ( uint8_t  data)
inlineoverridevirtual

Writes a single byte to the backing buffer.

Implements BaseBuffer< uint8_t >.

◆ writeArray()

int writeArray ( const uint8_t  data[],
int  len 
)
inlineoverridevirtual

Writes multiple bytes to the backing buffer and triggers a step size recalculation.

Reimplemented from BaseBuffer< uint8_t >.

◆ writeArrayOverwrite()

virtual int writeArrayOverwrite ( const uint8_t  data[],
int  len 
)
inlinevirtualinherited

Fills the buffer data and overwrites the oldest data if the buffer is full.

Member Data Documentation

◆ audio_info

AudioInfo audio_info
protected

◆ d

float d = 0.0001
protected

◆ has_peek

bool has_peek = false
protected

◆ i

float i = 0.00005
protected

◆ is_active

bool is_active = false
protected

◆ is_primed

bool is_primed = false
protected

◆ kalman_filter

KalmanFilter kalman_filter {0.01f, 0.1f}
protected

◆ last_time_ms

uint32_t last_time_ms = 0
protected

◆ level_percent_smoothed

float level_percent_smoothed = 0.0
protected

◆ min_bytes_for_drift_estimate

uint32_t min_bytes_for_drift_estimate = 4096
protected

◆ p

float p = 0.005
protected

◆ p_buffer

BaseBuffer<uint8_t>* p_buffer = nullptr
protected

◆ peek_byte

uint8_t peek_byte = 0
protected

◆ pid

PIDController pid
protected

◆ queue_stream

QueueStream<uint8_t> queue_stream
protected

◆ recalc_count

uint32_t recalc_count = 0
protected

◆ resample_range

float resample_range = 0
protected

◆ resample_stream

ResampleStream resample_stream
protected

◆ start_fill_percent

float start_fill_percent = 50.0f
protected

◆ step_size

std::atomic<float> step_size {1.0f}
protected

◆ total_bytes_read

uint64_t total_bytes_read = 0
protected

◆ total_bytes_written

uint64_t total_bytes_written = 0
protected

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