Skip to content

Commit

Permalink
Add buffer iterator to zip two buffers (#477)
Browse files Browse the repository at this point in the history
  • Loading branch information
jatinchowdhury18 authored Dec 15, 2023
1 parent 9b1c6c9 commit 89d60ab
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 108 deletions.
211 changes: 113 additions & 98 deletions modules/dsp/chowdsp_buffers/Buffers/chowdsp_BufferIterators.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,73 @@ namespace chowdsp
/** Iterators for iterating over buffers */
namespace buffer_iters
{
#ifndef DOXYGEN
namespace detail
{
template <typename BufferType>
auto getWriteSpan (BufferType& buffer, int channelIndex)
{
#if CHOWDSP_USING_JUCE
if constexpr (std::is_same_v<BufferType, juce::AudioBuffer<float>> || std::is_same_v<BufferType, juce::AudioBuffer<double>>)
return nonstd::span { buffer.getWritePointer (channelIndex), (size_t) buffer.getNumSamples() };
else
#endif
return buffer.getWriteSpan (channelIndex);
}

template <typename BufferType>
auto getWriteSubSpan (BufferType& buffer, int channelIndex, int start, size_t size)
{
#if CHOWDSP_USING_JUCE
if constexpr (std::is_same_v<BufferType, juce::AudioBuffer<float>> || std::is_same_v<BufferType, juce::AudioBuffer<double>>)
return nonstd::span { buffer.getWritePointer (channelIndex) + start, size };
else
#endif
return buffer.getWriteSpan (channelIndex).subspan ((size_t) start, size);
}

template <typename BufferType>
auto getReadSpan (BufferType& buffer, int channelIndex)
{
#if CHOWDSP_USING_JUCE
if constexpr (std::is_same_v<BufferType, const juce::AudioBuffer<float>> || std::is_same_v<BufferType, const juce::AudioBuffer<double>>)
return nonstd::span { buffer.getReadPointer (channelIndex), (size_t) buffer.getNumSamples() };
else
#endif
return buffer.getReadSpan (channelIndex);
}

template <typename BufferType>
auto getReadSubSpan (BufferType& buffer, int channelIndex, int start, size_t size)
{
#if CHOWDSP_USING_JUCE
if constexpr (std::is_same_v<BufferType, const juce::AudioBuffer<float>> || std::is_same_v<BufferType, const juce::AudioBuffer<double>>)
return nonstd::span { buffer.getReadPointer (channelIndex) + start, size };
else
#endif
return buffer.getReadSpan (channelIndex).subspan ((size_t) start, size);
}

template <typename BufferType>
auto getSpan (BufferType& buffer, int channelIndex)
{
if constexpr (IsConstBuffer<BufferType>)
return getReadSpan (buffer, channelIndex);
else
return getWriteSpan (buffer, channelIndex);
}

template <typename BufferType>
auto getSubSpan (BufferType& buffer, int channelIndex, int start, size_t size)
{
if constexpr (IsConstBuffer<BufferType>)
return getReadSubSpan (buffer, channelIndex, start, size);
else
return getWriteSubSpan (buffer, channelIndex, start, size);
}
} // namespace detail
#endif

/** Iterates over a buffer's channels */
template <typename BufferType>
constexpr auto channels (BufferType& buffer)
Expand All @@ -25,36 +92,7 @@ namespace buffer_iters

auto operator*() const
{
if constexpr (IsConstBuffer<BufferType>)
{
#if CHOWDSP_USING_JUCE
if constexpr (std::is_same_v<BufferType, const juce::AudioBuffer<float>> || std::is_same_v<BufferType, const juce::AudioBuffer<double>>)
{
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<BufferType, juce::AudioBuffer<float>> || std::is_same_v<BufferType, juce::AudioBuffer<double>>)
{
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
Expand All @@ -66,14 +104,6 @@ namespace buffer_iters
return iterable_wrapper { buffer };
}

/** Iterates over a buffer's channels */
template <typename SampleType,
typename = typename std::enable_if_t<std::is_floating_point_v<SampleType> || SampleTypeHelpers::IsSIMDRegister<SampleType>>>
constexpr auto channels (const BufferView<SampleType>& buffer)
{
return channels<const BufferView<SampleType>> (buffer);
}

/**
* Iterates over a buffer in sub-blocks.
*
Expand Down Expand Up @@ -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<BufferType>)
{
#if CHOWDSP_USING_JUCE
if constexpr (std::is_same_v<BufferType, const juce::AudioBuffer<float>> || std::is_same_v<BufferType, const juce::AudioBuffer<double>>)
{
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<BufferType, juce::AudioBuffer<float>> || std::is_same_v<BufferType, juce::AudioBuffer<double>>)
{
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
Expand All @@ -175,32 +168,17 @@ 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 <int subBlockSize,
bool channelWise = false,
typename SampleType,
typename = typename std::enable_if_t<std::is_floating_point_v<SampleType> || SampleTypeHelpers::IsSIMDRegister<SampleType>>>
constexpr auto sub_blocks (const BufferView<SampleType>& buffer)
{
return sub_blocks<subBlockSize, channelWise, const BufferView<SampleType>> (buffer);
}

/** Iterates over a buffer's samples*/
template <typename BufferType>
constexpr auto samples (BufferType& buffer)
{
struct iterator
{
using SampleType = BufferSampleType<std::remove_const_t<BufferType>>;
using SamplePtrType = typename std::conditional_t<IsConstBuffer<BufferType>, const SampleType*, SampleType*>;
using SamplePtrType = std::conditional_t<IsConstBuffer<BufferType>, const SampleType*, SampleType*>;

BufferType& buffer;
int sampleIndex;
int sampleIndex {};

iterator (BufferType& _buffer, int _sampleIndex)
: buffer (_buffer),
Expand All @@ -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<SampleTypeHelpers::NumericType<SampleType>>::arch_type::alignment()) SampleType channelData[CHOWDSP_BUFFER_MAX_NUM_CHANNELS];
alignas (xsimd::batch<SampleTypeHelpers::NumericType<SampleType>>::arch_type::alignment()) SampleType channelData[CHOWDSP_BUFFER_MAX_NUM_CHANNELS] {};
#endif

bool operator!= (const iterator& other) const
Expand All @@ -240,7 +218,7 @@ namespace buffer_iters

++sampleIndex;
for (int channel { 0 }; channel < numChannels; ++channel)
channelPtrs[channel]++;
++channelPtrs[channel];
}

auto operator*()
Expand Down Expand Up @@ -270,5 +248,42 @@ namespace buffer_iters
return iterable_wrapper { buffer };
}

/** Iterates over a buffer's channels */
template <typename BufferType1, typename BufferType2>
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
71 changes: 71 additions & 0 deletions tests/dsp_tests/chowdsp_buffers_test/BufferIteratorsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<float> (count);
y_n = (SampleType) static_cast<float> (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<float> (count) == x_n));
REQUIRE (chowdsp::SIMDUtils::all (x_n == y_n));
++count;
}
}
}

if constexpr (std::is_same_v<BufferType, chowdsp::Buffer<float>>)
{
SECTION ("Buffer View Channels")
Expand Down Expand Up @@ -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<SampleType> bufferView1 { buffer1 };
const chowdsp::BufferView<const SampleType> constBufferView1 { buffer1 };
const chowdsp::BufferView<SampleType> bufferView2 { buffer2 };
const chowdsp::BufferView<const SampleType> 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<float> (count);
y_n = (SampleType) static_cast<float> (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<float> (count) == x_n));
REQUIRE (chowdsp::SIMDUtils::all (x_n == y_n));
++count;
}
}
}

struct SubBlocksTester
{
int channel;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
}

Expand Down

0 comments on commit 89d60ab

Please sign in to comment.