-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4852 from davidchocholaty/ring_delay_buffer
RingDelayBuffer: ring buffer for delay handling
- Loading branch information
Showing
4 changed files
with
408 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
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; | ||
} |
Oops, something went wrong.