Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RingDelayBuffer: ring buffer for delay handling #4852

Merged
merged 43 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
88a160b
RingDelayBuffer: add a new ring buffer
davidchocholaty Jul 11, 2022
ccb9650
RingDelayBufferTest: add tests for a ring buffer
davidchocholaty Jul 11, 2022
207d26f
RingDelayBuffer: add original license
davidchocholaty Jul 11, 2022
785f636
RingDelayBuffer: clearing of m_jumpLeftAroundMask
davidchocholaty Jul 11, 2022
a155157
RingDelayBuffer: allow equality for the right jump
davidchocholaty Jul 11, 2022
eeb6e6d
RingDelayBuffer: solve jump left maximum size
davidchocholaty Jul 11, 2022
cb2d57b
RingDelayBufferTest: add benchmarks
davidchocholaty Jul 11, 2022
1dd8830
RingDelayBuffer: remove unnecessary destructor
davidchocholaty Jul 15, 2022
8f26438
RingDelayBuffer: use the size of SampleBuffer
davidchocholaty Jul 15, 2022
36d92d2
RingDelayBuffer: improve const correctness
davidchocholaty Jul 15, 2022
248f38e
RingDelayBuffer: add single-threaded only comment
davidchocholaty Jul 15, 2022
4e6a1ef
RingDelayBufferTest: use of unique_ptr
davidchocholaty Jul 15, 2022
d6565e7
RingDelayBuffer: replace zeros memset with fill
davidchocholaty Jul 29, 2022
0bfd168
RingDelayBuffer: replace with SampleUtil::copy
davidchocholaty Jul 29, 2022
9ef5752
RingDelayBuffer: fix buffer clear with zeros
davidchocholaty Jul 29, 2022
5772334
RingDelayBuffer: remove binary operations
davidchocholaty Jul 29, 2022
de60af2
RingDelayBuffer: apply fading in
davidchocholaty Aug 8, 2022
77181a8
RingDelayBuffer: update m_fullFlag handling
davidchocholaty Aug 15, 2022
1df0fbb
RingDelayBuffer: fix typo in phrase THREAD-SAFE
davidchocholaty Aug 15, 2022
7c2610e
RingDelayBuffer: remove const from the parameter
davidchocholaty Aug 15, 2022
95182ea
RingDelayBuffer: skip for invalid number of items
davidchocholaty Aug 17, 2022
90f9772
RingDelayBuffer: const number of items
davidchocholaty Aug 17, 2022
dd4980a
RingDelayBuffer: read with provided delay
davidchocholaty Aug 17, 2022
adf2454
RingDelayBuffer: deduplicate the buffers copying
davidchocholaty Aug 21, 2022
0a61a7b
RingDelayBuffer: remove unused function definition
davidchocholaty Aug 21, 2022
c6a6c3a
RingDelayBuffer:: update commentary part
davidchocholaty Aug 21, 2022
e3638b4
RingDelayBuffer: change to VERIFY_OR_DEBUG_ASSERT
davidchocholaty Aug 22, 2022
b95d6be
RingDelayBuffer: rename copy function to copyRing
davidchocholaty Aug 22, 2022
b73a2b5
RingDelayBuffer: move copyRing into a namespace
davidchocholaty Aug 22, 2022
2b2a7a3
RingDelayBuffer: handle invalid copy alignment
davidchocholaty Aug 22, 2022
0f4729f
RingDelayBuffer: add default value assignment
davidchocholaty Aug 23, 2022
c683b41
RingDelayBuffer: avoid uninitialized memory
davidchocholaty Aug 26, 2022
0828535
Merge remote-tracking branch 'upstream/main' into ring_delay_buffer
davidchocholaty Aug 27, 2022
6e212af
RingDelayBuffer: replace slices by std::span
davidchocholaty Aug 27, 2022
ac54694
RingDelayBuffer: rename without pointer prefix
davidchocholaty Aug 27, 2022
6ee6603
RingDelayBufferTest: introduce std::span
davidchocholaty Aug 27, 2022
bc6240a
RingDelayBuffer: rename m_firstInputBuffer
davidchocholaty Aug 28, 2022
74b52ea
RingDelayBuffer: add documentation comments
davidchocholaty Aug 28, 2022
9addec2
Merge remote-tracking branch 'upstream/main' into ring_delay_buffer
davidchocholaty Aug 29, 2022
8076e29
RingDelayBuffer: size returned value as constexpr
davidchocholaty Aug 29, 2022
d886d09
RingDelayBuffer: use spans in the API
davidchocholaty Aug 29, 2022
6c22c40
RingDelayBuffer: remove the unnecessary licence
davidchocholaty Aug 29, 2022
b686546
RingDelayBuffer: use SampleBuffer's span
davidchocholaty Aug 29, 2022
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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/util/performancetimer.cpp
src/util/rangelist.cpp
src/util/readaheadsamplebuffer.cpp
src/util/ringdelaybuffer.cpp
src/util/rotary.cpp
src/util/runtimeloggingcategory.cpp
src/util/sample.cpp
Expand Down Expand Up @@ -1701,6 +1702,7 @@ add_executable(mixxx-test
src/test/replaygaintest.cpp
src/test/rescalertest.cpp
src/test/rgbcolor_test.cpp
src/test/ringdelaybuffer_test.cpp
src/test/samplebuffertest.cpp
src/test/sampleutiltest.cpp
src/test/schemamanager_test.cpp
Expand Down
176 changes: 176 additions & 0 deletions src/test/ringdelaybuffer_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Tests for ringdelaybuffer.h

#include "util/ringdelaybuffer.h"

#include <benchmark/benchmark.h>
#include <gtest/gtest.h>

#include <QTest>
#include <span>

#include "test/mixxxtest.h"
#include "util/sample.h"
#include "util/samplebuffer.h"
#include "util/span.h"
#include "util/types.h"

namespace {

class RingDelayBufferTest : public MixxxTest {
protected:
void SetUp() override {
m_pRingDelayBuffer = std::make_unique<RingDelayBuffer>(m_ringDelayBufferSize);
}

void TearDown() override {
}

void AssertIdenticalBufferEquals(const std::span<CSAMPLE> buffer,
const std::span<const CSAMPLE> referenceBuffer) {
ASSERT_EQ(buffer.size(), referenceBuffer.size());

for (std::span<CSAMPLE>::size_type i = 0; i < buffer.size(); ++i) {
EXPECT_FLOAT_EQ(buffer[i], referenceBuffer[i]);
}
}

std::unique_ptr<RingDelayBuffer> m_pRingDelayBuffer;
const SINT m_ringDelayBufferSize = 8;
};

TEST_F(RingDelayBufferTest, ReadWriteNoDelayTest) {
const SINT numSamplesHalf = 2;
const SINT numSamples = 4;
const CSAMPLE inputBuffer[] = {-100.0, 100.0, -99.0, 99.0};
const CSAMPLE inputBufferHalf[] = {-98.0, 98.0};
const CSAMPLE firstExpectedResult[] = {-50.0, 50.0, -99.0, 99.0};
const CSAMPLE secondExpectedResult[] = {-99.0, 99.0, -98.0, 98.0};
const CSAMPLE thirdExpectedResult[] = {-100.0, 100.0, -99.0, 99.0};

mixxx::SampleBuffer output(numSamples);

EXPECT_EQ(m_pRingDelayBuffer->write(
mixxx::spanutil::spanFromPtrLen(inputBuffer, numSamples)),
numSamples);
EXPECT_EQ(m_pRingDelayBuffer->read(
output.span(), 0),
numSamples);

AssertIdenticalBufferEquals(output.span(),
mixxx::spanutil::spanFromPtrLen(firstExpectedResult, numSamples));

EXPECT_EQ(m_pRingDelayBuffer->write(
mixxx::spanutil::spanFromPtrLen(inputBufferHalf, numSamplesHalf)),
numSamplesHalf);
EXPECT_EQ(m_pRingDelayBuffer->read(
output.span(), 0),
numSamples);

AssertIdenticalBufferEquals(output.span(),
mixxx::spanutil::spanFromPtrLen(secondExpectedResult, numSamples));

// Write and read over one ring.
EXPECT_EQ(m_pRingDelayBuffer->write(
mixxx::spanutil::spanFromPtrLen(inputBuffer, numSamples)),
numSamples);
EXPECT_EQ(m_pRingDelayBuffer->read(
output.span(), 0),
numSamples);

AssertIdenticalBufferEquals(output.span(),
mixxx::spanutil::spanFromPtrLen(thirdExpectedResult, numSamples));
}

TEST_F(RingDelayBufferTest, ReadWriteDelayTest) {
const SINT numSamples = 4;
const SINT firstDelaySize = 2;
const SINT secondDelaySize = 4;
const CSAMPLE inputBuffer[] = {-100.0, 100.0, -99.0, 99.0};
const CSAMPLE firstExpectedResult[] = {-50.0, 50.0, -99.0, 99.0};
const CSAMPLE secondExpectedResult[] = {0.0, 0.0, -50.0, 50.0};
const CSAMPLE thirdExpectedResult[] = {-50.0, 50.0, -99.0, 99.0};

mixxx::SampleBuffer output(numSamples);

// Read without delay.
EXPECT_EQ(m_pRingDelayBuffer->write(
mixxx::spanutil::spanFromPtrLen(inputBuffer, numSamples)),
numSamples);
EXPECT_EQ(m_pRingDelayBuffer->read(
output.span(), 0),
numSamples);

AssertIdenticalBufferEquals(output.span(),
mixxx::spanutil::spanFromPtrLen(firstExpectedResult, numSamples));

// Read with delay.
EXPECT_EQ(m_pRingDelayBuffer->read(
output.span(), firstDelaySize),
numSamples);

AssertIdenticalBufferEquals(output.span(),
mixxx::spanutil::spanFromPtrLen(secondExpectedResult, numSamples));

// Fill the second half of the delay buffer with the first input data.
EXPECT_EQ(m_pRingDelayBuffer->write(
mixxx::spanutil::spanFromPtrLen(inputBuffer, numSamples)),
numSamples);

// Read with delay (not circle around).
EXPECT_EQ(m_pRingDelayBuffer->read(
output.span(), secondDelaySize),
numSamples);

AssertIdenticalBufferEquals(output.span(),
mixxx::spanutil::spanFromPtrLen(thirdExpectedResult, numSamples));
}

static void BM_WriteReadWholeBufferNoDelay(benchmark::State& state) {
const SINT ringDelayBufferSize = static_cast<SINT>(state.range(0));
const SINT numSamples = ringDelayBufferSize / 2;

RingDelayBuffer m_ringDelayBuffer(ringDelayBufferSize);

mixxx::SampleBuffer input(numSamples);
mixxx::SampleBuffer output(numSamples);

input.fill(0.0f);

for (auto _ : state) {
state.PauseTiming();
m_ringDelayBuffer.clear();
state.ResumeTiming();

m_ringDelayBuffer.write(input.span());
m_ringDelayBuffer.read(output.span(), 0);
m_ringDelayBuffer.write(input.span());
m_ringDelayBuffer.read(output.span(), 0);
}
}
BENCHMARK(BM_WriteReadWholeBufferNoDelay)->Range(64, 4 << 10);

static void BM_WriteReadWholeBufferDelay(benchmark::State& state) {
const SINT ringDelayBufferSize = static_cast<SINT>(state.range(0));
const SINT numSamples = ringDelayBufferSize / 2;
const SINT delaySize = numSamples;

RingDelayBuffer m_ringDelayBuffer(ringDelayBufferSize);

mixxx::SampleBuffer input(numSamples);
mixxx::SampleBuffer output(numSamples);

input.fill(0.0f);

for (auto _ : state) {
state.PauseTiming();
m_ringDelayBuffer.clear();
state.ResumeTiming();

m_ringDelayBuffer.write(input.span());
m_ringDelayBuffer.read(output.span(), delaySize);
m_ringDelayBuffer.write(input.span());
m_ringDelayBuffer.read(output.span(), delaySize);
}
}
BENCHMARK(BM_WriteReadWholeBufferDelay)->Range(64, 4 << 10);
} // namespace
147 changes: 147 additions & 0 deletions src/util/ringdelaybuffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include "ringdelaybuffer.h"

#include <span>

#include "util/math.h"
#include "util/sample.h"

namespace {
// The helper function for copying data by using one ring buffer
// and one contiguous buffer or using two contiguous buffers.
// The situation working with two ring buffers is not allowed
// due to this situation is not possible with delay handling
// and for this situation, there is a need for three copies.
// If the copying will come across the upper bound of the ring buffer,
// the next items are copied from/to the start of the ring buffer.
SINT copyRing(const std::span<const CSAMPLE> sourceBuffer,
SINT sourcePos,
const std::span<CSAMPLE> destBuffer,
SINT destPos,
const SINT numItems) {
const unsigned int newSourcePos = sourcePos + numItems;
const unsigned int newDestPos = destPos + numItems;

SINT sourceRemainingItems = sourceBuffer.size() - sourcePos;
SINT destRemainingItems = destBuffer.size() - destPos;

// Do not allow the new positions will both cross the upper bound
// of their buffers. Based on that, the three copies will be needed,
// but for the delay handling use case of the ring buffer,
// this situation is not possible and is not allowed
// in this helper function.
VERIFY_OR_DEBUG_ASSERT(newSourcePos <= sourceBuffer.size() ||
newDestPos <= destBuffer.size()) {
return 0;
}

// Check to see if the copy is not contiguous.
if (newSourcePos > sourceBuffer.size() ||
newDestPos > destBuffer.size()) {
// Copy is not contiguous.
SINT firstDataBlockSize = math_min(sourceRemainingItems, destRemainingItems);

// Copy the first data part until the end of the destination
// or the source buffer.
SampleUtil::copy(destBuffer.last(destRemainingItems).data(),
sourceBuffer.last(sourceRemainingItems).data(),
firstDataBlockSize);

// Calculate new source and destination position.
sourcePos = (sourcePos + firstDataBlockSize) % sourceBuffer.size();
destPos = (destPos + firstDataBlockSize) % destBuffer.size();

// Based on the new source and destination positions recalculate
// the remaining items from the new positions.
sourceRemainingItems = sourceBuffer.size() - sourcePos;
destRemainingItems = destBuffer.size() - destPos;

// The second data part is the start of the source
// or destination buffer.
SampleUtil::copy(destBuffer.last(destRemainingItems).data(),
sourceBuffer.last(sourceRemainingItems).data(),
numItems - firstDataBlockSize);
} else {
// Copy is contiguous.
SampleUtil::copy(destBuffer.last(destRemainingItems).data(),
sourceBuffer.last(sourceRemainingItems).data(),
numItems);
}

return numItems;
}

} // anonymous namespace

RingDelayBuffer::RingDelayBuffer(SINT bufferSize)
: m_firstInputChunk(true),
m_writePos(0),
m_buffer(bufferSize) {
// Set the ring buffer items to 0.
m_buffer.fill(0);
}

SINT RingDelayBuffer::read(std::span<CSAMPLE> destinationBuffer, const SINT delayItems) {
const SINT itemsToRead = destinationBuffer.size();
const SINT shift = itemsToRead + delayItems;

// The reading position shift against the write position
// has to be smaller or equal to the ring buffer size.
VERIFY_OR_DEBUG_ASSERT(shift <= m_buffer.size()) {
return 0;
}
Swiftb0y marked this conversation as resolved.
Show resolved Hide resolved

Swiftb0y marked this conversation as resolved.
Show resolved Hide resolved
SINT readPos = m_writePos - shift;

// The reading position crossed the left bound of the ring buffer.
// Add the size of the ring buffer to move the position around and keep it
// in the valid index range.
if (readPos < 0) {
readPos = readPos + m_buffer.size();
}

return copyRing(mixxx::spanutil::spanFromPtrLen(m_buffer.data(), m_buffer.size()),
readPos,
destinationBuffer,
0,
itemsToRead);
}

SINT RingDelayBuffer::write(std::span<const CSAMPLE> sourceBuffer) {
const SINT itemsToWrite = sourceBuffer.size();

VERIFY_OR_DEBUG_ASSERT(itemsToWrite <= m_buffer.size()) {
return 0;
}

const SINT numItems = [&]() {
if (m_firstInputChunk) {
// If the first input chunk is written, the first sample is on the index 0.
// Based on the checking of an available number of samples, the situation,
// that the writing will be non-contiguous cannot occur.
SampleUtil::copyWithRampingGain(m_buffer.data(),
sourceBuffer.data(),
0.0f,
1.0f,
itemsToWrite);
m_firstInputChunk = false;
return itemsToWrite;
} else {
return copyRing(sourceBuffer,
0,
mixxx::spanutil::spanFromPtrLen(m_buffer.data(), m_buffer.size()),
m_writePos,
itemsToWrite);
}
}();

// Calculate the new write position. If the new write position
// is after the ring buffer end, move it around from the start
// of the ring buffer.
m_writePos = (m_writePos + numItems);

if (m_writePos >= m_buffer.size()) {
m_writePos = m_writePos - m_buffer.size();
}

return numItems;
}
Loading