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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not recommended to capture everything by ref [&]. Just use [frameStart, frameEnd].

int64_t len = static_cast<int64_t>(frameEnd - frameStart);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int64_t len = static_cast<int64_t>(frameEnd - frameStart);
auto len = static_cast<int64_t>(frameEnd - frameStart);

int64_t rel = static_cast<int64_t>(idx) - static_cast<int64_t>(frameStart);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cast is not necessary

Suggested change
int64_t rel = static_cast<int64_t>(idx) - static_cast<int64_t>(frameStart);
int64_t rel = idx - static_cast<int64_t>(frameStart);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implicates forward only playback. Please handle reverse playback case too (when playbackRate < 0).

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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use curly braces for readability.

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.
Comment on lines +31 to +34
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use doxygen style for docstrings. We follow /// convention.

[[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