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 30 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 @@ -937,6 +937,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 @@ -1682,6 +1683,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
151 changes: 151 additions & 0 deletions src/test/ringdelaybuffer_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Tests for ringdelaybuffer.h

#include "util/ringdelaybuffer.h"

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

#include <QTest>

#include "test/mixxxtest.h"
#include "util/sample.h"
#include "util/samplebuffer.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 CSAMPLE* pBuffer,
int iBufferLen,
const CSAMPLE* pReferenceBuffer,
int iReferenceBufferLength) {
EXPECT_EQ(iBufferLen, iReferenceBufferLength);

for (int i = 0; i < iBufferLen; ++i) {
EXPECT_FLOAT_EQ(pBuffer[i], pReferenceBuffer[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(inputBuffer, numSamples), numSamples);
EXPECT_EQ(m_pRingDelayBuffer->read(output.data(), numSamples, 0), numSamples);

AssertIdenticalBufferEquals(output.data(),
numSamples,
firstExpectedResult,
numSamples);

EXPECT_EQ(m_pRingDelayBuffer->write(inputBufferHalf, numSamplesHalf), numSamplesHalf);
EXPECT_EQ(m_pRingDelayBuffer->read(output.data(), numSamples, 0), numSamples);

AssertIdenticalBufferEquals(output.data(), numSamples, secondExpectedResult, numSamples);

// Write and read over one ring.
EXPECT_EQ(m_pRingDelayBuffer->write(inputBuffer, numSamples), numSamples);
EXPECT_EQ(m_pRingDelayBuffer->read(output.data(), numSamples, 0), numSamples);

AssertIdenticalBufferEquals(output.data(), numSamples, 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(inputBuffer, numSamples), numSamples);
EXPECT_EQ(m_pRingDelayBuffer->read(output.data(), numSamples, 0), numSamples);

AssertIdenticalBufferEquals(output.data(), numSamples, firstExpectedResult, numSamples);

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

AssertIdenticalBufferEquals(output.data(), numSamples, secondExpectedResult, numSamples);

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

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

AssertIdenticalBufferEquals(output.data(), numSamples, 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);

SampleUtil::fill(input.data(), 0.0f, numSamples);

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

m_ringDelayBuffer.write(input.data(), numSamples);
m_ringDelayBuffer.read(output.data(), numSamples, 0);
m_ringDelayBuffer.write(input.data(), numSamples);
m_ringDelayBuffer.read(output.data(), numSamples, 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);

SampleUtil::fill(input.data(), 0.0f, numSamples);

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

m_ringDelayBuffer.write(input.data(), numSamples);
m_ringDelayBuffer.read(output.data(), numSamples, delaySize);
m_ringDelayBuffer.write(input.data(), numSamples);
m_ringDelayBuffer.read(output.data(), numSamples, delaySize);
}
}
BENCHMARK(BM_WriteReadWholeBufferDelay)->Range(64, 4 << 10);
} // namespace
158 changes: 158 additions & 0 deletions src/util/ringdelaybuffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* $Id$
* Portable Audio I/O Library
* Ring Buffer utility.
*
* Author: Phil Burk, http://www.softsynth.com
* modified for SMP safety on OS X by Bjorn Roche.
* also allowed for const where possible.
* modified for multiple-byte-sized data elements by Sven Fischer
*
* This program is distributed with the PortAudio Portable Audio Library.
* For more information see: http://www.portaudio.com
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/

#include "ringdelaybuffer.h"

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

using ReadableSlice = mixxx::SampleBuffer::ReadableSlice;
using WritableSlice = mixxx::SampleBuffer::WritableSlice;

namespace {
SINT copyRing(const ReadableSlice pSourceBuffer,
SINT sourcePos,
const WritableSlice pDestBuffer,
SINT destPos,
const SINT numItems) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm planning to deprecate mixxx::SampleBuffer::*Slice. Why not use std::span instead?

Copy link
Contributor Author

@davidchocholaty davidchocholaty Aug 23, 2022

Choose a reason for hiding this comment

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

Of course, std::span can be used. I thought, that I should rather work with span through SampleBuffer and forgot, that I can call the mixxx::spanutil::spanFromPtrLen() directly.

Copy link
Member

Choose a reason for hiding this comment

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

Right, you just use spanFromPtrLen as an adapter so to speak and then try to use std::span as much as possible in any APIs that take the usual (pointer, size) pair.

const SINT newSourcePos = sourcePos + numItems;
const SINT newDestPos = destPos + numItems;

VERIFY_OR_DEBUG_ASSERT(newSourcePos <= pSourceBuffer.length() ||
newDestPos <= pDestBuffer.length()) {
return 0;
}
Swiftb0y marked this conversation as resolved.
Show resolved Hide resolved

// Check to see if the copy is not contiguous.
if (newSourcePos > pSourceBuffer.length() ||
newDestPos > pDestBuffer.length()) {
// Copy is not contiguous.
SINT firstDataBlockSize = math_min(pSourceBuffer.length() - sourcePos,
pDestBuffer.length() - destPos);

SampleUtil::copy(pDestBuffer.data(destPos),
pSourceBuffer.data(sourcePos),
firstDataBlockSize);

sourcePos = (sourcePos + firstDataBlockSize) % pSourceBuffer.length();
destPos = (destPos + firstDataBlockSize) % pDestBuffer.length();

// The second data part is the start of the ring buffer.
SampleUtil::copy(pDestBuffer.data(destPos),
pSourceBuffer.data(sourcePos),
numItems - firstDataBlockSize);
Swiftb0y marked this conversation as resolved.
Show resolved Hide resolved
} else {
// Copy is contiguous.
SampleUtil::copy(pDestBuffer.data(destPos),
pSourceBuffer.data(sourcePos),
numItems);
}

return numItems;
}

} // anonymous namespace

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

SINT RingDelayBuffer::read(CSAMPLE* pBuffer, const SINT itemsToRead, const SINT delayItems) {
const SINT shift = itemsToRead + delayItems;

VERIFY_OR_DEBUG_ASSERT(shift <= m_buffer.size()) {
return 0;
}
Swiftb0y marked this conversation as resolved.
Show resolved Hide resolved

SINT readPos = m_writePos - shift;

if (readPos < 0) {
readPos = readPos + m_buffer.size();
}

return copyRing(ReadableSlice(m_buffer.data(), m_buffer.size()),
readPos,
WritableSlice(pBuffer, itemsToRead),
0,
itemsToRead);
}

SINT RingDelayBuffer::write(const CSAMPLE* pBuffer, const SINT itemsToWrite) {
VERIFY_OR_DEBUG_ASSERT(itemsToWrite <= m_buffer.size()) {
return 0;
}

SINT copiedItems;
Swiftb0y marked this conversation as resolved.
Show resolved Hide resolved
if (m_firstInputBuffer) {
// If the first input buffer 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.
// The itemsToWrite value is multiply by 2 to
SampleUtil::copyWithRampingGain(m_buffer.data(), pBuffer, 0.0f, 1.0f, itemsToWrite);
m_firstInputBuffer = false;
copiedItems = itemsToWrite;
} else {
copiedItems = copyRing(ReadableSlice(pBuffer, itemsToWrite),
0,
WritableSlice(m_buffer.data(), m_buffer.size()),
m_writePos,
itemsToWrite);
}

Swiftb0y marked this conversation as resolved.
Show resolved Hide resolved
// Calculate the new write position. If the new write position
// is after the ring delay buffer end, move it around from the start
// of the ring delay buffer.
m_writePos = (m_writePos + copiedItems);

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

return copiedItems;
}
Loading