-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
+408
−0
Merged
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 ccb9650
RingDelayBufferTest: add tests for a ring buffer
davidchocholaty 207d26f
RingDelayBuffer: add original license
davidchocholaty 785f636
RingDelayBuffer: clearing of m_jumpLeftAroundMask
davidchocholaty a155157
RingDelayBuffer: allow equality for the right jump
davidchocholaty eeb6e6d
RingDelayBuffer: solve jump left maximum size
davidchocholaty cb2d57b
RingDelayBufferTest: add benchmarks
davidchocholaty 1dd8830
RingDelayBuffer: remove unnecessary destructor
davidchocholaty 8f26438
RingDelayBuffer: use the size of SampleBuffer
davidchocholaty 36d92d2
RingDelayBuffer: improve const correctness
davidchocholaty 248f38e
RingDelayBuffer: add single-threaded only comment
davidchocholaty 4e6a1ef
RingDelayBufferTest: use of unique_ptr
davidchocholaty d6565e7
RingDelayBuffer: replace zeros memset with fill
davidchocholaty 0bfd168
RingDelayBuffer: replace with SampleUtil::copy
davidchocholaty 9ef5752
RingDelayBuffer: fix buffer clear with zeros
davidchocholaty 5772334
RingDelayBuffer: remove binary operations
davidchocholaty de60af2
RingDelayBuffer: apply fading in
davidchocholaty 77181a8
RingDelayBuffer: update m_fullFlag handling
davidchocholaty 1df0fbb
RingDelayBuffer: fix typo in phrase THREAD-SAFE
davidchocholaty 7c2610e
RingDelayBuffer: remove const from the parameter
davidchocholaty 95182ea
RingDelayBuffer: skip for invalid number of items
davidchocholaty 90f9772
RingDelayBuffer: const number of items
davidchocholaty dd4980a
RingDelayBuffer: read with provided delay
davidchocholaty adf2454
RingDelayBuffer: deduplicate the buffers copying
davidchocholaty 0a61a7b
RingDelayBuffer: remove unused function definition
davidchocholaty c6a6c3a
RingDelayBuffer:: update commentary part
davidchocholaty e3638b4
RingDelayBuffer: change to VERIFY_OR_DEBUG_ASSERT
davidchocholaty b95d6be
RingDelayBuffer: rename copy function to copyRing
davidchocholaty b73a2b5
RingDelayBuffer: move copyRing into a namespace
davidchocholaty 2b2a7a3
RingDelayBuffer: handle invalid copy alignment
davidchocholaty 0f4729f
RingDelayBuffer: add default value assignment
davidchocholaty c683b41
RingDelayBuffer: avoid uninitialized memory
davidchocholaty 0828535
Merge remote-tracking branch 'upstream/main' into ring_delay_buffer
davidchocholaty 6e212af
RingDelayBuffer: replace slices by std::span
davidchocholaty ac54694
RingDelayBuffer: rename without pointer prefix
davidchocholaty 6ee6603
RingDelayBufferTest: introduce std::span
davidchocholaty bc6240a
RingDelayBuffer: rename m_firstInputBuffer
davidchocholaty 74b52ea
RingDelayBuffer: add documentation comments
davidchocholaty 9addec2
Merge remote-tracking branch 'upstream/main' into ring_delay_buffer
davidchocholaty 8076e29
RingDelayBuffer: size returned value as constexpr
davidchocholaty d886d09
RingDelayBuffer: use spans in the API
davidchocholaty 6c22c40
RingDelayBuffer: remove the unnecessary licence
davidchocholaty b686546
RingDelayBuffer: use SampleBuffer's span
davidchocholaty File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,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 |
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,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) { | ||
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; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 usestd::span
instead?There was a problem hiding this comment.
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 throughSampleBuffer
and forgot, that I can call themixxx::spanutil::spanFromPtrLen()
directly.There was a problem hiding this comment.
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.