From 7922c98c7f01dfdc0feaa73653f66a0a04cd3a42 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 30 Jul 2019 00:29:23 +0200 Subject: [PATCH] Included buffer and prelim work for the FilePool --- CMakeLists.txt | 3 ++ sources/AudioBuffer.h | 116 ++++++++++++++++++++++++++++++++++++++++++ sources/Buffer.h | 56 +++++++++++++++++--- sources/FilePool.cpp | 1 + sources/FilePool.h | 17 +++++++ sources/Globals.h | 1 + sources/Main.cpp | 5 ++ sources/Region.cpp | 40 +++++++++++++-- sources/Region.h | 16 ++++-- sources/Synth.cpp | 21 ++++---- sources/Synth.h | 4 +- tests/AudioBuffer.cpp | 94 ++++++++++++++++++++++++++++++++++ tests/Buffer.cpp | 107 ++++++++++++++++++++++++++++++++++++++ 13 files changed, 459 insertions(+), 22 deletions(-) create mode 100644 sources/AudioBuffer.h create mode 100644 sources/FilePool.cpp create mode 100644 sources/FilePool.h create mode 100644 tests/AudioBuffer.cpp create mode 100644 tests/Buffer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d8289d4..9ae38de6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(COMMON_SOURCES sources/Opcode.cpp sources/Synth.cpp + sources/FilePool.cpp sources/Region.cpp sources/Parser.cpp ) @@ -39,6 +40,8 @@ set(TEST_SOURCES tests/Region.cpp tests/Range.cpp tests/Opcode.cpp + tests/Buffer.cpp + tests/AudioBuffer.cpp tests/Files.cpp tests/Main.cpp ) diff --git a/sources/AudioBuffer.h b/sources/AudioBuffer.h new file mode 100644 index 00000000..e10ca082 --- /dev/null +++ b/sources/AudioBuffer.h @@ -0,0 +1,116 @@ +#pragma once +#include "Buffer.h" +#include "Helpers.h" +#include "Globals.h" + +template + +class AudioBuffer +{ +public: + AudioBuffer() = default; + + AudioBuffer(int numFrames) + { + resize(numFrames); + } + + bool resize(int numFrames) + { + // should have a positive number of frames... + ASSERT(numFrames >= 0); + if (buffer.resize(static_cast(NumChannels * numFrames))) + { + this->numFrames = numFrames; + return true; + } + + return false; + } + + Type& getSample(int channelIndex, int sampleIndex) noexcept + { + ASSERT(channelIndex >= 0); + ASSERT(sampleIndex >= 0); + return *(buffer.data() + numFrames * channelIndex + sampleIndex); + } + + Type* getChannel(int channelIndex) noexcept + { + ASSERT(channelIndex >= 0); + if (channelIndex < NumChannels) + return buffer.data() + numFrames * channelIndex; + else + return nullptr; + } + + Type& operator()(int channelIndex, int sampleIndex) noexcept + { + return getSample(channelIndex, sampleIndex); + } + + int getNumFrames() const noexcept { return numFrames; } + int getNumChannels() const noexcept { return NumChannels; } + bool empty() const noexcept { return numFrames == 0; } + +private: + int numFrames { 0 }; + Buffer buffer {}; +}; + +template +class SplitAudioBuffer +{ +public: + SplitAudioBuffer() = default; + SplitAudioBuffer(int numFrames) + { + resize(numFrames); + } + + bool resize(int numFrames) + { + // should have a positive number of frames... + ASSERT(numFrames >= 0); + bool resizedOK = true; + + for (auto& buffer: buffers) + resizedOK &= buffer.resize(static_cast(numFrames)); + + if (resizedOK) + this->numFrames = numFrames; + else + this->numFrames = std::min(numFrames, this->numFrames); + + return resizedOK; + } + + Type& getSample(int channelIndex, int sampleIndex) noexcept + { + ASSERT(channelIndex >= 0); + ASSERT(sampleIndex >= 0); + return *(buffers[channelIndex].data() + sampleIndex); + } + + Type& operator()(int channelIndex, int sampleIndex) noexcept + { + return getSample(channelIndex, sampleIndex); + } + + Type* getChannel(int channelIndex) noexcept + { + ASSERT(channelIndex >= 0); + if (channelIndex < NumChannels) + return &buffers[channelIndex].data(); + else + return nullptr; + } + + int getNumFrames() const noexcept { return numFrames; } + int getNumChannels() const noexcept { return NumChannels; } + bool empty() const noexcept { return numFrames == 0; } + +private: + int numFrames { 0 }; + std::array, NumChannels> buffers; +}; \ No newline at end of file diff --git a/sources/Buffer.h b/sources/Buffer.h index 997deb15..b20d0ab0 100644 --- a/sources/Buffer.h +++ b/sources/Buffer.h @@ -1,20 +1,64 @@ #pragma once +#include "Globals.h" #include -template +#include +#include + +template class Buffer { static_assert(std::is_arithmetic::value, "Type should be arithmetic"); +static_assert(Alignment == 0 || Alignment == 4 || Alignment == 8 || Alignment == 16, "Bad alignment value"); public: - Buffer() + Buffer() { } + Buffer(size_t size) { + resize(size); + } + bool resize(size_t newSize) + { + if (newSize == 0) + { + clear(); + return true; + } + auto tempSize = newSize + Alignment; + auto* newData = largerData != nullptr ? std::realloc(largerData, tempSize * sizeof(Type)) : std::malloc(tempSize * sizeof(Type)); + if (newData == nullptr) + return false; + + largerSize = tempSize; + alignedSize = newSize; + largerData = static_cast(newData); + alignedData = static_cast(std::align(Alignment, alignedSize, newData, tempSize)); + return true; + } + + Type* data() { return alignedData; } + void clear() + { + std::free(largerData); + largerSize = 0; + alignedSize = 0; + alignedData = nullptr; } ~Buffer() { - + std::free(largerData); } + + Type& operator[](int idx) { return *(alignedData + idx); } + size_t size() const noexcept { return alignedSize; } + bool empty() const noexcept { return alignedSize == 0; } + + Type* begin() noexcept { return data(); } + Type* end() noexcept { return data() + alignedSize; } + const Type* cbegin() const noexcept { return data(); } + const Type* cend() const noexcept { return data() + alignedSize; } private: - size_t realSize; - size_t alignment; - Type* data; + size_t largerSize { 0 }; + size_t alignedSize { 0 }; + Type* largerData { nullptr }; + Type* alignedData { nullptr }; }; \ No newline at end of file diff --git a/sources/FilePool.cpp b/sources/FilePool.cpp new file mode 100644 index 00000000..b62b2cb7 --- /dev/null +++ b/sources/FilePool.cpp @@ -0,0 +1 @@ +#include "FilePool.h" \ No newline at end of file diff --git a/sources/FilePool.h b/sources/FilePool.h new file mode 100644 index 00000000..f2fe7a35 --- /dev/null +++ b/sources/FilePool.h @@ -0,0 +1,17 @@ +#pragma once +#include "AudioBuffer.h" +#include +#include +#include + +namespace sfz +{ +class FilePool +{ +public: + FilePool() = default; +private: + std::filesystem::path rootDirectory; + std::map>> preloadedData; +}; +} \ No newline at end of file diff --git a/sources/Globals.h b/sources/Globals.h index 8aaa7d84..91a8411e 100644 --- a/sources/Globals.h +++ b/sources/Globals.h @@ -16,6 +16,7 @@ namespace Config inline constexpr double fastReleaseDuration { 0.01 }; inline constexpr char defineCharacter { '$' }; inline constexpr int oversamplingFactor { 2 }; + inline constexpr unsigned int defaultAlignment { 16 }; } // namespace config } // namespace sfz \ No newline at end of file diff --git a/sources/Main.cpp b/sources/Main.cpp index 809871cb..1d66c9ec 100644 --- a/sources/Main.cpp +++ b/sources/Main.cpp @@ -33,5 +33,10 @@ int main(int argc, char** argv) std::cout << "Defines:" << '\n'; for (auto& define: synth.getDefines()) std::cout << '\t' << define.first << '=' << define.second << '\n'; + std::cout << "==========" << '\n'; + std::cout << "Unknown opcodes:"; + for (auto& opcode: synth.getUnknownOpcodes()) + std::cout << opcode << ','; + std::cout << '\n'; return 0; } \ No newline at end of file diff --git a/sources/Region.cpp b/sources/Region.cpp index 5723d19e..7ff88840 100644 --- a/sources/Region.cpp +++ b/sources/Region.cpp @@ -2,7 +2,7 @@ #include "Helpers.h" #include "absl/strings/str_replace.h" -void sfz::Region::parseOpcode(const Opcode& opcode) +bool sfz::Region::parseOpcode(const Opcode& opcode) { switch (hash(opcode.opcode)) { @@ -237,7 +237,41 @@ void sfz::Region::parseOpcode(const Opcode& opcode) case hash("ampeg_vel2depth"): break; default: - std::string opcodeStr { opcode.opcode.begin(), opcode.opcode.end() }; - unknownOpcodes.push_back(opcodeStr); + return false; } + + return true; +} + +bool sfz::Region::prepare() +{ + return false; +} +bool sfz::Region::isSwitchedOn() const noexcept +{ + return false; +} +bool sfz::Region::registerNoteOn(int channel, int noteNumber, uint8_t velocity, float randValue) +{ + return false; +} +bool sfz::Region::registerNoteOff(int channel, int noteNumber, uint8_t velocity, float randValue) +{ + return false; +} +bool sfz::Region::registerCC(int channel, int ccNumber, uint8_t ccValue) +{ + return false; +} +void sfz::Region::registerPitchWheel(int channel, int pitch) +{ + +} +void sfz::Region::registerAftertouch(int channel, uint8_t aftertouch) +{ + +} +void sfz::Region::registerTempo(float secondsPerQuarter) +{ + } \ No newline at end of file diff --git a/sources/Region.h b/sources/Region.h index 0301a861..a718103b 100644 --- a/sources/Region.h +++ b/sources/Region.h @@ -10,7 +10,19 @@ namespace sfz { struct Region { - void parseOpcode(const Opcode& opcode); + bool isRelease() const noexcept { return trigger == SfzTrigger::release || trigger == SfzTrigger::release_key; } + bool isGenerator() const noexcept { return sample.size() > 0 ? sample[0] == '*' : false; } + bool shouldLoop() const noexcept { return (loopMode == SfzLoopMode::loop_continuous || loopMode == SfzLoopMode::loop_sustain); } + bool isSwitchedOn() const noexcept; + bool registerNoteOn(int channel, int noteNumber, uint8_t velocity, float randValue); + bool registerNoteOff(int channel, int noteNumber, uint8_t velocity, float randValue); + bool registerCC(int channel, int ccNumber, uint8_t ccValue); + void registerPitchWheel(int channel, int pitch); + void registerAftertouch(int channel, uint8_t aftertouch); + void registerTempo(float secondsPerQuarter); + bool prepare(); + bool isStereo() const noexcept; + bool parseOpcode(const Opcode& opcode); // Sound source: sample playback std::string sample {}; // Sample float delay { Default::delay }; // delay @@ -93,7 +105,5 @@ struct Region double sampleRate { Config::defaultSampleRate }; int numChannels { 1 }; - - std::vector unknownOpcodes; }; } // namespace sfz \ No newline at end of file diff --git a/sources/Synth.cpp b/sources/Synth.cpp index ca44f748..1691981c 100644 --- a/sources/Synth.cpp +++ b/sources/Synth.cpp @@ -43,14 +43,17 @@ void sfz::Synth::callback(std::string_view header, std::vector members) void sfz::Synth::buildRegion(const std::vector& regionOpcodes) { auto& lastRegion = regions.emplace_back(); - for (auto& opcode: globalOpcodes) - lastRegion.parseOpcode(opcode); - for (auto& opcode: masterOpcodes) - lastRegion.parseOpcode(opcode); - for (auto& opcode: groupOpcodes) - lastRegion.parseOpcode(opcode); - for (auto& opcode: regionOpcodes) - lastRegion.parseOpcode(opcode); + + auto parseOpcodes = [&](const auto& opcodes) { + for (auto& opcode: opcodes) + if (!lastRegion.parseOpcode(opcode)) + unknownOpcodes.insert(opcode.opcode); + }; + + parseOpcodes(globalOpcodes); + parseOpcodes(masterOpcodes); + parseOpcodes(groupOpcodes); + parseOpcodes(regionOpcodes); } void sfz::Synth::clear() @@ -107,4 +110,4 @@ bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename) { clear(); return sfz::Parser::loadSfzFile(filename); -} \ No newline at end of file +} diff --git a/sources/Synth.h b/sources/Synth.h index c519ad90..35759467 100644 --- a/sources/Synth.h +++ b/sources/Synth.h @@ -3,6 +3,7 @@ #include "Region.h" #include "SfzHelpers.h" #include +#include #include #include @@ -18,6 +19,7 @@ public: int getNumMasters() const noexcept { return numMasters; } int getNumCurves() const noexcept { return numCurves; } const Region* getRegionView(int idx) const noexcept { return idx < regions.size() ? ®ions[idx] : nullptr; } + auto getUnknownOpcodes() { return unknownOpcodes; } protected: void callback(std::string_view header, std::vector members) final; private: @@ -36,7 +38,7 @@ private: CCValueArray ccState; std::vector ccNames; std::optional defaultSwitch; - + std::set unknownOpcodes; std::vector regions; void buildRegion(const std::vector& regionOpcodes); }; diff --git a/tests/AudioBuffer.cpp b/tests/AudioBuffer.cpp new file mode 100644 index 00000000..21d0e894 --- /dev/null +++ b/tests/AudioBuffer.cpp @@ -0,0 +1,94 @@ +#include "catch2/catch.hpp" +#include "../sources/AudioBuffer.h" +#include +using namespace Catch::literals; + +TEST_CASE("[AudioBuffer/SplitBuffer] 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); + + SplitAudioBuffer floatSplitBuffer; + REQUIRE(floatSplitBuffer.empty()); + REQUIRE(floatSplitBuffer.getNumFrames() == 0); + SplitAudioBuffer doubleSplitBuffer; + REQUIRE(doubleSplitBuffer.empty()); + REQUIRE(doubleSplitBuffer.getNumFrames() == 0); + SplitAudioBuffer intSplitBuffer; + REQUIRE(intSplitBuffer.empty()); + REQUIRE(intSplitBuffer.getNumFrames() == 0); +} + +TEST_CASE("[AudioBuffer/SplitBuffer] Non-empty") +{ + AudioBuffer floatBuffer(10); + REQUIRE(!floatBuffer.empty()); + REQUIRE(floatBuffer.getNumFrames() == 10); + AudioBuffer doubleBuffer(10); + REQUIRE(!doubleBuffer.empty()); + REQUIRE(doubleBuffer.getNumFrames() == 10); + AudioBuffer intBuffer(10); + REQUIRE(!intBuffer.empty()); + REQUIRE(intBuffer.getNumFrames() == 10); + + SplitAudioBuffer floatSplitBuffer(10); + REQUIRE(!floatSplitBuffer.empty()); + REQUIRE(floatSplitBuffer.getNumFrames() == 10); + SplitAudioBuffer doubleSplitBuffer(10); + REQUIRE(!doubleSplitBuffer.empty()); + REQUIRE(doubleSplitBuffer.getNumFrames() == 10); + SplitAudioBuffer intSplitBuffer(10); + REQUIRE(!intSplitBuffer.empty()); + REQUIRE(intSplitBuffer.getNumFrames() == 10); +} + +TEST_CASE("[AudioBuffer/SplitBuffer] Access") +{ + const int size { 5 }; + AudioBuffer doubleBuffer(size); + for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx) + for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx) + doubleBuffer.getSample(chanIdx, frameIdx) = static_cast(doubleBuffer.getNumFrames()) * chanIdx + frameIdx; + + for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx) + for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx) + REQUIRE(doubleBuffer.getSample(chanIdx, frameIdx) == static_cast(doubleBuffer.getNumFrames()) * chanIdx + frameIdx); + + SplitAudioBuffer splitDoubleBuffer(size); + for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx) + for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx) + splitDoubleBuffer.getSample(chanIdx, frameIdx) = static_cast(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx; + + for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx) + for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx) + REQUIRE(splitDoubleBuffer.getSample(chanIdx, frameIdx) == static_cast(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx); +} + +TEST_CASE("[AudioBuffer/SplitBuffer] Access 2") +{ + const int size { 5 }; + AudioBuffer doubleBuffer(size); + for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx) + for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx) + doubleBuffer(chanIdx, frameIdx) = static_cast(doubleBuffer.getNumFrames()) * chanIdx + frameIdx; + + for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx) + for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx) + REQUIRE(doubleBuffer(chanIdx, frameIdx) == static_cast(doubleBuffer.getNumFrames()) * chanIdx + frameIdx); + + SplitAudioBuffer splitDoubleBuffer(size); + for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx) + for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx) + splitDoubleBuffer(chanIdx, frameIdx) = static_cast(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx; + + for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx) + for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx) + REQUIRE(splitDoubleBuffer(chanIdx, frameIdx) == static_cast(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx); +} \ No newline at end of file diff --git a/tests/Buffer.cpp b/tests/Buffer.cpp new file mode 100644 index 00000000..18aa77fd --- /dev/null +++ b/tests/Buffer.cpp @@ -0,0 +1,107 @@ +#include "catch2/catch.hpp" +#include "../sources/Buffer.h" +#include +using namespace Catch::literals; + +TEST_CASE("[Buffer] Empty (float)") +{ + Buffer emptyBuffer; + REQUIRE(emptyBuffer.empty()); + REQUIRE(emptyBuffer.size() == 0); +} + +TEST_CASE("[Buffer] Empty (int)") +{ + Buffer emptyBuffer; + REQUIRE(emptyBuffer.empty()); + REQUIRE(emptyBuffer.size() == 0); +} + +TEST_CASE("[Buffer] Empty (double)") +{ + Buffer emptyBuffer; + REQUIRE(emptyBuffer.empty()); + REQUIRE(emptyBuffer.size() == 0); +} + +TEST_CASE("[Buffer] Empty (uint8_t)") +{ + Buffer emptyBuffer; + REQUIRE(emptyBuffer.empty()); + REQUIRE(emptyBuffer.size() == 0); +} + +TEST_CASE("[Buffer] 10 floats ") +{ + Buffer buffer(10); + REQUIRE(!buffer.empty()); + REQUIRE(buffer.size() == 10); + REQUIRE(((size_t)buffer.data() & (sfz::Config::defaultAlignment - 1)) == 0); + for (auto& element: buffer) + element = 0.0f; + for (auto& element: buffer) + REQUIRE(element == 0.0f); +} + +TEST_CASE("[Buffer] Resize 10 floats ") +{ + const int baseSize { 10 }; + const int smallSize { baseSize / 2 }; + const int bigSize { baseSize * 2 }; + Buffer buffer(baseSize); + REQUIRE(!buffer.empty()); + REQUIRE(buffer.size() == baseSize); + REQUIRE(((size_t)buffer.data() & (sfz::Config::defaultAlignment - 1)) == 0); + std::fill(buffer.begin(), buffer.end(), 1.0f); + REQUIRE( buffer.resize(smallSize) ); + REQUIRE( buffer.size() == smallSize ); + REQUIRE(((size_t)buffer.data() & (sfz::Config::defaultAlignment - 1)) == 0); + REQUIRE( std::all_of(buffer.begin(), buffer.end(), [](auto value) { return value == 1.0f; }) ); + REQUIRE( buffer.resize(bigSize) ); + REQUIRE( buffer.size() == bigSize ); + REQUIRE(((size_t)buffer.data() & (sfz::Config::defaultAlignment - 1)) == 0); + for (auto i = 0; i < smallSize; ++i) + REQUIRE(buffer[i] == 1.0f); +} + +TEST_CASE("[Buffer] Resize 4096 floats ") +{ + const int baseSize { 10 }; + const int smallSize { baseSize / 2 }; + const int bigSize { baseSize * 2 }; + Buffer buffer(baseSize); + REQUIRE(!buffer.empty()); + REQUIRE(buffer.size() == baseSize); + REQUIRE(((size_t)buffer.data() & (sfz::Config::defaultAlignment - 1)) == 0); + std::fill(buffer.begin(), buffer.end(), 1.0f); + REQUIRE( buffer.resize(smallSize) ); + REQUIRE( buffer.size() == smallSize ); + REQUIRE(((size_t)buffer.data() & (sfz::Config::defaultAlignment - 1)) == 0); + REQUIRE( std::all_of(buffer.begin(), buffer.end(), [](auto value) { return value == 1.0f; }) ); + REQUIRE( buffer.resize(bigSize) ); + REQUIRE( buffer.size() == bigSize ); + REQUIRE(((size_t)buffer.data() & (sfz::Config::defaultAlignment - 1)) == 0); + for (auto i = 0; i < smallSize; ++i) + REQUIRE(buffer[i] == 1.0f); +} + +TEST_CASE("[Buffer] Resize 65536 floats ") +{ + const int baseSize { 10 }; + const int smallSize { baseSize / 2 }; + const int bigSize { baseSize * 2 }; + Buffer buffer(baseSize); + REQUIRE(!buffer.empty()); + REQUIRE(buffer.size() == baseSize); + REQUIRE(((size_t)buffer.data() & (sfz::Config::defaultAlignment - 1)) == 0); + std::fill(buffer.begin(), buffer.end(), 1.0f); + REQUIRE( buffer.resize(smallSize) ); + REQUIRE( buffer.size() == smallSize ); + REQUIRE(((size_t)buffer.data() & (sfz::Config::defaultAlignment - 1)) == 0); + REQUIRE( std::all_of(buffer.begin(), buffer.end(), [](auto value) { return value == 1.0f; }) ); + REQUIRE( buffer.resize(bigSize) ); + REQUIRE( buffer.size() == bigSize ); + REQUIRE(((size_t)buffer.data() & (sfz::Config::defaultAlignment - 1)) == 0); + for (auto i = 0; i < smallSize; ++i) + REQUIRE(buffer[i] == 1.0f); +} \ No newline at end of file