Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -220,18 +220,32 @@ void AudioBufferSourceNode::processWithInterpolation(

while (framesLeft > 0) {
auto readIndex = static_cast<size_t>(vReadIndex_);
size_t nextReadIndex = readIndex + 1;
auto factor = static_cast<float>(vReadIndex_ - static_cast<double>(readIndex));

if (nextReadIndex >= frameEnd) {
nextReadIndex = loop_ ? frameStart : readIndex;
}

for (size_t i = 0; i < processingBuffer->getNumberOfChannels(); i++) {
auto destination = processingBuffer->getChannel(i)->span();
const auto source = buffer_->getChannel(i)->span();

destination[writeIndex] = dsp::linearInterpolate(source, readIndex, nextReadIndex, factor);
if (loop_) {
// Use Hermite 4-point interpolation with proper loop wrapping.
// Linear interpolation creates audible clicks at loop boundaries
// because it only ensures C0 (value) continuity. Hermite ensures
// C1 (slope) continuity, eliminating the click.
auto wrap = [&](int64_t idx) -> size_t {
int64_t len = static_cast<int64_t>(frameEnd - frameStart);
int64_t rel = static_cast<int64_t>(idx) - static_cast<int64_t>(frameStart);
return static_cast<size_t>(frameStart + ((rel % len) + len) % len);
};
size_t idx0 = wrap(static_cast<int64_t>(readIndex) - 1);
size_t idx1 = wrap(static_cast<int64_t>(readIndex));
size_t idx2 = wrap(static_cast<int64_t>(readIndex) + 1);
size_t idx3 = wrap(static_cast<int64_t>(readIndex) + 2);
destination[writeIndex] = dsp::hermiteInterpolate(source, idx0, idx1, idx2, idx3, factor);
} else {
size_t nextReadIndex = readIndex + 1;
if (nextReadIndex >= frameEnd) nextReadIndex = readIndex;
destination[writeIndex] = dsp::linearInterpolate(source, readIndex, nextReadIndex, factor);
}
}

writeIndex += 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@ namespace audioapi::dsp {
return std::lerp(source[firstIndex], source[secondIndex], factor);
}

// Hermite 4-point interpolation for smooth looping.
// Unlike linear interpolation, Hermite matches both value AND slope at
// the interpolation point, eliminating audible clicks at loop boundaries
// when playbackRate != 1.0.
[[nodiscard]] inline float hermiteInterpolate(
std::span<const float> source,
size_t idx0,
size_t idx1,
size_t idx2,
size_t idx3,
float t) {
float y0 = source[idx0], y1 = source[idx1], y2 = source[idx2], y3 = source[idx3];
float c0 = y1;
float c1 = 0.5f * (y2 - y0);
float c2 = y0 - 2.5f * y1 + 2.0f * y2 - 0.5f * y3;
float c3 = 0.5f * (y3 - y0) + 1.5f * (y1 - y2);
return ((c3 * t + c2) * t + c1) * t + c0;
}

[[nodiscard]] inline float linearToDecibels(float value) {
constexpr float kDecibelsLinearFactor = 20.0f;
return kDecibelsLinearFactor * log10f(value);
Expand Down