|
arduino-audio-tools
|
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>
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_t * | address () 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. | |
| AdaptiveResamplingBuffer & | operator= (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_t > | queue_stream |
| uint32_t | recalc_count = 0 |
| float | resample_range = 0 |
| ResampleStream | resample_stream |
| float | start_fill_percent = 50.0f |
| std::atomic< float > | step_size {1.0f} |
| uint64_t | total_bytes_read = 0 |
| uint64_t | total_bytes_written = 0 |
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.
|
default |
Construct a new AdaptiveResamplingBuffer object You need to call setBuffer() and setStepRangePercent() before begin()
|
inline |
Construct a new AdaptiveResamplingBuffer object.
| buffer | Reference to the raw (unresampled) backing buffer |
| stepRangePercent | Allowed resampling range in percent (default: 5.0) |
|
delete |
|
inlineoverridevirtual |
Not supported: resampled data has no contiguous physical representation.
Implements BaseBuffer< uint8_t >.
|
inlineoverridevirtual |
provides the actual input AudioInfo
Implements AudioInfoSupport.
provides the actual output AudioInfo: this is usually the same as audioInfo() unless we use a transforming stream
Reimplemented in MP3EncoderShine, PureDataStream, PWMAudioOutput< PWMDriverT >, ChannelFormatConverterStreamT< T >, ChannelFormatConverterStream, NumberFormatConverterStreamT< TFrom, TTo >, NumberFormatConverterStream, FormatConverterStream, Pipeline, ResampleStream, and ResampleStreamT< TInterpolator >.
|
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 >.
|
inlineoverridevirtual |
Number of raw bytes that can still be written to the backing buffer.
Implements BaseBuffer< uint8_t >.
|
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()).
|
inline |
Initialize the buffer and internal components.
Provides the number of entries that are available to write: -1 does not apply.
Provides the number of entries that are available to read: -1 does not apply.
|
inlineprotected |
Returns true once priming is complete; latches permanently once the fill threshold has been reached (does not re-arm on later underflow).
|
inlineinherited |
same as reset
|
inline |
End the buffer and release resources.
Submit any partially-filled write buffer so the reader can access it. Only meaningful for NBuffer-style block pools; no-op for ring buffers.
|
inlineinherited |
|
inline |
True once the buffer has reached setStartFillPercent() (or always true if priming is disabled).
|
inlineoverridevirtual |
Current actual fill level of the backing buffer in percent (0-100).
Reimplemented from BaseBuffer< uint8_t >.
|
inline |
Get the Kalman-smoothed fill level from the last recalculate() call, in percent.
|
delete |
Peeks the next resampled byte without consuming it. Returns false while priming (see setStartFillPercent()).
Implements BaseBuffer< uint8_t >.
Reads a single resampled byte.
Implements BaseBuffer< uint8_t >.
Reads multiple resampled bytes. Returns 0 while priming (see setStartFillPercent()) even if the backing buffer already has data.
Reimplemented from BaseBuffer< uint8_t >.
|
inline |
Recalculate the resampling step size based on buffer fill level. Called automatically by writeArray().
|
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 >.
|
inlineprotected |
Resets the cumulative counters used by averageStepSize().
Resizes the buffer if supported: returns false if not supported.
Defines the audio format: needed to correctly interpret and resample the sample frames stored in the backing buffer.
Implements AudioInfoSupport.
|
inline |
Set the raw (unresampled) backing buffer.
| buffer | Reference to the raw (unresampled) backing buffer |
Set the Kalman filter parameters.
| process_noise | Process noise covariance (Q) |
| measurement_noise | Measurement noise covariance (R) |
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.
Set the PID controller parameters.
| p_value | Proportional gain |
| i_value | Integral gain |
| d_value | Derivative gain |
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.
| percent | Fill level in percent (e.g. 50.0 for 50%); 0 disables priming. |
Set the allowed resampling range as a percent.
| rangePercent | Allowed range in percent (e.g., 5.0 for ± 5%) |
|
inlineoverridevirtual |
Capacity (in bytes) of the backing buffer.
Implements BaseBuffer< uint8_t >.
|
inline |
Get the resampling step size calculated by the last recalculate() call, without triggering a new calculation.
|
inline |
Cumulative resampled bytes delivered via readArray() since the drift counters were last reset (begin(), reset(), or priming completion).
|
inline |
Writes a single byte to the backing buffer.
Implements BaseBuffer< uint8_t >.
Writes multiple bytes to the backing buffer and triggers a step size recalculation.
Reimplemented from BaseBuffer< uint8_t >.
Fills the buffer data and overwrites the oldest data if the buffer is full.
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |