diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index efc03ea..7c7d680 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,7 +2,7 @@ enable_testing() add_executable(unit_tests) target_sources(unit_tests PRIVATE - ConvolverUtilitiesTests.cpp + SplitBlockTests.cpp ComplexBufferTests.cpp ) diff --git a/test/ComplexBufferTests.cpp b/test/ComplexBufferTests.cpp index f7c541d..b338d11 100644 --- a/test/ComplexBufferTests.cpp +++ b/test/ComplexBufferTests.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include SCENARIO ("can construct complex buffers with different channel and point sizes", "[ComplexBuffer]") { diff --git a/test/ConvolverUtilitiesTests.cpp b/test/SplitBlockTests.cpp similarity index 98% rename from test/ConvolverUtilitiesTests.cpp rename to test/SplitBlockTests.cpp index dc3b453..561691f 100644 --- a/test/ConvolverUtilitiesTests.cpp +++ b/test/SplitBlockTests.cpp @@ -1,7 +1,6 @@ -#include - #include #include +#include SCENARIO ("split blocks can be cleared", "[SplitBlock]") { diff --git a/zones_convolver/ConvolverUtilities.cpp b/zones_convolver/ConvolverUtilities.cpp deleted file mode 100644 index 8f80836..0000000 --- a/zones_convolver/ConvolverUtilities.cpp +++ /dev/null @@ -1,197 +0,0 @@ -#include "ConvolverUtilities.h" - -SplitBlock::SplitBlock (juce::dsp::AudioBlock first_block, - juce::dsp::AudioBlock wrapped_block) - : first_block_ (first_block) - , wrapped_block_ (wrapped_block) -{ - total_num_samples_ = first_block.getNumSamples () + wrapped_block.getNumSamples (); -} - -void SplitBlock::CopyFrom (const juce::dsp::AudioBlock block) -{ - auto num_samples = block.getNumSamples (); - auto first_samples_to_copy = std::min (num_samples, first_block_.getNumSamples ()); - auto remaining_samples_to_copy = num_samples - first_samples_to_copy; - - if (first_samples_to_copy > 0) - first_block_.copyFrom (block.getSubBlock (0, first_samples_to_copy)); - if (remaining_samples_to_copy > 0) - wrapped_block_.copyFrom ( - block.getSubBlock (first_samples_to_copy, remaining_samples_to_copy)); -} - -void SplitBlock::AddFrom (const juce::dsp::AudioBlock block) -{ - auto num_samples = block.getNumSamples (); - auto first_samples_to_copy = std::min (num_samples, first_block_.getNumSamples ()); - auto remaining_samples_to_copy = num_samples - first_samples_to_copy; - - if (first_samples_to_copy > 0) - first_block_.add (block.getSubBlock (0, first_samples_to_copy)); - if (remaining_samples_to_copy > 0) - wrapped_block_.add (block.getSubBlock (first_samples_to_copy, remaining_samples_to_copy)); -} - -void SplitBlock::CopyTo (juce::dsp::AudioBlock block) -{ - block.copyFrom (first_block_); - - auto first_num_samples = first_block_.getNumSamples (); - if (block.getNumSamples () > first_num_samples && wrapped_block_.getNumSamples () > 0) - block.getSubBlock (first_num_samples).copyFrom (wrapped_block_); -} - -void SplitBlock::Clear () -{ - first_block_.clear (); - wrapped_block_.clear (); -} - -SplitBlock SplitBlock::GetSubBlock (std::size_t num_samples) -{ - jassert (num_samples <= total_num_samples_); - - auto first_samples_to_take = std::min (num_samples, first_block_.getNumSamples ()); - auto wrapped_samples_to_take = num_samples - first_samples_to_take; - - return {first_block_.getSubBlock (0, first_samples_to_take), - wrapped_block_.getSubBlock (0, wrapped_samples_to_take)}; -} - -CircularBuffer::CircularBuffer (juce::AudioBuffer & buffer) - : buffer_ (buffer) -{ -} - -SplitBlock CircularBuffer::GetNext (std::size_t advancement) -{ - juce::dsp::AudioBlock block {buffer_}; - auto num_samples = buffer_.getNumSamples (); - head_position_ = (head_position_ + advancement) % num_samples; - - auto first_samples_to_take = num_samples - head_position_; - auto wrapped_samples_to_take = num_samples - first_samples_to_take; - - return {block.getSubBlock (head_position_, first_samples_to_take), - block.getSubBlock (0, wrapped_samples_to_take)}; -} - -ComplexBuffer::ComplexBuffer (std::size_t num_points, std::size_t num_channels) - : num_channels_ (num_channels) - , num_points_ (num_points) -{ - channel_data_.resize (num_points * num_channels); - for (auto channel_index = 0; channel_index < num_channels; ++channel_index) - channel_pointers_.push_back (&channel_data_ [channel_index * num_points]); -} - -std::size_t ComplexBuffer::GetNumChannels () const -{ - return num_channels_; -} - -std::size_t ComplexBuffer::GetNumPoints () const -{ - return num_points_; -} - -const std::complex * ComplexBuffer::GetReadPointer (std::size_t channel_index) const -{ - jassert (juce::isPositiveAndBelow (channel_index, num_channels_)); - return channel_pointers_ [channel_index]; -} - -std::complex * ComplexBuffer::GetWritePointer (std::size_t channel_index) -{ - jassert (juce::isPositiveAndBelow (channel_index, num_channels_)); - return channel_pointers_ [channel_index]; -} - -const std::complex * const * ComplexBuffer::GetArrayOfReadPointer () const -{ - return channel_pointers_.data (); -} - -std::complex * const * ComplexBuffer::GetArrayOfWritePointer () -{ - return channel_pointers_.data (); -} - -void ComplexBuffer::Clear () -{ - GetContinuousBlock ().clear (); -} - -void ComplexBuffer::ComplexMultiplyFrom (const ComplexBuffer & a, const ComplexBuffer & b) -{ - jassert (num_channels_ == a.num_channels_ && a.num_channels_ == b.num_channels_); - jassert (num_points_ == a.num_points_ && a.num_points_ == b.num_points_); - - for (auto point_index = 0; point_index < channel_data_.size (); ++point_index) - channel_data_ [point_index] = a.channel_data_ [point_index] * b.channel_data_ [point_index]; -} - -void ComplexBuffer::ComplexMultiplyAccumulateFrom (const ComplexBuffer & a, const ComplexBuffer & b) -{ - jassert (num_channels_ == a.num_channels_ && a.num_channels_ == b.num_channels_); - jassert (num_points_ == a.num_points_ && a.num_points_ == b.num_points_); - - for (auto point_index = 0; point_index < channel_data_.size (); ++point_index) - channel_data_ [point_index] += - a.channel_data_ [point_index] * b.channel_data_ [point_index]; -} - -void ComplexBuffer::CopyFromAudioBlock (const juce::dsp::AudioBlock block) -{ - jassert (num_channels_ == block.getNumChannels ()); - - auto fill_points = std::min (num_points_, block.getNumSamples ()); - - for (auto channel_index = 0u; channel_index < num_channels_; ++channel_index) - for (auto point_index = 0u; point_index < fill_points; ++point_index) - GetWritePointer (channel_index) [point_index] = { - block.getSample (channel_index, point_index), 0.f}; -} - -juce::dsp::AudioBlock ComplexBuffer::GetContinuousBlock () -{ - auto write_pointers = reinterpret_cast (GetArrayOfWritePointer ()); - return {write_pointers, num_channels_, 2 * num_points_}; -} - -juce::dsp::AudioBlock ComplexBuffer::GetContinuousBlock () const -{ - auto write_pointers = reinterpret_cast (GetArrayOfReadPointer ()); - return {write_pointers, num_channels_, 2 * num_points_}; -} - -FrequencyDelayLine::FrequencyDelayLine (std::size_t num_channels, - std::size_t num_blocks, - std::size_t num_points_per_block) -{ - num_blocks_ = num_blocks; - for (auto i = 0; i < num_blocks; ++i) - { - ComplexBuffer buffer {num_points_per_block, num_channels}; - buffer.Clear (); - delay_line_.emplace_back (std::move (buffer)); - } -} - -static inline std::size_t ReverseWrap (int a, int b) -{ - return static_cast ((b + (a % b)) % b); -} - -ComplexBuffer & FrequencyDelayLine::GetNextBlock () -{ - head_position_ = - ReverseWrap (static_cast (head_position_) - 1, static_cast (num_blocks_)); - return delay_line_ [head_position_]; -} - -const ComplexBuffer & FrequencyDelayLine::GetBlockWithOffset (std::size_t offset) const -{ - return delay_line_ [(head_position_ + offset) % num_blocks_]; -} \ No newline at end of file diff --git a/zones_convolver/ConvolverUtilities.h b/zones_convolver/ConvolverUtilities.h deleted file mode 100644 index 9309a04..0000000 --- a/zones_convolver/ConvolverUtilities.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once -#include - -class SplitBlock -{ -public: - SplitBlock (juce::dsp::AudioBlock first_block, - juce::dsp::AudioBlock wrapped_block); - - void CopyFrom (juce::dsp::AudioBlock block); - void AddFrom (juce::dsp::AudioBlock block); - - void CopyTo (juce::dsp::AudioBlock block); - void Clear (); - - SplitBlock GetSubBlock (std::size_t num_samples); - -private: - juce::dsp::AudioBlock first_block_; - juce::dsp::AudioBlock wrapped_block_; - - std::size_t total_num_samples_ = 0; -}; - -class CircularBuffer -{ -public: - explicit CircularBuffer (juce::AudioBuffer & buffer); - SplitBlock GetNext (std::size_t advancement); - -private: - std::size_t head_position_ = 0; - juce::AudioBuffer & buffer_; -}; - -class ComplexBuffer -{ -public: - ComplexBuffer (std::size_t num_points, std::size_t num_channels); - [[nodiscard]] std::size_t GetNumChannels () const; - [[nodiscard]] std::size_t GetNumPoints () const; - - [[nodiscard]] const std::complex * GetReadPointer (std::size_t channel_index) const; - std::complex * GetWritePointer (std::size_t channel_index); - - [[nodiscard]] const std::complex * const * GetArrayOfReadPointer () const; - std::complex * const * GetArrayOfWritePointer (); - - [[nodiscard]] juce::dsp::AudioBlock GetContinuousBlock (); - [[nodiscard]] juce::dsp::AudioBlock GetContinuousBlock () const; - - void Clear (); - void ComplexMultiplyFrom (const ComplexBuffer & a, const ComplexBuffer & b); - void ComplexMultiplyAccumulateFrom (const ComplexBuffer & a, const ComplexBuffer & b); - void CopyFromAudioBlock (juce::dsp::AudioBlock block); - -private: - std::size_t num_channels_ = 0; - std::size_t num_points_ = 0; - std::vector> channel_data_; - std::vector *> channel_pointers_; -}; - -class FrequencyDelayLine -{ -public: - FrequencyDelayLine (std::size_t num_channels, - std::size_t num_blocks, - std::size_t num_points_per_block); - - ComplexBuffer & GetNextBlock (); - [[nodiscard]] const ComplexBuffer & GetBlockWithOffset (std::size_t offset) const; - -private: - std::vector delay_line_; - std::size_t head_position_ = 0; - std::size_t num_blocks_; -}; \ No newline at end of file diff --git a/zones_convolver/util/CircularBuffer.cpp b/zones_convolver/util/CircularBuffer.cpp new file mode 100644 index 0000000..f3bf43b --- /dev/null +++ b/zones_convolver/util/CircularBuffer.cpp @@ -0,0 +1,19 @@ +#include "CircularBuffer.h" + +CircularBuffer::CircularBuffer (juce::AudioBuffer & buffer) + : buffer_ (buffer) +{ +} + +SplitBlock CircularBuffer::GetNext (std::size_t advancement) +{ + juce::dsp::AudioBlock block {buffer_}; + auto num_samples = buffer_.getNumSamples (); + head_position_ = (head_position_ + advancement) % num_samples; + + auto first_samples_to_take = num_samples - head_position_; + auto wrapped_samples_to_take = num_samples - first_samples_to_take; + + return {block.getSubBlock (head_position_, first_samples_to_take), + block.getSubBlock (0, wrapped_samples_to_take)}; +} \ No newline at end of file diff --git a/zones_convolver/util/CircularBuffer.h b/zones_convolver/util/CircularBuffer.h new file mode 100644 index 0000000..cf005c2 --- /dev/null +++ b/zones_convolver/util/CircularBuffer.h @@ -0,0 +1,16 @@ +#pragma once + +#include "SplitBlock.h" + +#include + +class CircularBuffer +{ +public: + explicit CircularBuffer (juce::AudioBuffer & buffer); + SplitBlock GetNext (std::size_t advancement); + +private: + std::size_t head_position_ = 0; + juce::AudioBuffer & buffer_; +}; \ No newline at end of file diff --git a/zones_convolver/util/ComplexBuffer.cpp b/zones_convolver/util/ComplexBuffer.cpp new file mode 100644 index 0000000..3af1098 --- /dev/null +++ b/zones_convolver/util/ComplexBuffer.cpp @@ -0,0 +1,90 @@ +#include "ComplexBuffer.h" + +ComplexBuffer::ComplexBuffer (std::size_t num_points, std::size_t num_channels) + : num_channels_ (num_channels) + , num_points_ (num_points) +{ + channel_data_.resize (num_points * num_channels); + for (auto channel_index = 0; channel_index < num_channels; ++channel_index) + channel_pointers_.push_back (&channel_data_ [channel_index * num_points]); +} + +std::size_t ComplexBuffer::GetNumChannels () const +{ + return num_channels_; +} + +std::size_t ComplexBuffer::GetNumPoints () const +{ + return num_points_; +} + +const std::complex * ComplexBuffer::GetReadPointer (std::size_t channel_index) const +{ + jassert (juce::isPositiveAndBelow (channel_index, num_channels_)); + return channel_pointers_ [channel_index]; +} + +std::complex * ComplexBuffer::GetWritePointer (std::size_t channel_index) +{ + jassert (juce::isPositiveAndBelow (channel_index, num_channels_)); + return channel_pointers_ [channel_index]; +} + +const std::complex * const * ComplexBuffer::GetArrayOfReadPointer () const +{ + return channel_pointers_.data (); +} + +std::complex * const * ComplexBuffer::GetArrayOfWritePointer () +{ + return channel_pointers_.data (); +} + +void ComplexBuffer::Clear () +{ + GetContinuousBlock ().clear (); +} + +void ComplexBuffer::ComplexMultiplyFrom (const ComplexBuffer & a, const ComplexBuffer & b) +{ + jassert (num_channels_ == a.num_channels_ && a.num_channels_ == b.num_channels_); + jassert (num_points_ == a.num_points_ && a.num_points_ == b.num_points_); + + for (auto point_index = 0; point_index < channel_data_.size (); ++point_index) + channel_data_ [point_index] = a.channel_data_ [point_index] * b.channel_data_ [point_index]; +} + +void ComplexBuffer::ComplexMultiplyAccumulateFrom (const ComplexBuffer & a, const ComplexBuffer & b) +{ + jassert (num_channels_ == a.num_channels_ && a.num_channels_ == b.num_channels_); + jassert (num_points_ == a.num_points_ && a.num_points_ == b.num_points_); + + for (auto point_index = 0; point_index < channel_data_.size (); ++point_index) + channel_data_ [point_index] += + a.channel_data_ [point_index] * b.channel_data_ [point_index]; +} + +void ComplexBuffer::CopyFromAudioBlock (const juce::dsp::AudioBlock block) +{ + jassert (num_channels_ == block.getNumChannels ()); + + auto fill_points = std::min (num_points_, block.getNumSamples ()); + + for (auto channel_index = 0u; channel_index < num_channels_; ++channel_index) + for (auto point_index = 0u; point_index < fill_points; ++point_index) + GetWritePointer (channel_index) [point_index] = { + block.getSample (channel_index, point_index), 0.f}; +} + +juce::dsp::AudioBlock ComplexBuffer::GetContinuousBlock () +{ + auto write_pointers = reinterpret_cast (GetArrayOfWritePointer ()); + return {write_pointers, num_channels_, 2 * num_points_}; +} + +juce::dsp::AudioBlock ComplexBuffer::GetContinuousBlock () const +{ + auto write_pointers = reinterpret_cast (GetArrayOfReadPointer ()); + return {write_pointers, num_channels_, 2 * num_points_}; +} diff --git a/zones_convolver/util/ComplexBuffer.h b/zones_convolver/util/ComplexBuffer.h new file mode 100644 index 0000000..e8a3ae6 --- /dev/null +++ b/zones_convolver/util/ComplexBuffer.h @@ -0,0 +1,30 @@ +#pragma once +#include + +class ComplexBuffer +{ +public: + ComplexBuffer (std::size_t num_points, std::size_t num_channels); + [[nodiscard]] std::size_t GetNumChannels () const; + [[nodiscard]] std::size_t GetNumPoints () const; + + [[nodiscard]] const std::complex * GetReadPointer (std::size_t channel_index) const; + std::complex * GetWritePointer (std::size_t channel_index); + + [[nodiscard]] const std::complex * const * GetArrayOfReadPointer () const; + std::complex * const * GetArrayOfWritePointer (); + + [[nodiscard]] juce::dsp::AudioBlock GetContinuousBlock (); + [[nodiscard]] juce::dsp::AudioBlock GetContinuousBlock () const; + + void Clear (); + void ComplexMultiplyFrom (const ComplexBuffer & a, const ComplexBuffer & b); + void ComplexMultiplyAccumulateFrom (const ComplexBuffer & a, const ComplexBuffer & b); + void CopyFromAudioBlock (juce::dsp::AudioBlock block); + +private: + std::size_t num_channels_ = 0; + std::size_t num_points_ = 0; + std::vector> channel_data_; + std::vector *> channel_pointers_; +}; \ No newline at end of file diff --git a/zones_convolver/util/FrequencyDelayLine.cpp b/zones_convolver/util/FrequencyDelayLine.cpp new file mode 100644 index 0000000..7005aa7 --- /dev/null +++ b/zones_convolver/util/FrequencyDelayLine.cpp @@ -0,0 +1,31 @@ +#include "FrequencyDelayLine.h" + +FrequencyDelayLine::FrequencyDelayLine (std::size_t num_channels, + std::size_t num_blocks, + std::size_t num_points_per_block) +{ + num_blocks_ = num_blocks; + for (auto i = 0; i < num_blocks; ++i) + { + ComplexBuffer buffer {num_points_per_block, num_channels}; + buffer.Clear (); + delay_line_.emplace_back (std::move (buffer)); + } +} + +static inline std::size_t ReverseWrap (int a, int b) +{ + return static_cast ((b + (a % b)) % b); +} + +ComplexBuffer & FrequencyDelayLine::GetNextBlock () +{ + head_position_ = + ReverseWrap (static_cast (head_position_) - 1, static_cast (num_blocks_)); + return delay_line_ [head_position_]; +} + +const ComplexBuffer & FrequencyDelayLine::GetBlockWithOffset (std::size_t offset) const +{ + return delay_line_ [(head_position_ + offset) % num_blocks_]; +} \ No newline at end of file diff --git a/zones_convolver/util/FrequencyDelayLine.h b/zones_convolver/util/FrequencyDelayLine.h new file mode 100644 index 0000000..b04aa06 --- /dev/null +++ b/zones_convolver/util/FrequencyDelayLine.h @@ -0,0 +1,21 @@ +#pragma once + +#include "ComplexBuffer.h" + +#include + +class FrequencyDelayLine +{ +public: + FrequencyDelayLine (std::size_t num_channels, + std::size_t num_blocks, + std::size_t num_points_per_block); + + ComplexBuffer & GetNextBlock (); + [[nodiscard]] const ComplexBuffer & GetBlockWithOffset (std::size_t offset) const; + +private: + std::vector delay_line_; + std::size_t head_position_ = 0; + std::size_t num_blocks_; +}; \ No newline at end of file diff --git a/zones_convolver/util/SplitBlock.cpp b/zones_convolver/util/SplitBlock.cpp new file mode 100644 index 0000000..d52276b --- /dev/null +++ b/zones_convolver/util/SplitBlock.cpp @@ -0,0 +1,60 @@ +#include "SplitBlock.h" + +SplitBlock::SplitBlock (juce::dsp::AudioBlock first_block, + juce::dsp::AudioBlock wrapped_block) + : first_block_ (first_block) + , wrapped_block_ (wrapped_block) +{ + total_num_samples_ = first_block.getNumSamples () + wrapped_block.getNumSamples (); +} + +void SplitBlock::CopyFrom (const juce::dsp::AudioBlock block) +{ + auto num_samples = block.getNumSamples (); + auto first_samples_to_copy = std::min (num_samples, first_block_.getNumSamples ()); + auto remaining_samples_to_copy = num_samples - first_samples_to_copy; + + if (first_samples_to_copy > 0) + first_block_.copyFrom (block.getSubBlock (0, first_samples_to_copy)); + if (remaining_samples_to_copy > 0) + wrapped_block_.copyFrom ( + block.getSubBlock (first_samples_to_copy, remaining_samples_to_copy)); +} + +void SplitBlock::AddFrom (const juce::dsp::AudioBlock block) +{ + auto num_samples = block.getNumSamples (); + auto first_samples_to_copy = std::min (num_samples, first_block_.getNumSamples ()); + auto remaining_samples_to_copy = num_samples - first_samples_to_copy; + + if (first_samples_to_copy > 0) + first_block_.add (block.getSubBlock (0, first_samples_to_copy)); + if (remaining_samples_to_copy > 0) + wrapped_block_.add (block.getSubBlock (first_samples_to_copy, remaining_samples_to_copy)); +} + +void SplitBlock::CopyTo (juce::dsp::AudioBlock block) +{ + block.copyFrom (first_block_); + + auto first_num_samples = first_block_.getNumSamples (); + if (block.getNumSamples () > first_num_samples && wrapped_block_.getNumSamples () > 0) + block.getSubBlock (first_num_samples).copyFrom (wrapped_block_); +} + +void SplitBlock::Clear () +{ + first_block_.clear (); + wrapped_block_.clear (); +} + +SplitBlock SplitBlock::GetSubBlock (std::size_t num_samples) +{ + jassert (num_samples <= total_num_samples_); + + auto first_samples_to_take = std::min (num_samples, first_block_.getNumSamples ()); + auto wrapped_samples_to_take = num_samples - first_samples_to_take; + + return {first_block_.getSubBlock (0, first_samples_to_take), + wrapped_block_.getSubBlock (0, wrapped_samples_to_take)}; +} \ No newline at end of file diff --git a/zones_convolver/util/SplitBlock.h b/zones_convolver/util/SplitBlock.h new file mode 100644 index 0000000..29f2993 --- /dev/null +++ b/zones_convolver/util/SplitBlock.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class SplitBlock +{ +public: + SplitBlock (juce::dsp::AudioBlock first_block, + juce::dsp::AudioBlock wrapped_block); + + void CopyFrom (juce::dsp::AudioBlock block); + void AddFrom (juce::dsp::AudioBlock block); + + void CopyTo (juce::dsp::AudioBlock block); + void Clear (); + + SplitBlock GetSubBlock (std::size_t num_samples); + +private: + juce::dsp::AudioBlock first_block_; + juce::dsp::AudioBlock wrapped_block_; + + std::size_t total_num_samples_ = 0; +}; \ No newline at end of file diff --git a/zones_convolver/zones_convolver.cpp b/zones_convolver/zones_convolver.cpp index 69c91b1..781b591 100644 --- a/zones_convolver/zones_convolver.cpp +++ b/zones_convolver/zones_convolver.cpp @@ -1,3 +1,6 @@ #include "zones_convolver.h" -#include "ConvolverUtilities.cpp" \ No newline at end of file +#include "util/CircularBuffer.cpp" +#include "util/ComplexBuffer.cpp" +#include "util/FrequencyDelayLine.cpp" +#include "util/SplitBlock.cpp" \ No newline at end of file diff --git a/zones_convolver/zones_convolver.h b/zones_convolver/zones_convolver.h index f31ecb8..4ee5130 100644 --- a/zones_convolver/zones_convolver.h +++ b/zones_convolver/zones_convolver.h @@ -14,4 +14,7 @@ BEGIN_JUCE_MODULE_DECLARATION END_JUCE_MODULE_DECLARATION */ -#include "ConvolverUtilities.h" \ No newline at end of file +#include "util/CircularBuffer.h" +#include "util/ComplexBuffer.h" +#include "util/FrequencyDelayLine.h" +#include "util/SplitBlock.h" \ No newline at end of file