Skip to content

fix: use Hermite interpolation at loop boundaries to eliminate clicks#1055

Open
mvanhoolwerff wants to merge 1 commit intosoftware-mansion:mainfrom
mvanhoolwerff:fix/hermite-loop-interpolation
Open

fix: use Hermite interpolation at loop boundaries to eliminate clicks#1055
mvanhoolwerff wants to merge 1 commit intosoftware-mansion:mainfrom
mvanhoolwerff:fix/hermite-loop-interpolation

Conversation

@mvanhoolwerff
Copy link
Copy Markdown

Summary

When playbackRate != 1.0, AudioBufferSourceNode uses the interpolation path for sample playback. At the loop boundary, linear interpolation between the last and first frame creates an audible click because it only ensures value (C0) continuity — the slope can change abruptly.

This replaces linear interpolation with 4-point Hermite spline interpolation for looping sources. Hermite ensures both value AND slope (C1) continuity at the loop point, eliminating the click artifact.

Changes

  • AudioUtils.hpp: Added hermiteInterpolate() function using cubic Hermite spline with 4 sample points
  • AudioBufferSourceNode.cpp: processWithInterpolation() now uses Hermite interpolation with proper loop-wrapping for looping sources. Non-looping sources continue to use linear interpolation (no behavior change).

Reproducer

  1. Create an AudioBufferSourceNode with loop = true
  2. Set playbackRate to anything other than 1.0 (e.g., 0.8 or 1.2)
  3. Listen for a periodic click at each loop cycle

With this fix, the loop is smooth at any playback rate.

Why Hermite?

At the loop boundary, the interpolation reads source[lastFrame] and source[firstFrame]. Even if these values are identical, the slope (derivative) at the boundary can jump — the waveform might be going down at the end and up at the start. Linear interpolation produces a sharp "kink" that's audible as a click.

Hermite uses 4 surrounding samples to estimate the slope, producing a smooth curve through the boundary. The wrap function ensures all 4 indices are valid across the loop point.

Test plan

  • Verify no audible clicks with loop=true and playbackRate=0.8
  • Verify no audible clicks with loop=true and playbackRate=1.2
  • Verify playbackRate=1.0 still uses the clean processWithoutInterpolation path
  • Verify non-looping playback is unchanged

🤖 Generated with Claude Code

When playbackRate != 1.0, AudioBufferSourceNode uses the interpolation
path for sample playback. At the loop boundary, linear interpolation
between the last and first frame creates an audible click because it
only ensures value (C0) continuity — the slope can change abruptly.

This replaces linear interpolation with 4-point Hermite spline
interpolation for looping sources. Hermite ensures both value AND slope
(C1) continuity at the loop point, eliminating the click artifact.

The fix wraps sample indices correctly through the loop boundary so
all 4 points are always valid. Non-looping sources continue to use
linear interpolation (no behavior change).

Reproducer: play any AudioBufferSourceNode with loop=true and
playbackRate=0.8 or 1.2 — audible periodic click at each loop cycle.
With this fix, the loop is smooth.
@mdydek mdydek added the fix label May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants