Included buffer and prelim work for the FilePool

This commit is contained in:
Paul Ferrand 2019-07-30 00:29:23 +02:00
parent d1343ef6f5
commit 7922c98c7f
13 changed files with 459 additions and 22 deletions

View file

@ -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
)

116
sources/AudioBuffer.h Normal file
View file

@ -0,0 +1,116 @@
#pragma once
#include "Buffer.h"
#include "Helpers.h"
#include "Globals.h"
template<class Type, unsigned int NumChannels = sfz::Config::numChannels, unsigned int Alignment = sfz::Config::defaultAlignment>
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<size_t>(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<Type, Alignment> buffer {};
};
template<class Type, unsigned int NumChannels = sfz::Config::numChannels, unsigned int Alignment = sfz::Config::defaultAlignment>
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<size_t>(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<Buffer<Type, Alignment>, NumChannels> buffers;
};

View file

@ -1,20 +1,64 @@
#pragma once
#include "Globals.h"
#include <type_traits>
template<class Type>
#include <cstdlib>
#include <memory>
template<class Type, unsigned int Alignment = sfz::Config::defaultAlignment>
class Buffer
{
static_assert(std::is_arithmetic<Type>::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<Type*>(newData);
alignedData = static_cast<Type*>(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 };
};

1
sources/FilePool.cpp Normal file
View file

@ -0,0 +1 @@
#include "FilePool.h"

17
sources/FilePool.h Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include "AudioBuffer.h"
#include <filesystem>
#include <map>
#include <string_view>
namespace sfz
{
class FilePool
{
public:
FilePool() = default;
private:
std::filesystem::path rootDirectory;
std::map<std::string_view, std::shared_ptr<AudioBuffer<float>>> preloadedData;
};
}

View file

@ -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

View file

@ -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;
}

View file

@ -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)
{
}

View file

@ -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<std::string> unknownOpcodes;
};
} // namespace sfz

View file

@ -43,14 +43,17 @@ void sfz::Synth::callback(std::string_view header, std::vector<Opcode> members)
void sfz::Synth::buildRegion(const std::vector<Opcode>& 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);
}
}

View file

@ -3,6 +3,7 @@
#include "Region.h"
#include "SfzHelpers.h"
#include <vector>
#include <set>
#include <optional>
#include <string_view>
@ -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() ? &regions[idx] : nullptr; }
auto getUnknownOpcodes() { return unknownOpcodes; }
protected:
void callback(std::string_view header, std::vector<Opcode> members) final;
private:
@ -36,7 +38,7 @@ private:
CCValueArray ccState;
std::vector<CCNamePair> ccNames;
std::optional<uint8_t> defaultSwitch;
std::set<std::string_view> unknownOpcodes;
std::vector<Region> regions;
void buildRegion(const std::vector<Opcode>& regionOpcodes);
};

94
tests/AudioBuffer.cpp Normal file
View file

@ -0,0 +1,94 @@
#include "catch2/catch.hpp"
#include "../sources/AudioBuffer.h"
#include <algorithm>
using namespace Catch::literals;
TEST_CASE("[AudioBuffer/SplitBuffer] 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);
SplitAudioBuffer<float> floatSplitBuffer;
REQUIRE(floatSplitBuffer.empty());
REQUIRE(floatSplitBuffer.getNumFrames() == 0);
SplitAudioBuffer<double> doubleSplitBuffer;
REQUIRE(doubleSplitBuffer.empty());
REQUIRE(doubleSplitBuffer.getNumFrames() == 0);
SplitAudioBuffer<int> intSplitBuffer;
REQUIRE(intSplitBuffer.empty());
REQUIRE(intSplitBuffer.getNumFrames() == 0);
}
TEST_CASE("[AudioBuffer/SplitBuffer] Non-empty")
{
AudioBuffer<float> floatBuffer(10);
REQUIRE(!floatBuffer.empty());
REQUIRE(floatBuffer.getNumFrames() == 10);
AudioBuffer<double> doubleBuffer(10);
REQUIRE(!doubleBuffer.empty());
REQUIRE(doubleBuffer.getNumFrames() == 10);
AudioBuffer<int> intBuffer(10);
REQUIRE(!intBuffer.empty());
REQUIRE(intBuffer.getNumFrames() == 10);
SplitAudioBuffer<float> floatSplitBuffer(10);
REQUIRE(!floatSplitBuffer.empty());
REQUIRE(floatSplitBuffer.getNumFrames() == 10);
SplitAudioBuffer<double> doubleSplitBuffer(10);
REQUIRE(!doubleSplitBuffer.empty());
REQUIRE(doubleSplitBuffer.getNumFrames() == 10);
SplitAudioBuffer<int> intSplitBuffer(10);
REQUIRE(!intSplitBuffer.empty());
REQUIRE(intSplitBuffer.getNumFrames() == 10);
}
TEST_CASE("[AudioBuffer/SplitBuffer] Access")
{
const int size { 5 };
AudioBuffer<double> doubleBuffer(size);
for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx)
for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx)
doubleBuffer.getSample(chanIdx, frameIdx) = static_cast<double>(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<double>(doubleBuffer.getNumFrames()) * chanIdx + frameIdx);
SplitAudioBuffer<double> splitDoubleBuffer(size);
for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx)
for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx)
splitDoubleBuffer.getSample(chanIdx, frameIdx) = static_cast<double>(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<double>(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx);
}
TEST_CASE("[AudioBuffer/SplitBuffer] Access 2")
{
const int size { 5 };
AudioBuffer<int> doubleBuffer(size);
for (auto chanIdx = 0; chanIdx < doubleBuffer.getNumChannels(); ++chanIdx)
for (auto frameIdx = 0; frameIdx < doubleBuffer.getNumFrames(); ++frameIdx)
doubleBuffer(chanIdx, frameIdx) = static_cast<int>(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<int>(doubleBuffer.getNumFrames()) * chanIdx + frameIdx);
SplitAudioBuffer<int> splitDoubleBuffer(size);
for (auto chanIdx = 0; chanIdx < splitDoubleBuffer.getNumChannels(); ++chanIdx)
for (auto frameIdx = 0; frameIdx < splitDoubleBuffer.getNumFrames(); ++frameIdx)
splitDoubleBuffer(chanIdx, frameIdx) = static_cast<int>(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<int>(splitDoubleBuffer.getNumFrames()) * chanIdx + frameIdx);
}

107
tests/Buffer.cpp Normal file
View file

@ -0,0 +1,107 @@
#include "catch2/catch.hpp"
#include "../sources/Buffer.h"
#include <algorithm>
using namespace Catch::literals;
TEST_CASE("[Buffer] Empty (float)")
{
Buffer<float> emptyBuffer;
REQUIRE(emptyBuffer.empty());
REQUIRE(emptyBuffer.size() == 0);
}
TEST_CASE("[Buffer] Empty (int)")
{
Buffer<int> emptyBuffer;
REQUIRE(emptyBuffer.empty());
REQUIRE(emptyBuffer.size() == 0);
}
TEST_CASE("[Buffer] Empty (double)")
{
Buffer<double> emptyBuffer;
REQUIRE(emptyBuffer.empty());
REQUIRE(emptyBuffer.size() == 0);
}
TEST_CASE("[Buffer] Empty (uint8_t)")
{
Buffer<uint8_t> emptyBuffer;
REQUIRE(emptyBuffer.empty());
REQUIRE(emptyBuffer.size() == 0);
}
TEST_CASE("[Buffer] 10 floats ")
{
Buffer<float> 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<float> 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<float> 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<float> 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);
}