From 89d60ab7906347bf857c7e59e07a505f16dc0b2a Mon Sep 17 00:00:00 2001 From: jatinchowdhury18 Date: Thu, 14 Dec 2023 22:50:45 -0800 Subject: [PATCH] Add buffer iterator to zip two buffers (#477) --- .../Buffers/chowdsp_BufferIterators.h | 211 ++++++++++-------- .../BufferIteratorsTest.cpp | 71 ++++++ .../FIRPolyphaseDecimatorTest.cpp | 8 +- .../FIRPolyphaseInterpolatorTest.cpp | 9 +- 4 files changed, 191 insertions(+), 108 deletions(-) diff --git a/modules/dsp/chowdsp_buffers/Buffers/chowdsp_BufferIterators.h b/modules/dsp/chowdsp_buffers/Buffers/chowdsp_BufferIterators.h index 382288fba..74002cd10 100644 --- a/modules/dsp/chowdsp_buffers/Buffers/chowdsp_BufferIterators.h +++ b/modules/dsp/chowdsp_buffers/Buffers/chowdsp_BufferIterators.h @@ -5,6 +5,73 @@ namespace chowdsp /** Iterators for iterating over buffers */ namespace buffer_iters { +#ifndef DOXYGEN + namespace detail + { + template + auto getWriteSpan (BufferType& buffer, int channelIndex) + { +#if CHOWDSP_USING_JUCE + if constexpr (std::is_same_v> || std::is_same_v>) + return nonstd::span { buffer.getWritePointer (channelIndex), (size_t) buffer.getNumSamples() }; + else +#endif + return buffer.getWriteSpan (channelIndex); + } + + template + auto getWriteSubSpan (BufferType& buffer, int channelIndex, int start, size_t size) + { +#if CHOWDSP_USING_JUCE + if constexpr (std::is_same_v> || std::is_same_v>) + return nonstd::span { buffer.getWritePointer (channelIndex) + start, size }; + else +#endif + return buffer.getWriteSpan (channelIndex).subspan ((size_t) start, size); + } + + template + auto getReadSpan (BufferType& buffer, int channelIndex) + { +#if CHOWDSP_USING_JUCE + if constexpr (std::is_same_v> || std::is_same_v>) + return nonstd::span { buffer.getReadPointer (channelIndex), (size_t) buffer.getNumSamples() }; + else +#endif + return buffer.getReadSpan (channelIndex); + } + + template + auto getReadSubSpan (BufferType& buffer, int channelIndex, int start, size_t size) + { +#if CHOWDSP_USING_JUCE + if constexpr (std::is_same_v> || std::is_same_v>) + return nonstd::span { buffer.getReadPointer (channelIndex) + start, size }; + else +#endif + return buffer.getReadSpan (channelIndex).subspan ((size_t) start, size); + } + + template + auto getSpan (BufferType& buffer, int channelIndex) + { + if constexpr (IsConstBuffer) + return getReadSpan (buffer, channelIndex); + else + return getWriteSpan (buffer, channelIndex); + } + + template + auto getSubSpan (BufferType& buffer, int channelIndex, int start, size_t size) + { + if constexpr (IsConstBuffer) + return getReadSubSpan (buffer, channelIndex, start, size); + else + return getWriteSubSpan (buffer, channelIndex, start, size); + } + } // namespace detail +#endif + /** Iterates over a buffer's channels */ template constexpr auto channels (BufferType& buffer) @@ -25,36 +92,7 @@ namespace buffer_iters auto operator*() const { - if constexpr (IsConstBuffer) - { -#if CHOWDSP_USING_JUCE - if constexpr (std::is_same_v> || std::is_same_v>) - { - return std::make_tuple (channelIndex, - nonstd::span { buffer.getReadPointer (channelIndex), - (size_t) buffer.getNumSamples() }); - } - else -#endif - { - return std::make_tuple (channelIndex, buffer.getReadSpan (channelIndex)); - } - } - else - { -#if CHOWDSP_USING_JUCE - if constexpr (std::is_same_v> || std::is_same_v>) - { - return std::make_tuple (channelIndex, - nonstd::span { buffer.getWritePointer (channelIndex), - (size_t) buffer.getNumSamples() }); - } - else -#endif - { - return std::make_tuple (channelIndex, buffer.getWriteSpan (channelIndex)); - } - } + return std::make_tuple (channelIndex, detail::getSpan (buffer, channelIndex)); } }; struct iterable_wrapper @@ -66,14 +104,6 @@ namespace buffer_iters return iterable_wrapper { buffer }; } - /** Iterates over a buffer's channels */ - template || SampleTypeHelpers::IsSIMDRegister>> - constexpr auto channels (const BufferView& buffer) - { - return channels> (buffer); - } - /** * Iterates over a buffer in sub-blocks. * @@ -124,46 +154,9 @@ namespace buffer_iters { const auto startSample = buffer.getNumSamples() - samplesRemaining; const auto activeSubBlockSize = (size_t) juce::jmin (subBlockSize, samplesRemaining); - if constexpr (IsConstBuffer) - { -#if CHOWDSP_USING_JUCE - if constexpr (std::is_same_v> || std::is_same_v>) - { - return std::make_tuple (channelIndex, - startSample, - nonstd::span { - buffer.getReadPointer (channelIndex) + startSample, - activeSubBlockSize }); - } - else -#endif - { - return std::make_tuple (channelIndex, - startSample, - buffer.getReadSpan (channelIndex) - .subspan ((size_t) startSample, activeSubBlockSize)); - } - } - else - { -#if CHOWDSP_USING_JUCE - if constexpr (std::is_same_v> || std::is_same_v>) - { - return std::make_tuple (channelIndex, - startSample, - nonstd::span { - buffer.getWritePointer (channelIndex) + startSample, - activeSubBlockSize }); - } - else -#endif - { - return std::make_tuple (channelIndex, - startSample, - buffer.getWriteSpan (channelIndex) - .subspan ((size_t) startSample, activeSubBlockSize)); - } - } + return std::make_tuple (channelIndex, + startSample, + detail::getSubSpan (buffer, channelIndex, startSample, activeSubBlockSize)); } }; struct iterable_wrapper @@ -175,21 +168,6 @@ namespace buffer_iters return iterable_wrapper { buffer }; } - /** - * Iterates over a buffer in sub-blocks. - * - * @tparam subBlockSize The iterator will always supply sub-blocks of this size _or smaller_. - * @tparam channelWise If true, the iterator will iterate over the buffer channels as the innermost loop. - */ - template || SampleTypeHelpers::IsSIMDRegister>> - constexpr auto sub_blocks (const BufferView& buffer) - { - return sub_blocks> (buffer); - } - /** Iterates over a buffer's samples*/ template constexpr auto samples (BufferType& buffer) @@ -197,10 +175,10 @@ namespace buffer_iters struct iterator { using SampleType = BufferSampleType>; - using SamplePtrType = typename std::conditional_t, const SampleType*, SampleType*>; + using SamplePtrType = std::conditional_t, const SampleType*, SampleType*>; BufferType& buffer; - int sampleIndex; + int sampleIndex {}; iterator (BufferType& _buffer, int _sampleIndex) : buffer (_buffer), @@ -220,9 +198,9 @@ namespace buffer_iters SamplePtrType channelPtrs[CHOWDSP_BUFFER_MAX_NUM_CHANNELS] {}; #if CHOWDSP_NO_XSIMD - alignas (16) SampleType channelData[CHOWDSP_BUFFER_MAX_NUM_CHANNELS]; + alignas (16) SampleType channelData[CHOWDSP_BUFFER_MAX_NUM_CHANNELS] {}; #else - alignas (xsimd::batch>::arch_type::alignment()) SampleType channelData[CHOWDSP_BUFFER_MAX_NUM_CHANNELS]; + alignas (xsimd::batch>::arch_type::alignment()) SampleType channelData[CHOWDSP_BUFFER_MAX_NUM_CHANNELS] {}; #endif bool operator!= (const iterator& other) const @@ -240,7 +218,7 @@ namespace buffer_iters ++sampleIndex; for (int channel { 0 }; channel < numChannels; ++channel) - channelPtrs[channel]++; + ++channelPtrs[channel]; } auto operator*() @@ -270,5 +248,42 @@ namespace buffer_iters return iterable_wrapper { buffer }; } + /** Iterates over a buffer's channels */ + template + constexpr auto zip_channels (BufferType1& buffer1, BufferType2& buffer2) + { + struct iterator + { + BufferType1& buffer1; + BufferType2& buffer2; + int channelIndex; + bool operator!= (const iterator& other) const + { + return &buffer1 != &other.buffer1 || &buffer2 != &other.buffer2 || channelIndex != other.channelIndex; + } + + void operator++() + { + ++channelIndex; + } + + auto operator*() const + { + return std::make_tuple (channelIndex, + detail::getSpan (buffer1, channelIndex), + detail::getSpan (buffer2, channelIndex)); + } + }; + struct iterable_wrapper + { + BufferType1& buffer1; + BufferType2& buffer2; + auto begin() { return iterator { buffer1, buffer2, 0 }; } + auto end() { return iterator { buffer1, + buffer2, + std::min (buffer1.getNumChannels(), buffer2.getNumChannels()) }; } + }; + return iterable_wrapper { buffer1, buffer2 }; + } } // namespace buffer_iters } // namespace chowdsp diff --git a/tests/dsp_tests/chowdsp_buffers_test/BufferIteratorsTest.cpp b/tests/dsp_tests/chowdsp_buffers_test/BufferIteratorsTest.cpp index 17d54bee7..b6496bbba 100644 --- a/tests/dsp_tests/chowdsp_buffers_test/BufferIteratorsTest.cpp +++ b/tests/dsp_tests/chowdsp_buffers_test/BufferIteratorsTest.cpp @@ -73,6 +73,39 @@ TEMPLATE_TEST_CASE ("Buffer Iterators Test", } } + SECTION ("Zip Channels") + { + BufferType buffer1 { 2, 4 }; + BufferType buffer2 { 1, 2 }; + + { + int count = 0; + for (auto [channel, data1, data2] : chowdsp::buffer_iters::zip_channels (buffer1, buffer2)) + { + REQUIRE (channel == 0); + REQUIRE (data1.size() == 4); + REQUIRE (data2.size() == 2); + for (auto [x_n, y_n] : chowdsp::zip (data1, data2)) + { + x_n = (SampleType) static_cast (count); + y_n = (SampleType) static_cast (count); + ++count; + } + } + } + + int count = 0; + for (auto [channel, data1, data2] : chowdsp::buffer_iters::zip_channels (std::as_const (buffer1), std::as_const (buffer2))) + { + for (auto [x_n, y_n] : chowdsp::zip (data1, data2)) + { + REQUIRE (chowdsp::SIMDUtils::all ((SampleType) static_cast (count) == x_n)); + REQUIRE (chowdsp::SIMDUtils::all (x_n == y_n)); + ++count; + } + } + } + if constexpr (std::is_same_v>) { SECTION ("Buffer View Channels") @@ -103,6 +136,44 @@ TEMPLATE_TEST_CASE ("Buffer Iterators Test", } } + SECTION ("Buffer View Zip Channels") + { + BufferType buffer1 { 2, 4 }; + BufferType buffer2 { 1, 2 }; + const chowdsp::BufferView bufferView1 { buffer1 }; + const chowdsp::BufferView constBufferView1 { buffer1 }; + const chowdsp::BufferView bufferView2 { buffer2 }; + const chowdsp::BufferView constBufferView2 { buffer2 }; + + { + int count = 0; + for (auto [channel, data1, data2] : chowdsp::buffer_iters::zip_channels (bufferView1, bufferView2)) + { + REQUIRE (channel == 0); + REQUIRE (data1.size() == 4); + REQUIRE (data2.size() == 2); + for (auto [x_n, y_n] : chowdsp::zip (data1, data2)) + { + x_n = (SampleType) static_cast (count); + y_n = (SampleType) static_cast (count); + ++count; + } + } + } + + int count = 0; + for (auto [channel, data1, data2] : chowdsp::buffer_iters::zip_channels (std::as_const (constBufferView1), + std::as_const (constBufferView2))) + { + for (auto [x_n, y_n] : chowdsp::zip (data1, data2)) + { + REQUIRE (chowdsp::SIMDUtils::all ((SampleType) static_cast (count) == x_n)); + REQUIRE (chowdsp::SIMDUtils::all (x_n == y_n)); + ++count; + } + } + } + struct SubBlocksTester { int channel; diff --git a/tests/dsp_tests/chowdsp_filters_test/FIRPolyphaseDecimatorTest.cpp b/tests/dsp_tests/chowdsp_filters_test/FIRPolyphaseDecimatorTest.cpp index 71aac825d..2a43c8401 100644 --- a/tests/dsp_tests/chowdsp_filters_test/FIRPolyphaseDecimatorTest.cpp +++ b/tests/dsp_tests/chowdsp_filters_test/FIRPolyphaseDecimatorTest.cpp @@ -33,12 +33,12 @@ static void decimationFilterCompare (int filterOrder, int decimationFactor, int decimatorFilter.processBlock (chowdsp::BufferView { bufferIn, halfSamples, halfSamples }, chowdsp::BufferView { testBufferOut, halfSamples / decimationFactor, halfSamples / decimationFactor }); - for (int ch = 0; ch < numChannels; ++ch) + for (const auto [ch, refData, testData] : chowdsp::buffer_iters::zip_channels (std::as_const (referenceBufferOut), + std::as_const (testBufferOut))) { - for (int n = 0; n < numSamples / decimationFactor; ++n) + for (const auto [n, test] : chowdsp::enumerate (testData)) { - const auto ref = referenceBufferOut.getReadPointer (ch)[n * decimationFactor]; - const auto test = testBufferOut.getReadPointer (ch)[n]; + const auto ref = refData[n * (size_t) decimationFactor]; REQUIRE (test == Catch::Approx { ref }.margin (1.0e-6)); } } diff --git a/tests/dsp_tests/chowdsp_filters_test/FIRPolyphaseInterpolatorTest.cpp b/tests/dsp_tests/chowdsp_filters_test/FIRPolyphaseInterpolatorTest.cpp index 425c1703e..58bea48a4 100644 --- a/tests/dsp_tests/chowdsp_filters_test/FIRPolyphaseInterpolatorTest.cpp +++ b/tests/dsp_tests/chowdsp_filters_test/FIRPolyphaseInterpolatorTest.cpp @@ -38,14 +38,11 @@ static void interpolationFilterCompare (int filterOrder, int interpolationFactor interpolatorFilter.processBlock (chowdsp::BufferView { bufferIn, halfSamples, halfSamples }, chowdsp::BufferView { testBufferOut, halfSamples * interpolationFactor, halfSamples * interpolationFactor }); - for (int ch = 0; ch < numChannels; ++ch) + for (const auto [ch, refData, testData] : chowdsp::buffer_iters::zip_channels (std::as_const (referenceBufferOut), + std::as_const (testBufferOut))) { - for (int n = 0; n < numSamples * interpolationFactor; ++n) - { - const auto ref = referenceBufferOut.getReadPointer (ch)[n]; - const auto test = testBufferOut.getReadPointer (ch)[n]; + for (const auto [ref, test] : chowdsp::zip (refData, testData)) REQUIRE (test == Catch::Approx { ref }.margin (1.0e-6)); - } } }