forked from Chowdhury-DSP/chowdsp_utils
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding
BufferMultiple
processor (Chowdhury-DSP#563)
* Starting on Buffer Multiple processor * Buffer multiple passes tests * Add comments * Cmake * Apply clang-format --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
e0c8ba0
commit 53df542
Showing
8 changed files
with
244 additions
and
2 deletions.
There are no files selected for viewing
6 changes: 6 additions & 0 deletions
6
modules/common/chowdsp_core/JUCEHelpers/juce_ExtraDefinitions.h
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
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
170 changes: 170 additions & 0 deletions
170
modules/dsp/chowdsp_dsp_data_structures/Processors/chowdsp_BufferMultiple.h
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,170 @@ | ||
#pragma once | ||
|
||
namespace chowdsp | ||
{ | ||
/** | ||
* BufferMultiple can be used to process buffers that need to be a multiple of some | ||
* number of samples. | ||
* | ||
* BufferMultiple requires less latency than RebufferedProcessor, but RebufferedProcessor | ||
* should still be preferred when an exact buffer length is required. | ||
* | ||
* Here's an example where we need to process buffers that are a multiple of 3 samples, | ||
* for example if we wanted to down-sample by 3x: | ||
* @code | ||
* // prepare | ||
* BufferMultiple<float> bufferMultiple {}; | ||
* const auto m3BufferSize = bufferMultiple.prepare (spec, 3); | ||
* arenaBytesNeeded += m3BufferSize * numChannels * sizeof (float); | ||
* nextProcessor.prepare ({ sampleRate, m3BufferSize, numChannels }); | ||
* ... | ||
* arena.reset (arenaBytesNeeded); | ||
* | ||
* // use | ||
* auto m3Buffer = bufferMultiple.processBufferIn (arena, buffer); | ||
* nextProcessor.process (m3Buffer); | ||
* bufferMultiple.processBufferOut (m3Buffer, buffer); | ||
* @endcode | ||
*/ | ||
template <typename T> | ||
class BufferMultiple | ||
{ | ||
public: | ||
BufferMultiple() = default; | ||
|
||
/** | ||
* Prepares the processor for a given multiple. | ||
* | ||
* This method returns the maximum number of samples | ||
* that may be allocated by the arena allocator used | ||
* by processBufferIn(). Note that this number may not | ||
* be a multiple of the requested multiple do to buffer | ||
* padding restrictions. | ||
*/ | ||
int prepare (const juce::dsp::ProcessSpec& spec, int multiple) | ||
{ | ||
jassert (multiple > 1 && multiple <= static_cast<int> (maxMultiple)); | ||
M = multiple; | ||
numChannels = static_cast<int> (spec.numChannels); | ||
|
||
reset(); | ||
|
||
return Math::round_to_next_multiple (Math::round_to_next_multiple (static_cast<int> (spec.maximumBlockSize), M), | ||
static_cast<int> (SIMDUtils::defaultSIMDAlignment / sizeof (T))); | ||
} | ||
|
||
/** Resets the processor state. */ | ||
void reset() noexcept | ||
{ | ||
leftoverDataIn.fill (T {}); | ||
leftoverDataOut.fill (T {}); | ||
numSamplesLeftoverIn = M - 1; | ||
numSamplesLeftoverOut = 1; | ||
} | ||
|
||
/** Returns the latency of the "multiple" buffer. */ | ||
[[nodiscard]] int getMultipleBufferLatency() const noexcept | ||
{ | ||
return M - 1; | ||
} | ||
|
||
/** Returns the "round trip" latency for this processor. */ | ||
[[nodiscard]] int getRoundTripLatency() const noexcept | ||
{ | ||
return M; | ||
} | ||
|
||
/** Returns a buffer that is the requested multiple number of samples. */ | ||
BufferView<T> processBufferIn (ArenaAllocatorView arena, const BufferView<const T>& input) noexcept | ||
{ | ||
const auto numSamplesIn = input.getNumSamples(); | ||
const auto numMultiplesOut = (numSamplesIn + numSamplesLeftoverIn - 1) / M; | ||
const auto numSamplesOut = M * numMultiplesOut; | ||
const auto newNumLeftoverSamples = numSamplesIn + numSamplesLeftoverIn - numSamplesOut; | ||
jassert (newNumLeftoverSamples <= M); | ||
|
||
auto leftoverSamplesToUse = std::min (numSamplesLeftoverIn, numSamplesOut); | ||
|
||
const auto bufferOut = make_temp_buffer<T> (arena, input.getNumChannels(), numSamplesOut); | ||
|
||
for (auto [ch, outData] : buffer_iters::channels (bufferOut)) | ||
{ | ||
const auto inputs = input.getReadSpan (ch); | ||
const auto leftovers = getLeftoversIn (ch); | ||
|
||
std::copy (leftovers.begin(), leftovers.begin() + leftoverSamplesToUse, outData.begin()); | ||
std::copy (inputs.begin(), inputs.begin() + numSamplesOut - leftoverSamplesToUse, outData.begin() + leftoverSamplesToUse); | ||
|
||
if (leftoverSamplesToUse < numSamplesLeftoverIn) | ||
{ | ||
std::copy (leftovers.begin() + leftoverSamplesToUse, leftovers.end(), leftovers.begin()); | ||
} | ||
|
||
std::copy (inputs.begin() + numSamplesOut - leftoverSamplesToUse, | ||
inputs.end(), | ||
leftovers.begin() + numSamplesLeftoverIn - leftoverSamplesToUse); | ||
} | ||
numSamplesLeftoverIn = newNumLeftoverSamples; | ||
|
||
return bufferOut; | ||
} | ||
|
||
/** | ||
* Copies the multiple buffer into some output buffer. | ||
* | ||
* The output buffer is expected to be the same size as the | ||
* most recent buffer provided to processBufferIn. | ||
*/ | ||
void processBufferOut (const BufferView<const T>& input, const BufferView<T>& output) noexcept | ||
{ | ||
const auto numSamplesIn = input.getNumSamples(); | ||
const auto numSamplesOut = output.getNumSamples(); | ||
const auto newNumLeftoverSamples = numSamplesIn + numSamplesLeftoverOut - numSamplesOut; | ||
jassert (newNumLeftoverSamples <= M); | ||
|
||
auto leftoverSamplesToUse = std::min (numSamplesLeftoverOut, numSamplesOut); | ||
|
||
for (auto [ch, outData] : buffer_iters::channels (output)) | ||
{ | ||
const auto inputs = input.getReadSpan (ch); | ||
const auto leftovers = getLeftoversOut (ch); | ||
|
||
std::copy (leftovers.begin(), leftovers.begin() + leftoverSamplesToUse, outData.begin()); | ||
std::copy (inputs.begin(), inputs.begin() + numSamplesOut - leftoverSamplesToUse, outData.begin() + leftoverSamplesToUse); | ||
|
||
if (leftoverSamplesToUse < numSamplesLeftoverOut) | ||
{ | ||
std::copy (leftovers.begin() + leftoverSamplesToUse, leftovers.end(), leftovers.begin()); | ||
} | ||
|
||
std::copy (inputs.begin() + numSamplesOut - leftoverSamplesToUse, | ||
inputs.end(), | ||
leftovers.begin() + numSamplesLeftoverOut - leftoverSamplesToUse); | ||
} | ||
|
||
numSamplesLeftoverOut = newNumLeftoverSamples; | ||
} | ||
|
||
private: | ||
nonstd::span<T> getLeftoversIn (int ch) | ||
{ | ||
return { leftoverDataIn.data() + ch * M, static_cast<size_t> (M) }; | ||
} | ||
|
||
nonstd::span<T> getLeftoversOut (int ch) | ||
{ | ||
return { leftoverDataOut.data() + ch * M, static_cast<size_t> (M) }; | ||
} | ||
|
||
int M = 0; | ||
int numChannels = 0; | ||
int numSamplesLeftoverIn = 0; | ||
int numSamplesLeftoverOut = -1; | ||
|
||
static constexpr size_t maxMultiple = 8; | ||
std::array<T, CHOWDSP_BUFFER_MAX_NUM_CHANNELS * maxMultiple> leftoverDataIn {}; | ||
std::array<T, CHOWDSP_BUFFER_MAX_NUM_CHANNELS * maxMultiple> leftoverDataOut {}; | ||
|
||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferMultiple) | ||
}; | ||
} // namespace chowdsp |
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
58 changes: 58 additions & 0 deletions
58
tests/dsp_tests/chowdsp_dsp_data_structures_test/BufferMultipleTest.cpp
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,58 @@ | ||
#include <CatchUtils.h> | ||
#include <chowdsp_dsp_data_structures/chowdsp_dsp_data_structures.h> | ||
|
||
#include <iostream> | ||
|
||
TEST_CASE ("Buffer Multiple Test", "[dsp][data-structures]") | ||
{ | ||
static constexpr int numChannels = 2; | ||
static constexpr int bufferSize = 64; | ||
static constexpr int N = 1000; | ||
|
||
for (int multiple : std::initializer_list<int> { 2, 3, 4, 5, 6, 7 }) | ||
{ | ||
std::vector<float> data (N, 0.0f); | ||
std::iota (std::begin (data), std::end (data), 0.0f); | ||
std::vector<float> data2 { data.begin(), data.end() }; | ||
float* dataPtrs[numChannels] = { data.data(), data2.data() }; | ||
|
||
chowdsp::BufferMultiple<float> multipleProcessor {}; | ||
int maxMultipleBufferSize = multipleProcessor.prepare ({ 0.0, (uint32_t) bufferSize, (uint32_t) numChannels }, multiple); | ||
const auto mLatencySamples = multipleProcessor.getMultipleBufferLatency(); | ||
const auto roundTripLatencySamples = multipleProcessor.getRoundTripLatency(); | ||
|
||
chowdsp::ArenaAllocator<> arena { numChannels * maxMultipleBufferSize * sizeof (float) }; | ||
|
||
int inOutCount = 0; | ||
int mCount = 0; | ||
for (int n : std::initializer_list<int> { 6, 20, 1, 3, 15, 16, 5, 7, 18, bufferSize, bufferSize, bufferSize }) | ||
{ | ||
chowdsp::BufferView buffer { dataPtrs, numChannels, n, inOutCount }; | ||
const auto mBuffer = multipleProcessor.processBufferIn (arena, buffer); | ||
|
||
REQUIRE (mBuffer.getNumSamples() % multiple == 0); | ||
REQUIRE (mBuffer.getNumSamples() <= maxMultipleBufferSize); | ||
for (auto [ch, channelData] : chowdsp::buffer_iters::channels (mBuffer)) | ||
{ | ||
for (const auto& [idx, x] : chowdsp::enumerate (channelData)) | ||
{ | ||
REQUIRE (x == std::max (static_cast<float> (mCount + (int) idx - mLatencySamples), 0.0f)); | ||
x = -x; | ||
} | ||
} | ||
|
||
multipleProcessor.processBufferOut (mBuffer, buffer); | ||
for (auto [ch, channelData] : chowdsp::buffer_iters::channels (buffer)) | ||
{ | ||
for (const auto& [idx, x] : chowdsp::enumerate (channelData)) | ||
{ | ||
REQUIRE (-x == std::max (static_cast<float> (inOutCount + (int) idx - roundTripLatencySamples), 0.0f)); | ||
} | ||
} | ||
|
||
inOutCount += n; | ||
mCount += mBuffer.getNumSamples(); | ||
arena.clear(); | ||
} | ||
} | ||
} |
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