diff --git a/sfizz/AudioBuffer.h b/sfizz/AudioBuffer.h new file mode 100644 index 00000000..eef3b738 --- /dev/null +++ b/sfizz/AudioBuffer.h @@ -0,0 +1,144 @@ +// Copyright (c) 2019, Paul Ferrand +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once +#include "Buffer.h" +#include "Config.h" +#include "Debug.h" +#include "LeakDetector.h" +#include "absl/types/span.h" +#include + +template +class AudioBuffer { +public: + using value_type = std::remove_cv_t; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = pointer; + using const_iterator = const_pointer; + using size_type = size_t; + + AudioBuffer() + { + } + AudioBuffer(int numChannels, int numFrames) + : numChannels(numChannels) + , numFrames(numFrames) + { + for (auto i = 0; i < numChannels; ++i) + buffers[i] = std::make_unique(numFrames); + } + + iterator channelWriter(int channelIndex) + { + ASSERT(channelIndex < numChannels) + if (channelIndex < numChannels) + return buffers[channelIndex]->data(); + + return {}; + } + + iterator channelWriterEnd(int channelIndex) + { + ASSERT(channelIndex < numChannels) + if (channelIndex < numChannels) + return buffers[channelIndex]->end(); + + return {}; + } + + const_iterator channelReader(int channelIndex) + { + ASSERT(channelIndex < numChannels) + if (channelIndex < numChannels) + return buffers[channelIndex]->data(); + + return {}; + } + + const_iterator channelReaderEnd(int channelIndex) + { + ASSERT(channelIndex < numChannels) + if (channelIndex < numChannels) + return buffers[channelIndex]->end(); + + return {}; + } + + absl::Span getSpan(int channelIndex) + { + ASSERT(channelIndex < numChannels) + if (channelIndex < numChannels) + return { buffers[channelIndex]->data(), buffers[channelIndex]->size() }; + + return {}; + } + + absl::Span getConstSpan(int channelIndex) + { + return getSpan(channelIndex); + } + + void addChannel() + { + if (numChannels < MaxChannels) + buffers[numChannels++] = std::make_unique(numFrames); + } + + size_type getNumFrames() + { + return numFrames; + } + + size_type getNumChannels() + { + return numChannels; + } + + bool empty() + { + return numFrames == 0; + } + + Type& getSample(int channelIndex, size_type frameIndex) + { + // Uhoh + ASSERT(buffers[channelIndex] != nullptr); + ASSERT(frameIndex < numFrames); + + return *(buffers[channelIndex]->data() + frameIndex); + } + + Type& operator()(int channelIndex, size_type frameIndex) + { + return getSample(channelIndex, frameIndex); + } + +private: + using buffer_type = Buffer; + using buffer_ptr = std::unique_ptr; + std::array buffers; + int numChannels { 0 }; + size_type numFrames { 0 }; +}; \ No newline at end of file diff --git a/sfizz/AudioSpan.h b/sfizz/AudioSpan.h new file mode 100644 index 00000000..4cd6bb71 --- /dev/null +++ b/sfizz/AudioSpan.h @@ -0,0 +1,169 @@ +// Copyright (c) 2019, Paul Ferrand +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once +#include "AudioBuffer.h" +#include "Buffer.h" +#include "Config.h" +#include "Debug.h" +#include "LeakDetector.h" +#include "SIMDHelpers.h" +#include +#include + +template +class AudioSpan { +public: + using size_type = size_t; + AudioSpan() + { + } + + AudioSpan(std::initializer_list spans, size_type numFrames) + : numFrames(numFrames) + , numChannels(spans.size()) + { + static_assert(spans.size() <= MaxChannels); + for (auto i = 0; i < spans.size(); i++) { + // This will not end well... + ASSERT(spans[i] != nullptr); + this->spans[i] = spans[i]; + } + } + + AudioSpan(std::initializer_list> spans) + : numChannels(spans.size()) + { + static_assert(spans.size() <= MaxChannels); + auto size = absl::Span::npos; + for (auto i = 0; i < spans.size(); i++) { + // This will not end well... + ASSERT(spans[i] != nullptr); + this->spans[i] = spans[i].data(); + size = std::min(size, spans[i].size()); + } + } + + template > + AudioSpan(const AudioBuffer& audioBuffer) + : numFrames(audioBuffer.getNumFrames()) + , numChannels(audioBuffer.getNumChannels()) + { + for (auto i = 0; i < N; i++) { + if constexpr (std::is_const::value) + this->spans[i] = audioBuffer.channelReader(i); + else + this->spans[i] = audioBuffer.channelWriter(i); + } + } + + template > + AudioSpan(const AudioSpan& other) + : numFrames(other.getNumFrames()) + , numChannels(other.getNumChannels()) + { + for (auto i = 0; i < N; i++) { + this->spans[i] = other.getChannel(i); + } + } + + Type* getChannel(int channelIndex) + { + ASSERT(channelIndex < numChannels); + if (channelIndex < numChannels) + return spans[channelIndex]; + + return {}; + } + + absl::Span getSpan(int channelIndex) + { + ASSERT(channelIndex < numChannels); + if (channelIndex < numChannels) + return { spans[channelIndex], numFrames }; + + return {}; + } + + absl::Span> getConstSpan(int channelIndex) + { + ASSERT(channelIndex < numChannels); + if (channelIndex < numChannels) + return { spans[channelIndex], numFrames }; + + return {}; + } + + void fill(Type value) noexcept + { + for (int i = 0; i < numChannels; ++i) + ::fill({ getChannel(i), numFrames }, value); + } + + void applyGain(absl::Span gain) noexcept + { + for (int i = 0; i < numChannels; ++i) + ::applyGain({ getChannel(i), numFrames }, gain); + } + + void applyGain(Type gain) noexcept + { + for (int i = 0; i < numChannels; ++i) + ::applyGain({ getChannel(i), numFrames }, gain); + } + + template > + void add(const AudioSpan& other) + { + ASSERT(other.getNumChannels() == numChannels); + if (other.getNumChannels() == numChannels) { + for (int i = 0; i < numChannels; ++i) + ::add(other.getConstSpan(i), getSpan(i)); + } + } + + template > + void copy(const AudioSpan& other) + { + ASSERT(other.getNumChannels() == numChannels); + if (other.getNumChannels() == numChannels) { + for (int i = 0; i < numChannels; ++i) + ::copy(other.getConstSpan(i), getSpan(i)); + } + } + + size_type getNumFrames() + { + return numFrames; + } + + int getNumChannels() + { + return numChannels; + } + +private: + std::array spans; + size_type numFrames { 0 }; + int numChannels { 0 }; +}; \ No newline at end of file diff --git a/tests/AudioBufferT.cpp b/tests/AudioBufferT.cpp new file mode 100644 index 00000000..87ee67fd --- /dev/null +++ b/tests/AudioBufferT.cpp @@ -0,0 +1,102 @@ +// Copyright (c) 2019, Paul Ferrand +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "AudioBuffer.h" +#include "AudioSpan.h" +#include "catch2/catch.hpp" +#include +using namespace Catch::literals; + +TEST_CASE("[AudioBuffer] Empty buffers") +{ + AudioBuffer floatBuffer; + REQUIRE(floatBuffer.empty()); + REQUIRE(floatBuffer.getNumFrames() == 0); + AudioBuffer doubleBuffer; + REQUIRE(doubleBuffer.empty()); + REQUIRE(doubleBuffer.getNumFrames() == 0); + AudioBuffer intBuffer; + REQUIRE(intBuffer.empty()); + REQUIRE(intBuffer.getNumFrames() == 0); +} + +TEST_CASE("[AudioBuffer] Non-empty") +{ + AudioBuffer floatBuffer(1, 10); + REQUIRE(!floatBuffer.empty()); + REQUIRE(floatBuffer.getNumFrames() == 10); + REQUIRE(floatBuffer.getNumChannels() == 1); + AudioBuffer doubleBuffer(2, 10); + REQUIRE(!doubleBuffer.empty()); + REQUIRE(doubleBuffer.getNumFrames() == 10); + REQUIRE(doubleBuffer.getNumChannels() == 2); + AudioBuffer intBuffer(1, 10); + REQUIRE(!intBuffer.empty()); + REQUIRE(intBuffer.getNumFrames() == 10); + REQUIRE(intBuffer.getNumChannels() == 1); +} + +TEST_CASE("[AudioBuffer] Access") +{ + const int size { 5 }; + AudioBuffer buffer(2, size); + for (size_t frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) { + buffer.getSample(0, frameIdx) = static_cast(buffer.getNumFrames()) + frameIdx; + buffer.getSample(1, frameIdx) = static_cast(buffer.getNumFrames()) - frameIdx; + } + + for (size_t frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) { + REQUIRE(buffer.getSample(0, frameIdx) == static_cast(buffer.getNumFrames()) + frameIdx); + REQUIRE(buffer(0, frameIdx) == static_cast(buffer.getNumFrames()) + frameIdx); + REQUIRE(buffer.getSample(1, frameIdx) == static_cast(buffer.getNumFrames()) - frameIdx); + REQUIRE(buffer(1, frameIdx) == static_cast(buffer.getNumFrames()) - frameIdx); + } +} + +TEST_CASE("[AudioBuffer] Iterators") +{ + const int size { 256 }; + const float fillValue { 2.0f }; + AudioBuffer buffer(2, size); + std::fill(buffer.channelWriter(0), buffer.channelWriterEnd(0), fillValue); + std::fill(buffer.channelWriter(1), buffer.channelWriterEnd(1), fillValue); + + REQUIRE(std::all_of(buffer.channelReader(0), buffer.channelReaderEnd(0), [fillValue](auto value) { return value == fillValue; })); + REQUIRE(std::all_of(buffer.channelReader(1), buffer.channelReaderEnd(1), [fillValue](auto value) { return value == fillValue; })); +} + +TEST_CASE("[AudioSpan] Constructions") +{ + const int size { 256 }; + const float fillValue { 2.0f }; + AudioBuffer buffer(2, size); + std::fill(buffer.channelWriter(0), buffer.channelWriterEnd(0), fillValue); + std::fill(buffer.channelWriter(1), buffer.channelWriterEnd(1), fillValue); + + AudioSpan span { buffer }; + AudioSpan constSpan { buffer }; + AudioSpan manualSpan { { buffer.channelWriter(0), buffer.channelWriter(1) }, buffer.getNumFrames() }; + AudioSpan manualConstSpan { { buffer.channelReader(0), buffer.channelReader(1) }, buffer.getNumFrames() }; + AudioSpan manualSpan2 { {buffer.getSpan(0), buffer.getSpan(1) } }; + AudioSpan manualConstSpan2 { {buffer.getConstSpan(0), buffer.getConstSpan(1) } }; +} \ No newline at end of file