Added a more generic AudioSpan and AudioBuffer
This commit is contained in:
parent
2f717c2a76
commit
1e03e50747
3 changed files with 415 additions and 0 deletions
144
sfizz/AudioBuffer.h
Normal file
144
sfizz/AudioBuffer.h
Normal file
|
|
@ -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 <memory>
|
||||
|
||||
template <class Type, unsigned int MaxChannels = sfz::config::numChannels, unsigned int Alignment = SIMDConfig::defaultAlignment>
|
||||
class AudioBuffer {
|
||||
public:
|
||||
using value_type = std::remove_cv_t<Type>;
|
||||
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<buffer_type>(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<value_type> getSpan(int channelIndex)
|
||||
{
|
||||
ASSERT(channelIndex < numChannels)
|
||||
if (channelIndex < numChannels)
|
||||
return { buffers[channelIndex]->data(), buffers[channelIndex]->size() };
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
absl::Span<const value_type> getConstSpan(int channelIndex)
|
||||
{
|
||||
return getSpan(channelIndex);
|
||||
}
|
||||
|
||||
void addChannel()
|
||||
{
|
||||
if (numChannels < MaxChannels)
|
||||
buffers[numChannels++] = std::make_unique<buffer_type>(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<Type, Alignment>;
|
||||
using buffer_ptr = std::unique_ptr<buffer_type>;
|
||||
std::array<buffer_ptr, MaxChannels> buffers;
|
||||
int numChannels { 0 };
|
||||
size_type numFrames { 0 };
|
||||
};
|
||||
169
sfizz/AudioSpan.h
Normal file
169
sfizz/AudioSpan.h
Normal file
|
|
@ -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 <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
template <class Type, unsigned int MaxChannels = sfz::config::numChannels>
|
||||
class AudioSpan {
|
||||
public:
|
||||
using size_type = size_t;
|
||||
AudioSpan()
|
||||
{
|
||||
}
|
||||
|
||||
AudioSpan(std::initializer_list<Type*> 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<absl::Span<Type>> spans)
|
||||
: numChannels(spans.size())
|
||||
{
|
||||
static_assert(spans.size() <= MaxChannels);
|
||||
auto size = absl::Span<Type>::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 <class U, unsigned int N, unsigned int Alignment, typename = std::enable_if<N <= MaxChannels>>
|
||||
AudioSpan(const AudioBuffer<U, N, Alignment>& audioBuffer)
|
||||
: numFrames(audioBuffer.getNumFrames())
|
||||
, numChannels(audioBuffer.getNumChannels())
|
||||
{
|
||||
for (auto i = 0; i < N; i++) {
|
||||
if constexpr (std::is_const<Type>::value)
|
||||
this->spans[i] = audioBuffer.channelReader(i);
|
||||
else
|
||||
this->spans[i] = audioBuffer.channelWriter(i);
|
||||
}
|
||||
}
|
||||
|
||||
template <class U, unsigned int N, typename = std::enable_if<N <= MaxChannels>>
|
||||
AudioSpan(const AudioSpan<U, N>& 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<Type> getSpan(int channelIndex)
|
||||
{
|
||||
ASSERT(channelIndex < numChannels);
|
||||
if (channelIndex < numChannels)
|
||||
return { spans[channelIndex], numFrames };
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
absl::Span<const std::remove_cv<Type>> 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<Type>({ getChannel(i), numFrames }, value);
|
||||
}
|
||||
|
||||
void applyGain(absl::Span<const Type> gain) noexcept
|
||||
{
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
::applyGain<Type>({ getChannel(i), numFrames }, gain);
|
||||
}
|
||||
|
||||
void applyGain(Type gain) noexcept
|
||||
{
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
::applyGain<Type>({ getChannel(i), numFrames }, gain);
|
||||
}
|
||||
|
||||
template <class U, unsigned int N, typename = std::enable_if<N <= MaxChannels>>
|
||||
void add(const AudioSpan<U, N>& other)
|
||||
{
|
||||
ASSERT(other.getNumChannels() == numChannels);
|
||||
if (other.getNumChannels() == numChannels) {
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
::add<Type>(other.getConstSpan(i), getSpan(i));
|
||||
}
|
||||
}
|
||||
|
||||
template <class U, unsigned int N, typename = std::enable_if<N <= MaxChannels>>
|
||||
void copy(const AudioSpan<U, N>& other)
|
||||
{
|
||||
ASSERT(other.getNumChannels() == numChannels);
|
||||
if (other.getNumChannels() == numChannels) {
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
::copy<Type>(other.getConstSpan(i), getSpan(i));
|
||||
}
|
||||
}
|
||||
|
||||
size_type getNumFrames()
|
||||
{
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
int getNumChannels()
|
||||
{
|
||||
return numChannels;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<Type*, MaxChannels> spans;
|
||||
size_type numFrames { 0 };
|
||||
int numChannels { 0 };
|
||||
};
|
||||
102
tests/AudioBufferT.cpp
Normal file
102
tests/AudioBufferT.cpp
Normal file
|
|
@ -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 <algorithm>
|
||||
using namespace Catch::literals;
|
||||
|
||||
TEST_CASE("[AudioBuffer] Empty buffers")
|
||||
{
|
||||
AudioBuffer<float> floatBuffer;
|
||||
REQUIRE(floatBuffer.empty());
|
||||
REQUIRE(floatBuffer.getNumFrames() == 0);
|
||||
AudioBuffer<double> doubleBuffer;
|
||||
REQUIRE(doubleBuffer.empty());
|
||||
REQUIRE(doubleBuffer.getNumFrames() == 0);
|
||||
AudioBuffer<int> intBuffer;
|
||||
REQUIRE(intBuffer.empty());
|
||||
REQUIRE(intBuffer.getNumFrames() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("[AudioBuffer] Non-empty")
|
||||
{
|
||||
AudioBuffer<float> floatBuffer(1, 10);
|
||||
REQUIRE(!floatBuffer.empty());
|
||||
REQUIRE(floatBuffer.getNumFrames() == 10);
|
||||
REQUIRE(floatBuffer.getNumChannels() == 1);
|
||||
AudioBuffer<double> doubleBuffer(2, 10);
|
||||
REQUIRE(!doubleBuffer.empty());
|
||||
REQUIRE(doubleBuffer.getNumFrames() == 10);
|
||||
REQUIRE(doubleBuffer.getNumChannels() == 2);
|
||||
AudioBuffer<int> intBuffer(1, 10);
|
||||
REQUIRE(!intBuffer.empty());
|
||||
REQUIRE(intBuffer.getNumFrames() == 10);
|
||||
REQUIRE(intBuffer.getNumChannels() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("[AudioBuffer] Access")
|
||||
{
|
||||
const int size { 5 };
|
||||
AudioBuffer<float> buffer(2, size);
|
||||
for (size_t frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) {
|
||||
buffer.getSample(0, frameIdx) = static_cast<double>(buffer.getNumFrames()) + frameIdx;
|
||||
buffer.getSample(1, frameIdx) = static_cast<double>(buffer.getNumFrames()) - frameIdx;
|
||||
}
|
||||
|
||||
for (size_t frameIdx = 0; frameIdx < buffer.getNumFrames(); ++frameIdx) {
|
||||
REQUIRE(buffer.getSample(0, frameIdx) == static_cast<double>(buffer.getNumFrames()) + frameIdx);
|
||||
REQUIRE(buffer(0, frameIdx) == static_cast<double>(buffer.getNumFrames()) + frameIdx);
|
||||
REQUIRE(buffer.getSample(1, frameIdx) == static_cast<double>(buffer.getNumFrames()) - frameIdx);
|
||||
REQUIRE(buffer(1, frameIdx) == static_cast<double>(buffer.getNumFrames()) - frameIdx);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[AudioBuffer] Iterators")
|
||||
{
|
||||
const int size { 256 };
|
||||
const float fillValue { 2.0f };
|
||||
AudioBuffer<float> 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<float> buffer(2, size);
|
||||
std::fill(buffer.channelWriter(0), buffer.channelWriterEnd(0), fillValue);
|
||||
std::fill(buffer.channelWriter(1), buffer.channelWriterEnd(1), fillValue);
|
||||
|
||||
AudioSpan<float> span { buffer };
|
||||
AudioSpan<const float> constSpan { buffer };
|
||||
AudioSpan<float> manualSpan { { buffer.channelWriter(0), buffer.channelWriter(1) }, buffer.getNumFrames() };
|
||||
AudioSpan<const float> manualConstSpan { { buffer.channelReader(0), buffer.channelReader(1) }, buffer.getNumFrames() };
|
||||
AudioSpan<float> manualSpan2 { {buffer.getSpan(0), buffer.getSpan(1) } };
|
||||
AudioSpan<const float> manualConstSpan2 { {buffer.getConstSpan(0), buffer.getConstSpan(1) } };
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue