Parse <sample> with base64 data

Also added the necessary plumbing to load from RAM
It's missing AIFF since the API is file-based for now.
This commit is contained in:
Paul Fd 2021-11-21 23:55:28 +01:00
parent 2aab560f39
commit 803052b552
23 changed files with 1910 additions and 248 deletions

View file

@ -177,6 +177,111 @@ static st_audio_file* st_generic_open_file(const void* filename, int widepath)
return NULL;
}
st_audio_file* st_open_memory(const void* memory, size_t length)
{
st_audio_file* af = (st_audio_file*)malloc(sizeof(st_audio_file));
if (!af)
return NULL;
// Try WAV
{
af->wav = (drwav*)malloc(sizeof(drwav));
if (!af->wav) {
free(af);
return NULL;
}
drwav_bool32 ok =
drwav_init_memory(af->wav, memory, length, NULL);
if (!ok)
free(af->wav);
else {
af->type = st_audio_file_wav;
return af;
}
}
// Try FLAC
{
af->flac =
drflac_open_memory(memory, length, NULL);
if (af->flac) {
af->type = st_audio_file_flac;
return af;
}
}
// Try OGG
{
int err = 0;
char* alloc_buffer = NULL;
int alloc_size = 0;
const int alloc_max_size = 16 * 1024 * 1024;
af->alloc.ogg.alloc_buffer = NULL;
af->alloc.ogg.alloc_buffer_length_in_bytes = 0;
int try_again;
do {
af->ogg =
stb_vorbis_open_memory(memory, length, &err, &af->alloc.ogg);
try_again = 0;
if (!af->ogg && err == VORBIS_outofmem) {
int next_size = alloc_size ? (int)(alloc_size * 3L / 2) : (128 * 1024);
if (next_size <= alloc_max_size) {
free(alloc_buffer);
alloc_size = next_size;
alloc_buffer = (char*)malloc(alloc_size);
af->alloc.ogg.alloc_buffer = alloc_buffer;
af->alloc.ogg.alloc_buffer_length_in_bytes = alloc_size;
try_again = alloc_buffer != NULL;
}
}
} while (try_again);
if (!af->ogg)
free(alloc_buffer);
else {
af->cache.ogg.frames = stb_vorbis_stream_length_in_samples(af->ogg);
if (af->cache.ogg.frames == 0) {
stb_vorbis_close(af->ogg);
free(alloc_buffer);
free(af);
return NULL;
}
stb_vorbis_info info = stb_vorbis_get_info(af->ogg);
af->cache.ogg.channels = info.channels;
af->cache.ogg.sample_rate = info.sample_rate;
af->type = st_audio_file_ogg;
return af;
}
}
// Try MP3
{
af->mp3 = (drmp3*)malloc(sizeof(drmp3));
if (!af->mp3) {
free(af);
return NULL;
}
drmp3_bool32 ok = drmp3_init_memory(af->mp3, memory, length, NULL);
if (!ok)
free(af->mp3);
else {
af->cache.mp3.frames = drmp3_get_pcm_frame_count(af->mp3);
if (af->cache.mp3.frames == 0) {
free(af->mp3);
free(af);
return NULL;
}
af->type = st_audio_file_mp3;
return af;
}
}
free(af);
return NULL;
}
st_audio_file* st_open_file(const char* filename)
{
return st_generic_open_file(filename, 0);

View file

@ -14,6 +14,7 @@
#endif
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#if defined(_WIN32)
#include <wchar.h>
#endif
@ -33,6 +34,7 @@ typedef enum st_audio_file_type {
st_audio_file_other,
} st_audio_file_type;
st_audio_file* st_open_memory(const void* memory, size_t length);
st_audio_file* st_open_file(const char* filename);
#if defined(_WIN32)
st_audio_file* st_open_file_w(const wchar_t* filename);

View file

@ -25,6 +25,7 @@ public:
#if defined(_WIN32)
bool open_file_w(const wchar_t* filename);
#endif
bool open_memory(const void* memory, size_t length);
int get_type() const noexcept;
const char* get_type_string() const noexcept;
@ -96,6 +97,13 @@ bool ST_AudioFile::open_file(const char* filename)
reset(new_af);
return new_af != nullptr;
}
bool ST_AudioFile::open_memory(const void* memory, size_t length)
{
st_audio_file* new_af = st_open_memory(memory, length);
reset(new_af);
return new_af != nullptr;
}
#if defined(_WIN32)
inline bool ST_AudioFile::open_file_w(const wchar_t* filename)
{

View file

@ -6,6 +6,7 @@
#include "AudioReader.h"
#include "FileMetadata.h"
#include <absl/memory/memory.h>
#include <st_audiofile.hpp>
#if defined(SFIZZ_USE_SNDFILE)
#include <sndfile.h>
@ -16,17 +17,19 @@ namespace sfz {
class BasicSndfileReader : public AudioReader {
public:
explicit BasicSndfileReader(ST_AudioFile handle) : handle_(std::move(handle)) {}
explicit BasicSndfileReader(ST_AudioFile handle, std::unique_ptr<MetadataReader> mdReader)
: handle_(std::move(handle)), mdReader_(std::move(mdReader)) {}
virtual ~BasicSndfileReader() {}
int format() const override;
int64_t frames() const override;
unsigned channels() const override;
unsigned sampleRate() const override;
bool getInstrument(InstrumentInfo* instrument) override;
bool getInstrumentInfo(InstrumentInfo& instrument) override;
bool getWavetableInfo(WavetableInfo& instrument) override;
protected:
ST_AudioFile handle_;
std::unique_ptr<MetadataReader> mdReader_;
};
int BasicSndfileReader::format() const
@ -49,7 +52,21 @@ unsigned BasicSndfileReader::sampleRate() const
return handle_.get_sample_rate();
}
bool BasicSndfileReader::getInstrument(InstrumentInfo* instrument)
bool BasicSndfileReader::getWavetableInfo(WavetableInfo& wt)
{
if (!mdReader_)
return false;
if (!mdReader_->isOpened())
mdReader_->open();
if (mdReader_->isOpened())
return mdReader_->extractWavetableInfo(wt);
return false;
};
bool BasicSndfileReader::getInstrumentInfo(InstrumentInfo& instrument)
{
#if defined(SFIZZ_USE_SNDFILE)
SNDFILE* sndfile = reinterpret_cast<SNDFILE*>(handle_.get_sndfile_handle());
@ -57,7 +74,14 @@ bool BasicSndfileReader::getInstrument(InstrumentInfo* instrument)
if (sf_command(sndfile, SFC_GET_INSTRUMENT, sfins, sizeof(SF_INSTRUMENT)) == SF_TRUE)
return true;
#else
(void)instrument;
if (!mdReader_)
return false;
if (!mdReader_->isOpened())
mdReader_->open();
if (mdReader_->isOpened())
return mdReader_->extractInstrument(instrument);
#endif
return false;
}
@ -69,13 +93,13 @@ bool BasicSndfileReader::getInstrument(InstrumentInfo* instrument)
*/
class ForwardReader : public BasicSndfileReader {
public:
explicit ForwardReader(ST_AudioFile handle);
explicit ForwardReader(ST_AudioFile handle, std::unique_ptr<MetadataReader> mdReader);
AudioReaderType type() const override;
size_t readNextBlock(float* buffer, size_t frames) override;
};
ForwardReader::ForwardReader(ST_AudioFile handle)
: BasicSndfileReader(std::move(handle))
ForwardReader::ForwardReader(ST_AudioFile handle, std::unique_ptr<MetadataReader> mdReader)
: BasicSndfileReader(std::move(handle), std::move(mdReader))
{
}
@ -138,7 +162,7 @@ static void reverse_frames(float* data, size_t frames, unsigned channels)
*/
class ReverseReader : public BasicSndfileReader {
public:
explicit ReverseReader(ST_AudioFile handle);
explicit ReverseReader(ST_AudioFile handle, std::unique_ptr<MetadataReader> mdReader);
AudioReaderType type() const override;
size_t readNextBlock(float* buffer, size_t frames) override;
@ -146,8 +170,8 @@ private:
uint64_t position_ {};
};
ReverseReader::ReverseReader(ST_AudioFile handle)
: BasicSndfileReader(std::move(handle))
ReverseReader::ReverseReader(ST_AudioFile handle, std::unique_ptr<MetadataReader> mdReader)
: BasicSndfileReader(std::move(handle), std::move(mdReader))
{
position_ = handle_.get_frame_count();
}
@ -183,7 +207,7 @@ size_t ReverseReader::readNextBlock(float* buffer, size_t frames)
*/
class NoSeekReverseReader : public BasicSndfileReader {
public:
explicit NoSeekReverseReader(ST_AudioFile handle);
explicit NoSeekReverseReader(ST_AudioFile handle, std::unique_ptr<MetadataReader> mdReader);
AudioReaderType type() const override;
size_t readNextBlock(float* buffer, size_t frames) override;
@ -195,8 +219,8 @@ private:
uint64_t fileFramesLeft_ { 0 };
};
NoSeekReverseReader::NoSeekReverseReader(ST_AudioFile handle)
: BasicSndfileReader(std::move(handle))
NoSeekReverseReader::NoSeekReverseReader(ST_AudioFile handle, std::unique_ptr<MetadataReader> mdReader)
: BasicSndfileReader(std::move(handle), std::move(mdReader))
{
}
@ -281,6 +305,7 @@ const std::error_category& undetailed_category()
}
//------------------------------------------------------------------------------
static fs::path emptyPath_ {};
class DummyAudioReader : public AudioReader {
public:
@ -291,8 +316,7 @@ public:
unsigned channels() const override { return 1; }
unsigned sampleRate() const override { return 44100; }
size_t readNextBlock(float*, size_t) override { return 0; }
bool getInstrument(InstrumentInfo* ) override { return false; }
bool getInstrumentInfo(InstrumentInfo& ) override { return false; }
private:
AudioReaderType type_ {};
};
@ -334,7 +358,7 @@ static bool formatHasFastSeeking(int format)
}
#endif
static AudioReaderPtr createAudioReaderWithHandle(ST_AudioFile handle, bool reverse, std::error_code* ec)
static AudioReaderPtr createAudioReaderWithHandle(ST_AudioFile handle, std::unique_ptr<MetadataReader> mdReader, bool reverse, std::error_code* ec)
{
AudioReaderPtr reader;
@ -347,7 +371,7 @@ static AudioReaderPtr createAudioReaderWithHandle(ST_AudioFile handle, bool reve
reader.reset(new DummyAudioReader(reverse ? AudioReaderType::Reverse : AudioReaderType::Forward));
}
else if (!reverse)
reader.reset(new ForwardReader(std::move(handle)));
reader.reset(new ForwardReader(std::move(handle), std::move(mdReader)));
else {
#if defined(SFIZZ_USE_SNDFILE)
bool hasFastSeeking = formatHasFastSeeking(handle.get_sndfile_format());
@ -355,9 +379,9 @@ static AudioReaderPtr createAudioReaderWithHandle(ST_AudioFile handle, bool reve
bool hasFastSeeking = true;
#endif
if (hasFastSeeking)
reader.reset(new ReverseReader(std::move(handle)));
reader.reset(new ReverseReader(std::move(handle), std::move(mdReader)));
else
reader.reset(new NoSeekReverseReader(std::move(handle)));
reader.reset(new NoSeekReverseReader(std::move(handle), std::move(mdReader)));
}
return reader;
@ -371,47 +395,16 @@ AudioReaderPtr createAudioReader(const fs::path& path, bool reverse, std::error_
#else
handle.open_file(path.c_str());
#endif
return createAudioReaderWithHandle(std::move(handle), reverse, ec);
return createAudioReaderWithHandle(std::move(handle),
absl::make_unique<FileMetadataReader>(path), reverse, ec);
}
static AudioReaderPtr createExplicitAudioReaderWithHandle(ST_AudioFile handle, AudioReaderType type, std::error_code* ec)
{
AudioReaderPtr reader;
if (ec)
ec->clear();
if (!handle) {
if (ec)
*ec = std::error_code(1, undetailed_category());
reader.reset(new DummyAudioReader(type));
}
else {
switch (type) {
case AudioReaderType::Forward:
reader.reset(new ForwardReader(std::move(handle)));
break;
case AudioReaderType::Reverse:
reader.reset(new ReverseReader(std::move(handle)));
break;
case AudioReaderType::NoSeekReverse:
reader.reset(new NoSeekReverseReader(std::move(handle)));
break;
}
}
return reader;
}
AudioReaderPtr createExplicitAudioReader(const fs::path& path, AudioReaderType type, std::error_code* ec)
AudioReaderPtr createAudioReaderFromMemory(const void* memory, size_t length, bool reverse, std::error_code* ec)
{
ST_AudioFile handle;
#if defined(_WIN32)
handle.open_file_w(path.wstring().c_str());
#else
handle.open_file(path.c_str());
#endif
return createExplicitAudioReaderWithHandle(std::move(handle), type, ec);
handle.open_memory(memory, length);
return createAudioReaderWithHandle(std::move(handle),
absl::make_unique<MemoryMetadataReader>(memory, length), reverse, ec);
}
} // namespace sfz

View file

@ -14,6 +14,7 @@
namespace sfz {
struct InstrumentInfo;
struct WavetableInfo;
/**
* @brief Designation of a particular kind of audio reader
@ -39,7 +40,8 @@ public:
virtual unsigned channels() const = 0;
virtual unsigned sampleRate() const = 0;
virtual size_t readNextBlock(float* buffer, size_t frames) = 0;
virtual bool getInstrument(InstrumentInfo* instrument) = 0;
virtual bool getInstrumentInfo(InstrumentInfo&) { return false; };
virtual bool getWavetableInfo(WavetableInfo&) { return false; };
};
typedef std::unique_ptr<AudioReader> AudioReaderPtr;
@ -50,8 +52,8 @@ typedef std::unique_ptr<AudioReader> AudioReaderPtr;
AudioReaderPtr createAudioReader(const fs::path& path, bool reverse, std::error_code* ec = nullptr);
/**
* @brief Create a file reader of explicit type. (for testing purposes)
* @brief Create a memory reader of detected type.
*/
AudioReaderPtr createExplicitAudioReader(const fs::path& path, AudioReaderType type, std::error_code* ec = nullptr);
AudioReaderPtr createAudioReaderFromMemory(const void* memory, size_t length, bool reverse, std::error_code* ec = nullptr);
} // namespace sfz

View file

@ -52,50 +52,12 @@ static uint16_t u16be(const uint8_t *bytes)
return (bytes[0] << 8) | bytes[1];
}
static bool fread_u32le(FILE* stream, uint32_t& value)
{
uint8_t bytes[4];
if (fread(bytes, 4, 1, stream) != 1)
return false;
value = u32le(bytes);
return true;
}
static bool fread_u32be(FILE* stream, uint32_t& value)
{
uint8_t bytes[4];
if (fread(bytes, 4, 1, stream) != 1)
return false;
value = u32be(bytes);
return true;
}
#if 0
static bool fread_u16le(FILE* stream, uint16_t& value)
{
uint8_t bytes[2];
if (fread(bytes, 2, 1, stream) != 1)
return false;
value = u16le(bytes);
return true;
}
#endif
static bool fread_u16be(FILE* stream, uint16_t& value)
{
uint8_t bytes[2];
if (fread(bytes, 2, 1, stream) != 1)
return false;
value = u16be(bytes);
return true;
}
//------------------------------------------------------------------------------
struct FileMetadataReader::Impl {
FILE_u stream_;
struct MetadataReader::Impl {
MetadataReader* parent;
std::vector<RiffChunkInfo> riffChunks_;
bool opened_ { false };
enum class ChunkType { None, Riff, Aiff };
ChunkType chunkType_ = ChunkType::None;
@ -110,35 +72,72 @@ struct FileMetadataReader::Impl {
const RiffChunkInfo* riffChunk(size_t index) const;
const RiffChunkInfo* riffChunkById(RiffChunkId id) const;
size_t readRiffData(size_t index, void* buffer, size_t count);
bool read_u32le(uint32_t& value);
bool read_u32be(uint32_t& value);
bool read_u16le(uint16_t& value);
bool read_u16be(uint16_t& value);
};
FileMetadataReader::FileMetadataReader()
bool MetadataReader::Impl::read_u32le(uint32_t& value)
{
uint8_t bytes[4];
if (parent->doRead(bytes, 4, 1) != 1)
return false;
value = u32le(bytes);
return true;
}
bool MetadataReader::Impl::read_u32be(uint32_t& value)
{
uint8_t bytes[4];
if (parent->doRead(bytes, 4, 1) != 1)
return false;
value = u32be(bytes);
return true;
}
#if 0
bool MetadataReader::Impl::read_u16le(uint16_t& value)
{
uint8_t bytes[2];
if (parent->do_read(bytes, 2, 1) != 1)
return false;
value = u16le(bytes);
return true;
}
#endif
bool MetadataReader::Impl::read_u16be(uint16_t& value)
{
uint8_t bytes[2];
if (parent->doRead(bytes, 2, 1) != 1)
return false;
value = u16be(bytes);
return true;
}
MetadataReader::MetadataReader()
: impl_(new Impl)
{
impl_->parent = this;
impl_->riffChunks_.reserve(16);
}
FileMetadataReader::~FileMetadataReader()
MetadataReader::~MetadataReader()
{
}
bool FileMetadataReader::open(const fs::path& path)
bool MetadataReader::open()
{
close();
#if !defined(_WIN32)
FILE* stream = fopen(path.c_str(), "rb");
#else
FILE* stream = _wfopen(path.wstring().c_str(), L"rb");
#endif
if (!stream)
if (!doOpen())
return false;
impl_->stream_.reset(stream);
Impl& impl = *impl_;
impl.opened_ = true;
char magic[4];
size_t count = fread(magic, 1, sizeof(magic), stream);
size_t count = doRead(magic, 1, sizeof(magic));
if (count >= 4 && !memcmp(magic, "fLaC", 4)) {
if (!impl_->openFlac()) {
@ -165,96 +164,99 @@ bool FileMetadataReader::open(const fs::path& path)
return true;
}
void FileMetadataReader::close()
bool MetadataReader::isOpened()
{
impl_->stream_.reset();
impl_->riffChunks_.clear();
return impl_->opened_;
}
bool FileMetadataReader::Impl::openFlac()
void MetadataReader::close()
{
Impl& impl = *impl_;
impl.riffChunks_.clear();
doClose();
impl.opened_ = false;
}
bool MetadataReader::Impl::openFlac()
{
FILE* stream = stream_.get();
std::vector<RiffChunkInfo>& riffChunks = riffChunks_;
if (fseek(stream, 4, SEEK_SET) != 0)
if (parent->doSeek(4, SEEK_SET) != 0)
return false;
uint32_t header = 0;
while (((header >> 31) & 1) != 1) {
if (!fread_u32be(stream, header))
if (!read_u32be(header))
return false;
const uint32_t blockType = (header >> 24) & 0x7f;
const uint32_t blockSize = header & ((1 << 24) - 1);
const off_t offStartBlock = ftell(stream);
const off_t offStartBlock = parent->doTell();
const off_t offNextBlock = offStartBlock + blockSize;
if (blockType == 2) { // APPLICATION block
char blockId[4];
char riffId[4];
uint32_t riffChunkSize;
if (fread(blockId, 4, 1, stream) == 1 && memcmp(blockId, "riff", 4) == 0 &&
fread(riffId, 4, 1, stream) == 1 &&
fread_u32le(stream, riffChunkSize) && riffChunkSize <= blockSize - 12)
if (parent->doRead(blockId, 4, 1) == 1 && memcmp(blockId, "riff", 4) == 0 &&
parent->doRead(riffId, 4, 1) == 1 &&
read_u32le(riffChunkSize) && riffChunkSize <= blockSize - 12)
{
RiffChunkInfo info;
info.index = riffChunks.size();
info.fileOffset = ftell(stream);
info.fileOffset = parent->doTell();
memcpy(info.id.data(), riffId, 4);
info.length = riffChunkSize;
riffChunks.push_back(info);
}
}
if (fseek(stream, offNextBlock, SEEK_SET) != 0)
if (parent->doSeek(offNextBlock, SEEK_SET) != 0)
return false;
}
return true;
}
bool FileMetadataReader::Impl::openRiff()
bool MetadataReader::Impl::openRiff()
{
FILE* stream = stream_.get();
std::vector<RiffChunkInfo>& riffChunks = riffChunks_;
if (fseek(stream, 12, SEEK_SET) != 0)
if (parent->doSeek(12, SEEK_SET) != 0)
return false;
char riffId[4];
uint32_t riffChunkSize;
while (fread(riffId, 4, 1, stream) == 1 && fread_u32le(stream, riffChunkSize)) {
while (parent->doRead(riffId, 4, 1) == 1 && read_u32le(riffChunkSize)) {
RiffChunkInfo info;
info.index = riffChunks.size();
info.fileOffset = ftell(stream);
info.fileOffset = parent->doTell();
memcpy(info.id.data(), riffId, 4);
info.length = riffChunkSize;
riffChunks.push_back(info);
if (fseek(stream, riffChunkSize + (riffChunkSize & 1), SEEK_CUR) != 0)
if (parent->doSeek(riffChunkSize + (riffChunkSize & 1), SEEK_CUR) != 0)
return false;
}
return true;
}
bool FileMetadataReader::Impl::openAiff()
bool MetadataReader::Impl::openAiff()
{
FILE* stream = stream_.get();
rewind(stream);
parent->doRewind();
char formId[4];
uint32_t formSize;
if (fread(formId, 4, 1, stream) != 1 || memcmp(formId, "FORM", 4) ||
!fread_u32be(stream, formSize))
if (parent->doRead(formId, 4, 1) != 1 || memcmp(formId, "FORM", 4) ||
!read_u32be(formSize))
{
return false;
}
char aiffId[4];
if (fread(aiffId, 4, 1, stream) != 1 ||
if (parent->doRead(aiffId, 4, 1) != 1 ||
(memcmp(aiffId, "AIFF", 4) && memcmp(aiffId, "AIFC", 4)))
{
return false;
@ -264,42 +266,42 @@ bool FileMetadataReader::Impl::openAiff()
char riffId[4];
uint32_t riffChunkSize;
while (fread(riffId, 4, 1, stream) == 1 && fread_u32be(stream, riffChunkSize)) {
while (parent->doRead(riffId, 4, 1) == 1 && read_u32be(riffChunkSize)) {
RiffChunkInfo info;
info.index = riffChunks.size();
info.fileOffset = ftell(stream);
info.fileOffset = parent->doTell();
memcpy(info.id.data(), riffId, 4);
info.length = riffChunkSize;
riffChunks.push_back(info);
if (fseek(stream, riffChunkSize + (riffChunkSize & 1), SEEK_CUR) != 0)
if (parent->doSeek(riffChunkSize + (riffChunkSize & 1), SEEK_CUR) != 0)
return false;
}
return true;
}
size_t FileMetadataReader::riffChunkCount() const
size_t MetadataReader::riffChunkCount() const
{
return impl_->riffChunks_.size();
}
const RiffChunkInfo* FileMetadataReader::riffChunk(size_t index) const
const RiffChunkInfo* MetadataReader::riffChunk(size_t index) const
{
return impl_->riffChunk(index);
}
const RiffChunkInfo* FileMetadataReader::Impl::riffChunk(size_t index) const
const RiffChunkInfo* MetadataReader::Impl::riffChunk(size_t index) const
{
return (index < riffChunks_.size()) ? &riffChunks_[index] : nullptr;
}
const RiffChunkInfo* FileMetadataReader::riffChunkById(RiffChunkId id) const
const RiffChunkInfo* MetadataReader::riffChunkById(RiffChunkId id) const
{
return impl_->riffChunkById(id);
}
const RiffChunkInfo* FileMetadataReader::Impl::riffChunkById(RiffChunkId id) const
const RiffChunkInfo* MetadataReader::Impl::riffChunkById(RiffChunkId id) const
{
for (const RiffChunkInfo& riff : riffChunks_) {
if (riff.id == id)
@ -308,12 +310,12 @@ const RiffChunkInfo* FileMetadataReader::Impl::riffChunkById(RiffChunkId id) con
return nullptr;
}
size_t FileMetadataReader::readRiffData(size_t index, void* buffer, size_t count)
size_t MetadataReader::readRiffData(size_t index, void* buffer, size_t count)
{
return impl_->readRiffData(index, buffer, count);
}
size_t FileMetadataReader::Impl::readRiffData(size_t index, void* buffer, size_t count)
size_t MetadataReader::Impl::readRiffData(size_t index, void* buffer, size_t count)
{
const RiffChunkInfo* riff = riffChunk(index);
if (!riff)
@ -321,14 +323,13 @@ size_t FileMetadataReader::Impl::readRiffData(size_t index, void* buffer, size_t
count = (count < riff->length) ? count : riff->length;
FILE* stream = stream_.get();
if (fseek(stream, riff->fileOffset, SEEK_SET) != 0)
if (parent->doSeek(riff->fileOffset, SEEK_SET) != 0)
return 0;
return fread(buffer, 1, count, stream);
return parent->doRead(buffer, 1, count);
}
bool FileMetadataReader::extractInstrument(InstrumentInfo& ins)
bool MetadataReader::extractInstrument(InstrumentInfo& ins)
{
if (extractRiffInstrument(ins))
return true;
@ -339,7 +340,7 @@ bool FileMetadataReader::extractInstrument(InstrumentInfo& ins)
return false;
}
bool FileMetadataReader::extractRiffInstrument(InstrumentInfo& ins)
bool MetadataReader::extractRiffInstrument(InstrumentInfo& ins)
{
if (impl_->chunkType_ != Impl::ChunkType::Riff)
return false;
@ -399,9 +400,10 @@ bool FileMetadataReader::extractRiffInstrument(InstrumentInfo& ins)
return true;
}
bool FileMetadataReader::extractAiffInstrument(InstrumentInfo& ins)
bool MetadataReader::extractAiffInstrument(InstrumentInfo& ins)
{
if (impl_->chunkType_ != Impl::ChunkType::Aiff)
Impl& impl = *impl_;
if (impl.chunkType_ != Impl::ChunkType::Aiff)
return false;
const RiffChunkInfo* instChunk = riffChunkById(RiffChunkId{'I', 'N', 'S', 'T'});
@ -418,12 +420,11 @@ bool FileMetadataReader::extractAiffInstrument(InstrumentInfo& ins)
//
std::map<uint16_t, uint32_t> markers;
if (markChunk) {
FILE* stream = impl_->stream_.get();
if (fseek(stream, markChunk->fileOffset, SEEK_SET) != 0)
if (doSeek(markChunk->fileOffset, SEEK_SET) != 0)
return false;
uint16_t numMarkers;
if (!fread_u16be(stream, numMarkers))
if (!impl.read_u16be(numMarkers))
return false;
for (uint32_t i = 0; i < numMarkers; ++i) {
@ -432,12 +433,12 @@ bool FileMetadataReader::extractAiffInstrument(InstrumentInfo& ins)
uint8_t size;
char name[256];
if (!fread_u16be(stream, id) || !fread_u32be(stream, position) || fread(&size, 1, 1, stream) != 1 || fread(name, size, 1, stream) != 1)
if (!impl.read_u16be(id) || !impl.read_u32be(position) || doRead(&size, 1, 1) != 1 || doRead(name, size, 1) != 1)
return false;
name[size] = '\0';
if (i + 1 < numMarkers && ((~size) & 1)) {
if (fseek(stream, 1, SEEK_CUR) != 0)
if (doSeek(1, SEEK_CUR) != 0)
return false;
}
@ -502,7 +503,7 @@ bool FileMetadataReader::extractAiffInstrument(InstrumentInfo& ins)
return true;
}
bool FileMetadataReader::extractWavetableInfo(WavetableInfo& wt)
bool MetadataReader::extractWavetableInfo(WavetableInfo& wt)
{
if (impl_->extractClmWavetable(wt))
return true;
@ -520,7 +521,7 @@ bool FileMetadataReader::extractWavetableInfo(WavetableInfo& wt)
return false;
}
bool FileMetadataReader::Impl::extractClmWavetable(WavetableInfo &wt)
bool MetadataReader::Impl::extractClmWavetable(WavetableInfo &wt)
{
const RiffChunkInfo* clm = riffChunkById(RiffChunkId{'c', 'l', 'm', ' '});
if (!clm)
@ -551,7 +552,7 @@ bool FileMetadataReader::Impl::extractClmWavetable(WavetableInfo &wt)
return true;
}
bool FileMetadataReader::Impl::extractSurgeWavetable(WavetableInfo &wt)
bool MetadataReader::Impl::extractSurgeWavetable(WavetableInfo &wt)
{
const RiffChunkInfo* srge;
@ -574,7 +575,7 @@ bool FileMetadataReader::Impl::extractSurgeWavetable(WavetableInfo &wt)
return true;
}
bool FileMetadataReader::Impl::extractUheWavetable(WavetableInfo &wt)
bool MetadataReader::Impl::extractUheWavetable(WavetableInfo &wt)
{
const RiffChunkInfo* uhwt = riffChunkById(RiffChunkId{'u', 'h', 'W', 'T'});
if (!uhwt)
@ -596,4 +597,160 @@ bool FileMetadataReader::Impl::extractUheWavetable(WavetableInfo &wt)
return true;
}
struct FileMetadataReader::Impl {
fs::path path;
FILE_u stream_;
};
FileMetadataReader::FileMetadataReader(const fs::path& path)
: impl_(new Impl)
{
impl_->path = path;
}
FileMetadataReader::~FileMetadataReader()
{
}
void FileMetadataReader::doClose()
{
impl_->stream_.reset();
}
bool FileMetadataReader::doOpen()
{
Impl& impl = *impl_;
#if !defined(_WIN32)
FILE* stream = fopen(impl.path.c_str(), "rb");
#else
FILE* stream = _wfopen(impl.path.wstring().c_str(), L"rb");
#endif
if (!stream)
return false;
impl_->stream_.reset(stream);
return true;
}
size_t FileMetadataReader::doRead(void* ptr, size_t size, size_t n)
{
return fread(ptr, size, n, impl_->stream_.get());
}
int FileMetadataReader::doSeek(long off, int whence)
{
return fseek(impl_->stream_.get(), off, whence);
}
void FileMetadataReader::doRewind()
{
rewind(impl_->stream_.get());
}
long FileMetadataReader::doTell()
{
return ftell(impl_->stream_.get());
}
struct MemoryMetadataReader::Impl {
const char* memory;
size_t length;
size_t position { 0 };
};
MemoryMetadataReader::MemoryMetadataReader(const void* memory, size_t length)
: impl_(new Impl)
{
impl_->memory = reinterpret_cast<const char*>(memory);
impl_->length = length;
}
MemoryMetadataReader::~MemoryMetadataReader()
{
}
void MemoryMetadataReader::doClose()
{
}
bool MemoryMetadataReader::doOpen()
{
Impl& impl = *impl_;
if (impl.length == 0)
return false;
return true;
}
size_t MemoryMetadataReader::doRead(void* ptr, size_t size, size_t n)
{
Impl& impl = *impl_;
size_t actualCount =
(std::min(impl.position + n * size, impl.length) - impl.position) / size;
size_t totalBytes = actualCount * size;
memcpy(ptr, impl.memory + impl.position, totalBytes);
impl.position += totalBytes;
return actualCount;
}
int MemoryMetadataReader::doSeek(long off, int whence)
{
Impl& impl = *impl_;
switch(whence) {
case SEEK_SET:
{
if (off < 0)
return -1;
const size_t offset = static_cast<size_t>(off);
if (offset > impl.length)
return -1;
impl.position = offset;
}
break;
case SEEK_CUR:
{
if (off < 0) {
const size_t minusOffset = static_cast<size_t>(-off);
if (minusOffset > impl.position)
return -1;
impl.position -= minusOffset;
} else {
const size_t offset = static_cast<size_t>(off);
const size_t newPosition = impl.position + offset;
if (newPosition > impl.length)
return -1;
impl.position = newPosition;
}
}
break;
case SEEK_END:
{
if (off > 0)
return -1;
const size_t minusOffset = static_cast<size_t>(-off);
if (minusOffset > impl.length)
return -1;
impl.position = impl.length - minusOffset;
}
break;
}
return 0;
}
void MemoryMetadataReader::doRewind()
{
impl_->position = 0;
}
long MemoryMetadataReader::doTell()
{
return static_cast<long>(impl_->position);
}
} // namespace sfz

View file

@ -81,19 +81,10 @@ struct WavetableInfo {
bool oneShot;
};
class FileMetadataReader {
class MetadataReader {
public:
FileMetadataReader();
~FileMetadataReader();
/**
* @brief Open an audio file of supported format and read internal structures
*/
bool open(const fs::path& path);
/**
* @brief Close an audio file
*/
void close();
MetadataReader();
virtual ~MetadataReader();
/**
* @brief Get the number of RIFF chunks in the file
@ -132,9 +123,68 @@ public:
*/
bool extractWavetableInfo(WavetableInfo& wt);
/**
* @brief Opens the metadata reader and perform initialization.
* If the reader is opened already it closes it and reopens.
*
* @return true
* @return false
*/
bool open();
/**
* @brief Check if the reader is already opened
*
* @return true
* @return false
*/
bool isOpened();
/**
* @brief Close the metadata reader
*
*/
void close();
protected:
virtual bool doOpen() { return true; };
virtual void doClose() {}
virtual size_t doRead(void* ptr, size_t size, size_t n) = 0;
virtual int doSeek(long off, int whence) = 0;
virtual void doRewind() = 0;
virtual long doTell() = 0;
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
class FileMetadataReader: public MetadataReader {
public:
FileMetadataReader(const fs::path& path);
~FileMetadataReader();
private:
bool doOpen() override;
void doClose() override;
size_t doRead(void* ptr, size_t size, size_t n) override;
int doSeek(long off, int whence) override;
void doRewind() override;
long doTell() override;
struct Impl;
std::unique_ptr<Impl> impl_;
};
class MemoryMetadataReader: public MetadataReader {
public:
MemoryMetadataReader(const void* memory, size_t length);
~MemoryMetadataReader();
private:
bool doOpen() override;
void doClose() override;
size_t doRead(void* ptr, size_t size, size_t n) override;
int doSeek(long off, int whence) override;
void doRewind() override;
long doTell() override;
struct Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace sfz

View file

@ -232,6 +232,9 @@ bool sfz::FilePool::checkSample(std::string& filename) const noexcept
bool sfz::FilePool::checkSampleId(FileId& fileId) const noexcept
{
if (loadedFiles.contains(fileId))
return true;
std::string filename = fileId.filename();
bool result = checkSample(filename);
if (result)
@ -239,46 +242,23 @@ bool sfz::FilePool::checkSampleId(FileId& fileId) const noexcept
return result;
}
absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const FileId& fileId) noexcept
absl::optional<sfz::FileInformation> getReaderInformation(sfz::AudioReader* reader) noexcept
{
const fs::path file { rootDirectory / fileId.filename() };
if (!fs::exists(file))
return {};
AudioReaderPtr reader = createAudioReader(file, fileId.isReverse());
const unsigned channels = reader->channels();
if (channels != 1 && channels != 2) {
DBG("[sfizz] Missing logic for " << reader->channels() << " channels, discarding sample " << fileId);
if (channels != 1 && channels != 2)
return {};
}
FileInformation returnedValue;
sfz::FileInformation returnedValue;
returnedValue.end = static_cast<uint32_t>(reader->frames()) - 1;
returnedValue.sampleRate = static_cast<double>(reader->sampleRate());
returnedValue.numChannels = static_cast<int>(reader->channels());
returnedValue.numChannels = static_cast<int>(channels);
InstrumentInfo instrumentInfo {};
bool haveInstrumentInfo = reader->getInstrument(&instrumentInfo);
FileMetadataReader mdReader;
bool mdReaderOpened = mdReader.open(file);
if (!haveInstrumentInfo) {
// if no instrument, then try extracting from embedded RIFF chunks (flac)
if (mdReaderOpened)
haveInstrumentInfo = mdReader.extractInstrument(instrumentInfo);
}
if (mdReaderOpened) {
WavetableInfo wt;
if (mdReader.extractWavetableInfo(wt))
returnedValue.wavetable = wt;
}
if (!fileId.isReverse()) {
if (haveInstrumentInfo && instrumentInfo.loop_count > 0) {
// Check for instrument info
sfz::InstrumentInfo instrumentInfo {};
if (reader->getInstrumentInfo(instrumentInfo)) {
returnedValue.rootKey = clamp<uint8_t>(instrumentInfo.basenote, 0, 127);
if (reader->type() == sfz::AudioReaderType::Forward) {
if (instrumentInfo.loop_count > 0) {
returnedValue.hasLoop = true;
returnedValue.loopStart = instrumentInfo.loops[0].start;
returnedValue.loopEnd =
@ -288,15 +268,52 @@ absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const Fil
// TODO loops ignored when reversed
// prehaps it can make use of SF_LOOP_BACKWARD?
}
}
if (haveInstrumentInfo)
returnedValue.rootKey = clamp<uint8_t>(instrumentInfo.basenote, 0, 127);
// Check for wavetable info
sfz::WavetableInfo wt {};
if (reader->getWavetableInfo(wt))
returnedValue.wavetable = wt;
return returnedValue;
}
absl::optional<sfz::FileInformation> sfz::FilePool::checkExistingFileInformation(const FileId& fileId) noexcept
{
const auto loadedFile = loadedFiles.find(fileId);
if (loadedFile != loadedFiles.end())
return loadedFile->second.information;
const auto preloadedFile = preloadedFiles.find(fileId);
if (preloadedFile != preloadedFiles.end())
return preloadedFile->second.information;
return {};
}
absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const FileId& fileId) noexcept
{
auto existingInformation = checkExistingFileInformation(fileId);
if (existingInformation)
return existingInformation;
const fs::path file { rootDirectory / fileId.filename() };
if (!fs::exists(file))
return {};
AudioReaderPtr reader = createAudioReader(file, fileId.isReverse());
return getReaderInformation(reader.get());
}
bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexcept
{
const auto loadedFile = loadedFiles.find(fileId);
if (loadedFile != loadedFiles.end()) {
loadedFile->second.preloadCallCount++;
return true;
}
auto fileInformation = getFileInformation(fileId);
if (!fileInformation)
return false;
@ -368,16 +385,16 @@ sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept
if (!fileInformation)
return {};
const fs::path file { rootDirectory / fileId.filename() };
AudioReaderPtr reader = createAudioReader(file, fileId.isReverse());
const auto frames = static_cast<uint32_t>(reader->frames());
const auto existingFile = loadedFiles.find(fileId);
if (existingFile != loadedFiles.end()) {
existingFile->second.preloadCallCount++;
return { &existingFile->second };
} else {
fileInformation->sampleRate = static_cast<double>(reader->sampleRate());
}
const fs::path file { rootDirectory / fileId.filename() };
AudioReaderPtr reader = createAudioReader(file, fileId.isReverse());
const auto frames = static_cast<uint32_t>(reader->frames());
auto insertedPair = loadedFiles.insert_or_assign(fileId, {
readFromFile(*reader, frames),
*fileInformation
@ -386,10 +403,32 @@ sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept
insertedPair.first->second.preloadCallCount++;
return { &insertedPair.first->second };
}
sfz::FileDataHolder sfz::FilePool::loadFromRam(const FileId& fileId, const std::vector<char>& data) noexcept
{
const auto loaded = loadedFiles.find(fileId);
if (loaded != loadedFiles.end())
return { &loaded->second };
auto reader = createAudioReaderFromMemory(data.data(), data.size(), fileId.isReverse());
auto fileInformation = getReaderInformation(reader.get());
const auto frames = static_cast<uint32_t>(reader->frames());
auto insertedPair = loadedFiles.insert_or_assign(fileId, {
readFromFile(*reader, frames),
*fileInformation
});
insertedPair.first->second.status = FileData::Status::Preloaded;
insertedPair.first->second.preloadCallCount++;
DBG("Added a file " << fileId.filename());
return { &insertedPair.first->second };
}
sfz::FileDataHolder sfz::FilePool::getFilePromise(const std::shared_ptr<FileId>& fileId) noexcept
{
const auto loaded = loadedFiles.find(*fileId);
if (loaded != loadedFiles.end())
return { &loaded->second };
const auto preloaded = preloadedFiles.find(*fileId);
if (preloaded == preloadedFiles.end()) {
DBG("[sfizz] File not found in the preloaded files: " << fileId->filename());

View file

@ -206,7 +206,7 @@ public:
*
* @return size_t
*/
size_t getNumPreloadedSamples() const noexcept { return preloadedFiles.size(); }
size_t getNumPreloadedSamples() const noexcept { return preloadedFiles.size() + loadedFiles.size(); }
/**
* @brief Get metadata information about a file.
@ -236,6 +236,16 @@ public:
*/
FileDataHolder loadFile(const FileId& fileId) noexcept;
/**
* @brief Load a file from RAM and return its information. The file pool will store\
* this data for future requests so use this function responsibly.
*
* @param fileId
* @param data
* @return A handle on the file data
*/
FileDataHolder loadFromRam(const FileId& fileId, const std::vector<char>& data) noexcept;
/**
* @brief Check that the sample exists. If not, try to find it in a case insensitive way.
*
@ -325,6 +335,8 @@ public:
*/
void triggerGarbageCollection() noexcept;
private:
absl::optional<sfz::FileInformation> checkExistingFileInformation(const FileId& fileId) noexcept;
fs::path rootDirectory;
bool loadInRam { config::loadInRam };

View file

@ -24,6 +24,7 @@
#include "Metronome.h"
#include "SynthConfig.h"
#include "ScopedFTZ.h"
#include "utility/Base64.h"
#include "utility/StringViewHelpers.h"
#include "utility/Timing.h"
#include "utility/XmlHelpers.h"
@ -130,6 +131,9 @@ void Synth::Impl::onParseFullBlock(const std::string& header, const std::vector<
case hash("effect"):
handleEffectOpcodes(members);
break;
case hash("sample"):
handleSampleOpcodes(members);
break;
default:
std::cerr << "Unknown header: " << header << '\n';
}
@ -549,6 +553,47 @@ void Synth::Impl::handleEffectOpcodes(const std::vector<Opcode>& rawMembers)
getOrCreateBus(busIndex).addEffect(std::move(fx));
}
void Synth::Impl::handleSampleOpcodes(const std::vector<Opcode>& rawMembers)
{
absl::string_view name { "" };
bool hasData { false };
absl::string_view sampleData;
for (const Opcode& opcode : rawMembers) {
switch (opcode.lettersOnlyHash) {
case hash("name"):
name = opcode.value;
break;
case hash("base&data"):
if (opcode.parameters.front() == 64)
sampleData = opcode.value;
break;
case hash("data"):
hasData = true;
break;
}
}
if (name.empty())
return;
if (hasData && sampleData.empty()) {
DBG("The sample data provided for sample " << name
<< " doesn't use base64 encoding, which is the only one sfizz knows how to decode.\n "
<< "If it does, please use base64data= instead of data=."
);
return;
}
if (sampleData.empty())
return;
auto data = decodeBase64(sampleData);
FilePool& filePool = resources_.getFilePool();
FileId id { std::string(name) };
filePool.loadFromRam(id, data);
}
void Synth::Impl::resetDefaultCCValues() noexcept
{
fill(absl::MakeSpan(defaultCCValues_), 0.0f);

View file

@ -88,6 +88,12 @@ struct Synth::Impl final: public Parser::Listener {
* @param members the opcodes of the <effect> block
*/
void handleEffectOpcodes(const std::vector<Opcode>& members);
/**
* @brief Helper function to dispatch <effect> opcodes
*
* @param members the opcodes of the <effect> block
*/
void handleSampleOpcodes(const std::vector<Opcode>& members);
/**
* @brief Helper function to merge all the currently active opcodes
* as set by the successive callbacks and create a new region to store

View file

@ -288,8 +288,26 @@ void Parser::processOpcode()
}
reader.getChar();
SourceLocation valueStart = reader.location();
// If we're parsing a base64 data field, ignore comments and process until the next header/directive
if (nameExpanded == "base64data") {
std::string valueRaw;
reader.extractWhile(&valueRaw, [&reader](char c) {
if (c == '<' || c == '#')
return false;
return true;
});
SourceLocation valueEnd = reader.location();
_currentOpcodes.emplace_back(nameExpanded, valueRaw);
if (_listener)
_listener->onParseOpcode({ opcodeStart, opcodeEnd }, { valueStart, valueEnd }, nameExpanded, valueRaw);
return;
}
std::string valueRaw;
extractToEol(reader, &valueRaw);

View file

@ -0,0 +1,86 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
// Inspired by the public domain implementation at https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64
#pragma once
#include "Debug.h"
#include <vector>
#include <absl/strings/string_view.h>
namespace sfz
{
inline std::vector<char> decodeBase64(absl::string_view input)
{
if (input.empty())
return {};
std::size_t padding { 0 };
auto length = input.length();
if (length >= 1 and input[length - 1] == '=')
padding++;
if (length >= 2 and input[length - 2] == '=')
padding++;
input.remove_suffix(padding);
std::vector<char> decoded;
decoded.reserve(length);
auto cursor = input.begin();
int32_t temp { 0 };
size_t position { 0 };
while (cursor != input.end()) {
char c = *cursor++;
if (c == ' ' || c == '\n' || c == '\t' || c == '\r') // Ignore whitespace
continue;
if (c >= 0x41 && c <= 0x5A) // This area will need tweaking if
temp |= c - 0x41; // you are using an alternate alphabet
else if (c >= 0x61 && c <= 0x7A)
temp |= c - 0x47;
else if (c >= 0x30 && c <= 0x39)
temp |= c + 0x04;
else if (c == 0x2B)
temp |= 0x3E; //change to 0x2D for URL alphabet
else if (c == 0x2F)
temp |= 0x3F; //change to 0x5F for URL alphabet
else
return {};
if (++position == 4) {
decoded.push_back((temp >> 16) & 0x000000FF);
decoded.push_back((temp >> 8 ) & 0x000000FF);
decoded.push_back((temp ) & 0x000000FF);
position = 0;
temp = 0;
} else {
temp <<= 6;
}
}
switch(position) {
case 3:
decoded.push_back((temp >> 16) & 0x000000FF);
decoded.push_back((temp >> 8 ) & 0x000000FF);
break;
case 2:
decoded.push_back((temp >> 10) & 0x000000FF);
break;
case 1:
return {};
default:
break;
}
return decoded;
}
}

View file

@ -270,10 +270,10 @@ TEST_CASE("[Files] Channels (channels.sfz)")
REQUIRE(synth.getRegionView(1)->isStereo());
}
TEST_CASE("[Files] Channels (channels_multi.sfz)")
TEST_CASE("[Files] Generators and wavetables")
{
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels_multi.sfz");
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/wavetables.sfz");
REQUIRE(synth.getNumRegions() == 12);
int regionNumber = 0;
@ -373,6 +373,69 @@ TEST_CASE("[Files] Channels (channels_multi.sfz)")
REQUIRE(region->oscillatorEnabled == OscillatorEnabled::Auto);
}
TEST_CASE("[Files] Embedded wavetables")
{
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/wavetables_embedded.sfz");
REQUIRE(synth.getNumRegions() == 7);
int regionNumber = 0;
const Region* region = nullptr;
// explicit wavetable
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId->filename() == "ramp_wave.wav");
REQUIRE(!region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == OscillatorEnabled::On);
// explicit wavetable with multi
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId->filename() == "ramp_wave.wav");
REQUIRE(region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == OscillatorEnabled::On);
// explicit disabled wavetable
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId->filename() == "ramp_wave.wav");
REQUIRE(!region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(!region->isOscillator());
REQUIRE(region->oscillatorEnabled == OscillatorEnabled::Off);
// explicit disabled wavetable with multi
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId->filename() == "ramp_wave.wav");
REQUIRE(!region->isStereo());
REQUIRE(!region->isGenerator());
REQUIRE(!region->isOscillator());
REQUIRE(region->oscillatorEnabled == OscillatorEnabled::Off);
// implicit wavetable (sound file < 3000 frames and wavetable tags)
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId->filename() == "wavetables/surge.wav");
REQUIRE(!region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == OscillatorEnabled::Auto);
// Parse oscillator=auto and same as above
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId->filename() == "wavetables/surge.wav");
REQUIRE(!region->isGenerator());
REQUIRE(region->isOscillator());
REQUIRE(region->oscillatorEnabled == OscillatorEnabled::Auto);
// implicit non wavetable (sound file < 3000 frames but no wavetable tags)
region = synth.getRegionView(regionNumber++);
REQUIRE(region->sampleId->filename() == "short_non_wavetable.wav");
REQUIRE(!region->isGenerator());
REQUIRE(!region->isOscillator());
REQUIRE(region->oscillatorEnabled == OscillatorEnabled::Auto);
}
TEST_CASE("[Files] wrong (overlapping) replacement for defines")
{
Synth synth;
@ -715,3 +778,43 @@ TEST_CASE("[Files] Unused samples are cleared on reloading")
)");
REQUIRE(synth.getNumPreloadedSamples() == 0);
}
TEST_CASE("[Files] Embedded sample data")
{
sfz::Synth synth1;
sfz::Synth synth2;
synth1.loadSfzFile(fs::current_path() / "tests/TestFiles/kick.sfz");
synth2.loadSfzFile(fs::current_path() / "tests/TestFiles/kick_embedded.sfz");
REQUIRE(synth1.getNumPreloadedSamples() == 1);
REQUIRE(synth2.getNumPreloadedSamples() == 1);
AudioBuffer<float> buffer1 { 2, 1024 };
AudioBuffer<float> buffer2 { 2, 1024 };
synth1.noteOn(0, 60, 100);
synth2.noteOn(0, 60, 100);
for (unsigned i = 0; i < 100; ++i) {
synth1.renderBlock(buffer1);
synth2.renderBlock(buffer2);
bool equal = true;
for (unsigned j = 0; j < buffer1.getNumFrames(); ++j) {
equal &= (buffer1.getSample(0, j) == buffer2.getSample(0, j));
equal &= (buffer1.getSample(1, j) == buffer2.getSample(1, j));
}
REQUIRE(equal);
}
}
TEST_CASE("[Files] Key center from audio file, with embedded sample data")
{
sfz::Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/sample_keycenter_embedded.sfz");
REQUIRE(synth.getNumRegions() == 6);
REQUIRE(synth.getRegionView(0)->pitchKeycenter == 38);
REQUIRE(synth.getRegionView(1)->pitchKeycenter == 62);
REQUIRE(synth.getRegionView(2)->pitchKeycenter == 38);
REQUIRE(synth.getRegionView(3)->pitchKeycenter == 62);
REQUIRE(synth.getRegionView(4)->pitchKeycenter == 10);
REQUIRE(synth.getRegionView(5)->pitchKeycenter == 62);
}

View file

@ -5,6 +5,7 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "sfizz/utility/StringViewHelpers.h"
#include "sfizz/utility/Base64.h"
#include "catch2/catch.hpp"
#include "absl/strings/string_view.h"
using namespace Catch::literals;
@ -66,3 +67,78 @@ TEST_CASE("[Helpers] trim")
REQUIRE(trim(input).empty());
}
}
TEST_CASE("[Parsing] Base64")
{
{
auto result = sfz::decodeBase64("");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "" );
}
{
auto result = sfz::decodeBase64("Zg==");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "f" );
}
{
auto result = sfz::decodeBase64("Zg");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "f" );
}
{
auto result = sfz::decodeBase64("Zm8=");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "fo" );
}
{
auto result = sfz::decodeBase64("Zm8");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "fo" );
}
{
auto result = sfz::decodeBase64("Zm9v");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "foo" );
}
{
auto result = sfz::decodeBase64("Zm9vYg==");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "foob" );
}
{
auto result = sfz::decodeBase64("Zm9vYg");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "foob" );
}
{
auto result = sfz::decodeBase64("Zm9vYmE=");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "fooba" );
}
{
auto result = sfz::decodeBase64("Zm9vYmE");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "fooba" );
}
{
auto result = sfz::decodeBase64("Zm9vYmFy");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "foobar" );
}
{
auto result = sfz::decodeBase64("Zm9v\r\n Ym \tFy");
absl::string_view view { result.data(), result.size() };
REQUIRE( view == "foobar" );
}
}

View file

@ -673,3 +673,75 @@ TEST_CASE("[Parsing] Expanded value of #include")
REQUIRE(mock.fullBlockHeaders == expectedHeaders);
REQUIRE(mock.fullBlockMembers == expectedMembers);
}
TEST_CASE("[Parsing] Sample data")
{
sfz::Parser parser;
ParsingMocker mock;
parser.setListener(&mock);
parser.parseString("/opcodeValueWithMultipleConsecutiveSpaces.sfz",R"(
#define $test 2
<sample> base64data=fdsfeq==w=ew=/////we/* $test */dsafr
// fderwq==d=
<region> sample=*sine
)");
std::vector<std::vector<sfz::Opcode>> expectedMembers = {
{{"base64data", R"(fdsfeq==w=ew=/////we/* $test */dsafr
// fderwq==d=)"}},
{{"sample", "*sine"}},
};
std::vector<std::string> expectedHeaders = {
"sample", "region"
};
std::vector<sfz::Opcode> expectedOpcodes;
for (auto& members: expectedMembers)
for (auto& opcode: members)
expectedOpcodes.push_back(opcode);
REQUIRE(mock.beginnings == 1);
REQUIRE(mock.endings == 1);
REQUIRE(mock.errors.empty());
REQUIRE(mock.warnings.empty());
REQUIRE(mock.opcodes == expectedOpcodes);
REQUIRE(mock.headers == expectedHeaders);
REQUIRE(mock.fullBlockHeaders == expectedHeaders);
REQUIRE(mock.fullBlockMembers == expectedMembers);
}
TEST_CASE("[Parsing] Sample data 2")
{
sfz::Parser parser;
ParsingMocker mock;
parser.setListener(&mock);
parser.parseString("/opcodeValueWithMultipleConsecutiveSpaces.sfz",R"(
#define $test 2
<region> sample=*sine
<sample> base64data=fdsfeq==w=ew=/////we/* $test */dsafr
// fderwq==d=
)");
std::vector<std::vector<sfz::Opcode>> expectedMembers = {
{{"sample", "*sine"}},
{{"base64data", R"(fdsfeq==w=ew=/////we/* $test */dsafr
// fderwq==d=)"}},
};
std::vector<std::string> expectedHeaders = {
"region", "sample"
};
std::vector<sfz::Opcode> expectedOpcodes;
for (auto& members: expectedMembers)
for (auto& opcode: members)
expectedOpcodes.push_back(opcode);
REQUIRE(mock.beginnings == 1);
REQUIRE(mock.endings == 1);
REQUIRE(mock.errors.empty());
REQUIRE(mock.warnings.empty());
REQUIRE(mock.opcodes == expectedOpcodes);
REQUIRE(mock.headers == expectedHeaders);
REQUIRE(mock.fullBlockHeaders == expectedHeaders);
REQUIRE(mock.fullBlockMembers == expectedMembers);
}

1
tests/TestFiles/kick.sfz Normal file
View file

@ -0,0 +1 @@
<region> sample=kick.wav

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,627 @@
<group> pitch_keycenter=sample oscillator=off
<region> sample=root_key_38.wav
<region> sample=root_key_62.wav
<region> sample=root_key_38.flac
<region> sample=root_key_62.flac
<region> pitch_keycenter=10 sample=root_key_62.flac
<region> key=10 sample=root_key_62.flac
<sample> name=root_key_38.wav base64data=
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
NJ7z/s+wKr7M2azG
<sample> name=root_key_62.wav base64data=
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
NJ7z/s+wKr7M2azG
<sample> name=root_key_38.flac base64data=
ZkxhQwAAACIQABAAABE3ABE3CsRA8AAACJa/iIn0E/di5nowFYEmMX5GAwAAEgAAAAAAAAAAAAAA
AAAAAAAIlgQAACggAAAAcmVmZXJlbmNlIGxpYkZMQUMgMS4zLjIgMjAxNzAxMDEAAAAAAgAAEHJp
ZmZSSUZG1BEAAFdBVkUCAAAccmlmZmZtdCAQAAAAAQABAESsAACIWAEAAgAQAAIAAFByaWZmTElT
VEQAAABJTkZPSVNGVCQAAABMb29wQXVkaXRpb25lZXIgKGxpYnNuZGZpbGUtMS4wLjI4KQBJQ1JE
DAAAADIwMjEtMDQtMTUAAAIAABByaWZmY3VlIAQAAAAAAAAAAgAAMHJpZmZzbXBsJAAAAAAAAAAA
AAAAk1gAACYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAxyaWZmZGF0YSwRAACBACAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
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
<sample> name=root_key_62.flac base64data=
ZkxhQwAAACIQABAAABE3ABE3CsRA8AAACJa/iIn0E/di5nowFYEmMX5GAwAAEgAAAAAAAAAAAAAA
AAAAAAAIlgQAACggAAAAcmVmZXJlbmNlIGxpYkZMQUMgMS4zLjIgMjAxNzAxMDEAAAAAAgAAEHJp
ZmZSSUZG1BEAAFdBVkUCAAAccmlmZmZtdCAQAAAAAQABAESsAACIWAEAAgAQAAIAAFByaWZmTElT
VEQAAABJTkZPSVNGVCQAAABMb29wQXVkaXRpb25lZXIgKGxpYnNuZGZpbGUtMS4wLjI4KQBJQ1JE
DAAAADIwMjEtMDQtMTUAAAIAABByaWZmY3VlIAQAAAAAAAAAAgAAMHJpZmZzbXBsJAAAAAAAAAAA
AAAAk1gAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAxyaWZmZGF0YSwRAACBACAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
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

View file

@ -0,0 +1,38 @@
<region> oscillator=on sample=ramp_embedded.wav
<sample> name=ramp_embedded.wav encoding=base64 data=UklGRiQIAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAIAAABgEGAgYDBgAGBQYGB
gcGBAYJBgoGCwYIBg0GDgYPBgwGEQYSBhMGEAYVBhYGFwYUBhkGGgYbBhgGHQYeBh8GHAYhBiIGI
wYgBiUGJgYnBiQGKQYqBisGKAYtBi4GLwYsBjEGMgYzBjAGNQY2BjcGNAY5BjoGOwY4Bj0GPgY/B
jwGQQZCBkMGQAZFBkYGRwZEBkkGSgZLBkgGTQZOBk8GTAZRBlIGUwZQBlUGVgZXBlQGWQZaBlsGW
AZdBl4GXwZcBmEGYgZjBmAGZQZmBmcGZAZpBmoGawZoBm0GbgZvBmwGcQZyBnMGcAZ1BnYGdwZ0B
nkGegZ7BngGfQZ+Bn8GfAaBBoIGgwaABoUGhgaHBoQGiQaKBosGiAaNBo4GjwaMBpEGkgaTBpAGl
QaWBpcGlAaZBpoGmwaYBp0GngafBpwGoQaiBqMGoAalBqYGpwakBqkGqgarBqgGrQauBq8GrAaxB
rIGswawBrUGtga3BrQGuQa6BrsGuAa9Br4Gvwa8BsEGwgbDBsAGxQbGBscGxAbJBsoGywbIBs0Gz
gbPBswG0QbSBtMG0AbVBtYG1wbUBtkG2gbbBtgG3QbeBt8G3AbhBuIG4wbgBuUG5gbnBuQG6QbqB
usG6AbtBu4G7wbsBvEG8gbzBvAG9Qb2BvcG9Ab5BvoG+wb4Bv0G/gb/BvwDAQMCAwMDAAMFAwYDB
wMEAwkDCgMLAwgDDQMOAw8DDAMRAxIDEwMQAxUDFgMXAxQDGQMaAxsDGAMdAx4DHwMcAyEDIgMjA
yADJQMmAycDJAMpAyoDKwMoAy0DLgMvAywDMQMyAzMDMAM1AzYDNwM0AzkDOgM7AzgDPQM+Az8DP
ANBA0IDQwNAA0UDRgNHA0QDSQNKA0sDSANNA04DTwNMA1EDUgNTA1ADVQNWA1cDVANZA1oDWwNYA
10DXgNfA1wDYQNiA2MDYANlA2YDZwNkA2kDagNrA2gDbQNuA28DbANxA3IDcwNwA3UDdgN3A3QDe
QN6A3sDeAN9A34DfwN8A4EDggODA4ADhQOGA4cDhAOJA4oDiwOIA40DjgOPA4wDkQOSA5MDkAOVA
5YDlwOUA5kDmgObA5gDnQOeA58DnAOhA6IDowOgA6UDpgOnA6QDqQOqA6sDqAOtA64DrwOsA7EDs
gOzA7ADtQO2A7cDtAO5A7oDuwO4A70DvgO/A7wDwQPCA8MDwAPFA8YDxwPEA8kDygPLA8gDzQPOA
88DzAPRA9ID0wPQA9UD1gPXA9QD2QPaA9sD2APdA94D3wPcA+ED4gPjA+AD5QPmA+cD5APpA+oD6
wPoA+0D7gPvA+wD8QPyA/MD8AP1A/YD9wP0A/kD+gP7A/gD/QP+A/8D/AABAAIAAwAAAAUABgAHA
AQACQAKAAsACAANAA4ADwAMABEAEgATABAAFQAWABcAFAAZABoAGwAYAB0AHgAfABwAIQAiACMAI
AAlACYAJwAkACkAKgArACgALQAuAC8ALAAxADIAMwAwADUANgA3ADQAOQA6ADsAOAA9AD4APwA8A
EEAQgBDAEAARQBGAEcARABJAEoASwBIAE0ATgBPAEwAUQBSAFMAUABVAFYAVwBUAFkAWgBbAFgAX
QBeAF8AXABhAGIAYwBgAGUAZgBnAGQAaQBqAGsAaABtAG4AbwBsAHEAcgBzAHAAdQB2AHcAdAB5A
HoAewB4AH0AfgB/AHwAgQCCAIMAgACFAIYAhwCEAIkAigCLAIgAjQCOAI8AjACRAJIAkwCQAJUAl
gCXAJQAmQCaAJsAmACdAJ4AnwCcAKEAogCjAKAApQCmAKcApACpAKoAqwCoAK0ArgCvAKwAsQCyA
LMAsAC1ALYAtwC0ALkAugC7ALgAvQC+AL8AvADBAMIAwwDAAMUAxgDHAMQAyQDKAMsAyADNAM4Az
wDMANEA0gDTANAA1QDWANcA1ADZANoA2wDYAN0A3gDfANwA4QDiAOMA4ADlAOYA5wDkAOkA6gDrA
OgA7QDuAO8A7ADxAPIA8wDwAPUA9gD3APQA+QD6APsA+AD9AP4A/wD8AQD9Af0C/QP9AP0F/Qb9B
/0E/Qn9Cv0L/Qj9Df0O/Q/9DP0R/RL9E/0Q/RX9Fv0X/RT9Gf0a/Rv9GP0d/R79H/0c/SH9Iv0j/
SD9Jf0m/Sf9JP0p/Sr9K/0o/S39Lv0v/Sz9Mf0y/TP9MP01/Tb9N/00/Tn9Ov07/Tj9Pf0+/T/9P
P1B/UL9Q/1A/UX9Rv1H/UT9Sf1K/Uv9SP1N/U79T/1M/VH9Uv1T/VD9Vf1W/Vf9VP1Z/Vr9W/1Y/
V39Xv1f/Vz9Yf1i/WP9YP1l/Wb9Z/1k/Wn9av1r/Wj9bf1u/W/9bP1x/XL9c/1w/XX9dv13/XT9e
f16/Xv9eP19/X79f/18/YH9gv2D/YD9hf2G/Yf9hP2J/Yr9i/2I/Y39jv2P/Yz9kf2S/ZP9kP2V/
Zb9l/2U/Zn9mv2b/Zj9nf2e/Z/9nP2h/aL9o/2g/aX9pv2n/aT9qf2q/av9qP2t/a79r/2s/bH9s
v2z/bD9tf22/bf9tP25/br9u/24/b39vv2//bz9wf3C/cP9wP3F/cb9x/3E/cn9yv3L/cj9zf3O/
c/9zP3R/dL90/3Q/dX91v3X/dT92f3a/dv92P3d/d793/3c/eH94v3j/eD95f3m/ef95P3p/er96
/3o/e397v3v/ez98f3y/fP98P31/fb99/30/fn9+v37/fj9/f3+/fw==

View file

@ -0,0 +1,169 @@
<region> sample=ramp_wave.wav oscillator=on
<region> sample=ramp_wave.wav oscillator=on oscillator_multi=3
<region> sample=ramp_wave.wav oscillator=off
<region> sample=ramp_wave.wav oscillator=off oscillator_multi=3
<region> sample=wavetables/surge.wav
<region> oscillator=auto sample=wavetables/surge.wav
<region> sample=short_non_wavetable.wav
<sample> name=ramp_wave.wav base64data=
UklGRiQIAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAIAAABgEGAgYDBgAGBQYGB
gcGBAYJBgoGCwYIBg0GDgYPBgwGEQYSBhMGEAYVBhYGFwYUBhkGGgYbBhgGHQYeBh8GHAYhBiIGI
wYgBiUGJgYnBiQGKQYqBisGKAYtBi4GLwYsBjEGMgYzBjAGNQY2BjcGNAY5BjoGOwY4Bj0GPgY/B
jwGQQZCBkMGQAZFBkYGRwZEBkkGSgZLBkgGTQZOBk8GTAZRBlIGUwZQBlUGVgZXBlQGWQZaBlsGW
AZdBl4GXwZcBmEGYgZjBmAGZQZmBmcGZAZpBmoGawZoBm0GbgZvBmwGcQZyBnMGcAZ1BnYGdwZ0B
nkGegZ7BngGfQZ+Bn8GfAaBBoIGgwaABoUGhgaHBoQGiQaKBosGiAaNBo4GjwaMBpEGkgaTBpAGl
QaWBpcGlAaZBpoGmwaYBp0GngafBpwGoQaiBqMGoAalBqYGpwakBqkGqgarBqgGrQauBq8GrAaxB
rIGswawBrUGtga3BrQGuQa6BrsGuAa9Br4Gvwa8BsEGwgbDBsAGxQbGBscGxAbJBsoGywbIBs0Gz
gbPBswG0QbSBtMG0AbVBtYG1wbUBtkG2gbbBtgG3QbeBt8G3AbhBuIG4wbgBuUG5gbnBuQG6QbqB
usG6AbtBu4G7wbsBvEG8gbzBvAG9Qb2BvcG9Ab5BvoG+wb4Bv0G/gb/BvwDAQMCAwMDAAMFAwYDB
wMEAwkDCgMLAwgDDQMOAw8DDAMRAxIDEwMQAxUDFgMXAxQDGQMaAxsDGAMdAx4DHwMcAyEDIgMjA
yADJQMmAycDJAMpAyoDKwMoAy0DLgMvAywDMQMyAzMDMAM1AzYDNwM0AzkDOgM7AzgDPQM+Az8DP
ANBA0IDQwNAA0UDRgNHA0QDSQNKA0sDSANNA04DTwNMA1EDUgNTA1ADVQNWA1cDVANZA1oDWwNYA
10DXgNfA1wDYQNiA2MDYANlA2YDZwNkA2kDagNrA2gDbQNuA28DbANxA3IDcwNwA3UDdgN3A3QDe
QN6A3sDeAN9A34DfwN8A4EDggODA4ADhQOGA4cDhAOJA4oDiwOIA40DjgOPA4wDkQOSA5MDkAOVA
5YDlwOUA5kDmgObA5gDnQOeA58DnAOhA6IDowOgA6UDpgOnA6QDqQOqA6sDqAOtA64DrwOsA7EDs
gOzA7ADtQO2A7cDtAO5A7oDuwO4A70DvgO/A7wDwQPCA8MDwAPFA8YDxwPEA8kDygPLA8gDzQPOA
88DzAPRA9ID0wPQA9UD1gPXA9QD2QPaA9sD2APdA94D3wPcA+ED4gPjA+AD5QPmA+cD5APpA+oD6
wPoA+0D7gPvA+wD8QPyA/MD8AP1A/YD9wP0A/kD+gP7A/gD/QP+A/8D/AABAAIAAwAAAAUABgAHA
AQACQAKAAsACAANAA4ADwAMABEAEgATABAAFQAWABcAFAAZABoAGwAYAB0AHgAfABwAIQAiACMAI
AAlACYAJwAkACkAKgArACgALQAuAC8ALAAxADIAMwAwADUANgA3ADQAOQA6ADsAOAA9AD4APwA8A
EEAQgBDAEAARQBGAEcARABJAEoASwBIAE0ATgBPAEwAUQBSAFMAUABVAFYAVwBUAFkAWgBbAFgAX
QBeAF8AXABhAGIAYwBgAGUAZgBnAGQAaQBqAGsAaABtAG4AbwBsAHEAcgBzAHAAdQB2AHcAdAB5A
HoAewB4AH0AfgB/AHwAgQCCAIMAgACFAIYAhwCEAIkAigCLAIgAjQCOAI8AjACRAJIAkwCQAJUAl
gCXAJQAmQCaAJsAmACdAJ4AnwCcAKEAogCjAKAApQCmAKcApACpAKoAqwCoAK0ArgCvAKwAsQCyA
LMAsAC1ALYAtwC0ALkAugC7ALgAvQC+AL8AvADBAMIAwwDAAMUAxgDHAMQAyQDKAMsAyADNAM4Az
wDMANEA0gDTANAA1QDWANcA1ADZANoA2wDYAN0A3gDfANwA4QDiAOMA4ADlAOYA5wDkAOkA6gDrA
OgA7QDuAO8A7ADxAPIA8wDwAPUA9gD3APQA+QD6APsA+AD9AP4A/wD8AQD9Af0C/QP9AP0F/Qb9B
/0E/Qn9Cv0L/Qj9Df0O/Q/9DP0R/RL9E/0Q/RX9Fv0X/RT9Gf0a/Rv9GP0d/R79H/0c/SH9Iv0j/
SD9Jf0m/Sf9JP0p/Sr9K/0o/S39Lv0v/Sz9Mf0y/TP9MP01/Tb9N/00/Tn9Ov07/Tj9Pf0+/T/9P
P1B/UL9Q/1A/UX9Rv1H/UT9Sf1K/Uv9SP1N/U79T/1M/VH9Uv1T/VD9Vf1W/Vf9VP1Z/Vr9W/1Y/
V39Xv1f/Vz9Yf1i/WP9YP1l/Wb9Z/1k/Wn9av1r/Wj9bf1u/W/9bP1x/XL9c/1w/XX9dv13/XT9e
f16/Xv9eP19/X79f/18/YH9gv2D/YD9hf2G/Yf9hP2J/Yr9i/2I/Y39jv2P/Yz9kf2S/ZP9kP2V/
Zb9l/2U/Zn9mv2b/Zj9nf2e/Z/9nP2h/aL9o/2g/aX9pv2n/aT9qf2q/av9qP2t/a79r/2s/bH9s
v2z/bD9tf22/bf9tP25/br9u/24/b39vv2//bz9wf3C/cP9wP3F/cb9x/3E/cn9yv3L/cj9zf3O/
c/9zP3R/dL90/3Q/dX91v3X/dT92f3a/dv92P3d/d793/3c/eH94v3j/eD95f3m/ef95P3p/er96
/3o/e397v3v/ez98f3y/fP98P31/fb99/30/fn9+v37/fj9/f3+/fw==
<sample> name=wavetables/surge.wav base64data=
UklGRjgEAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAc3JnZQgAAAABAAAAAAEAAGRhdGH8
AwAAAAAkA0gGagmMDKsPyBLiFfkYCxwaHyMiKCUmKB8rES77MN8zujaMOVY8Fz/OQXpEHEe0ST9M
v04zUZtT9VVCWIJas1zXXutg8WLoZM9mpmhtaiNsyW1eb+JwVHK1cwR1QXZrd4R4iXl8elx7KXzj
fIl9HX6cfgl/YX+mf9h/9X//f/V/2H+mf2F/CX+cfh1+iX3jfCl8XHt8eol5hHhrd0F2BHW1c1Ry
4nBeb8ltI2xtaqZoz2boZPFi62DXXrNcglpCWPVVm1MzUb9OP0y0SRxHekTOQRc/VjyMObo23zP7
MBEuHysmKCglIyIaHwsc+RjiFcgSqw+MDGoJSAYkAwAA3Py4+Zb2dPNV8DjtHuoH5/Xj5uDd3dja
2tfh1O/RBc8hzEbJdMaqw+nAMr6Gu+S4TLbBs0Gxza5lrAuqvqd+pU2jKaEVnw+dGJsxmVqXk5Xd
kzeSopAej6yNS4z8ir+JlYh8h3eGhIWkhNeDHYN3guOBZIH3gJ+AWoAogAuAAYALgCiAWoCfgPeA
ZIHjgXeCHYPXg6SEhIV3hnyHlYi/ifyKS4ysjR6PopA3kt2Tk5ValzGZGJsPnRWfKaFNo36lvqcL
qmWsza5BscGzTLbkuIa7Mr7pwKrDdMZGySHMBc/v0eHU2tfY2t3d5uD14wfnHuo47VXwdPOW9rj5
AAAAAQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsA
HAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4
ADkAOgA7ADwAPQA+AD8AQP9A/0H/Qv9D/0T/Rf9G/0f/SP9J/0r/S/9M/03/Tv9P/1D/Uf9S/1P/
VP9V/1b/V/9Y/1n/Wv9b/1z/Xf9e/1//YP9h/2L/Y/9k/2X/Zv9n/2j/af9q/2v/bP9t/27/b/9w
/3H/cv9z/3T/df92/3f/eP95/3r/e/98/33/fgGAAYEBggGDAYQBhQGGAYcBiAGJAYoBiwGMAY0B
jgGPAZABkQGSAZMBlAGVAZYBlwGYAZkBmgGbAZwBnQGeAZ8BoAGhAaIBowGkAaUBpgGnAagBqQGq
AasBrAGtAa4BrwGwAbEBsgGzAbQBtQG2AbcBuAG5AboBuwG8Ab0BvgG/AMAAwQDCAMMAxADFAMYA
xwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDj
AOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+
<sample> name=short_non_wavetable.wav base64data=
UklGRtwWAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YbgWAAABAAIAAwAIAPn/DQD3
/woA+/8FAAIA//8IAP3/BgAAAAQAAwADAAEABgD+/wcA/v8EAP//BgD+/wcA+/8GAP//AgACAAIA
BAABAAAAAgD//wQAAAACAAEAAwACAAIAAQAEAP//BAADAAMAAQAAAAIA/v8IAPr/CwD3/w4A9v8K
APr/BgABAAYA/v8AAP//AgACAAIAAAAAAAMA//8EAPz/CQD8/wUA+v8HAAEABAAAAAEAAQABAAIA
AAACAAAABAABAAEABAD9/wQA/v8AAAMA//8FAPz/CAD7/wUAAQAAAAEABQD//w8ACwAOAAgACAAJ
AA8ADgAVABUAIQAlACcAIgAgACkALQAyADQAOABEADoARwA9AFIATABUAE8AWABSAGAAVQBgAGEA
YgBkAGEAZABtAHsAeQB+AHsAhACHAJEAjACYAJcApQCyALYAwQC/AMsA3QDhAO8A6AD4AAUBDAEV
ARoBJgExAUABPwFXAVMBagFnAXcBhQGUAbIBwAHhAeAB+wH7ARYCFgIkAiwCPAJLAmECewKbAsQC
0QLsAt4C9wIBAyADMAM9A04DXwOBA5QDtAPDA9YD7gMHBBYEMgQ/BGYEgASbBLsEygTnBAIFJwU+
BWAFdwWNBacFrAXKBd4FCwYoBk4GXgaBBp4GvwbeBvsGHQcwBzwHVQdlB5MHnQeuB7gHwgfZB/AH
DQgiCD4IWwiFCJcIrQi4CNYI/QgJCSYJIAlDCUUJYgltCYEJkQmYCasJrwnICdMJ5wnjCfYJ9QkI
CgYKDwoLChUKCQoICgQKGQoQChUKAQoJCgYKAQrwCfQJ5QnqCdQJ1gnYCc0JxgmrCaYJkAlzCVUJ
OgkjCQ4J7wjjCMgItwibCHYIWgg2CBYI6gfFB5EHfQdQBy0HBwfeBsEGkAZvBjoGJwbyBcQFjwVb
BS4F9wTIBIsEWgQhBN0DnQNPAx8D2QKNAksC/wHJAXUBPgHwALsAZAAUAMv/dv8q/8f+fP4i/tD9
af0M/bn8XfwP/KH7Tfvj+p/6Qvrm+YT5JfnI+Fv4+/eM9zz30fZt9vP1kfUt9dP0ZvT985DzJ/PI
8mLy8vGL8QrxmPAf8LXvRe/a7mfu/u2H7SDtrOxW7OfrjesW67XqRur16YHpK+mf6Evowudp5+vm
kuYz5s3lbeUL5bDkWOT545rjQePi4pDiGuK+4VThEOG+4G/gGuC9323fIN/T3oreOt7s3abdXt0T
3c3ch9xK3Azc1tud22fbKdvx2q3ae9oy2gfaz9mo2XDZR9kU2e7Yydip2IrYcdhS2DnYJdgc2A7Y
BNj11/HX5Nfd197X5dfh1/LX6df31/XXA9gc2DDYUNhZ2IXYj9jD2NjYANkp2U3Zb9mm2dXZDto+
2m3ap9rj2iPbXdua2+LbHdxr3K7c9Nw+3Ynd39063pPe7N5D36rfAeBx4MHgNeGL4fHhYuLX4kjj
wuM+5LzkPeWv5SDmouYq57bnPujO6F7p/OmT6jLryutj7Pfsku0t7szuZO8W8LLwWPEQ8svydfMG
9JH0PfX19aH2UPf597P4c/lQ+hz76/uq/GH9EP7Q/o//SAALAdkBpwJmAwwEwgSvBYgGSQfAB0UI
7gjaCdkKvguCDCsN4w21DoQPUhAbEd8RrhJcExkU1hS1FZMWaBcfGOsYnBldGgsbzxuMHEMdEB4N
H/wfpSAmIakhhyJRI/ojcCT1JLMliyZZJxIo1CiTKUcq0ipxKwMsliwzLectmC47L78vhTA4MdIx
ETJjMswyaTPxM240CzWlNTY2nDYRN5s3JziFOME4AzljObg5HTpoOrw6GTuHO/g7VDyEPLQ84Dwl
PW897D1EPnI+cj5+PrQ+7D4cPyg/VD+TP78/qj+DP30/iT+rP+Q/JUAcQNw/qz+oP4Q/Wj8ePxE/
Bz8nP2Q/Uj+7Pvw9qD3KPeo9qT0APYA8Xzx0PGk8MDzbO087rDoSOsM5pTleOew4XzjfN0836jar
NkU2hTWINOMzjzNbM5UyszH9MLgwhzAWMDAvRC5ZLb4sRSzrK1krpyq/KRcpvih1KPkn3SZ1Jf8j
GiPaIqwi6yGwIEkffR4xHvAdRR1eHGobgRqcGe8YVRibF5gWlxXfFE0UgRNjEggRCBA1D9gOfg4W
DhQNmws6CksJzQhfCJYHawYnBTkEsQNIA3kCXAExAFn/hv6n/cT8DPxQ+4X6g/mb+PT3YPe59sj1
s/Sz8/PyUfKB8b3w8O8o72Hudu1+7LrrNOvG6g3qO+lp6H7n1uY65v3liuXx5MjjkuKw4TPh8eCe
4EHgf9+u3gLerN043ZjcvNs12wLbwdoh2mHZythC2LfXLtcL1y7XG9dk1nzV8dSr1HnUHtS+01/T
BNOm0iTSndEv0RzRItH+0ITQKtD7z/LP5s/4z8fPI89szvbNzM3bzR/OTc4MzmPNrcyvzG/NCM7r
zXLNCs0PzTPNPM0KzcHMxczfzOfMn8ybzPDMXc1EzdvM2cxSzcHN0s30zfrN182czczNJM5nzn7O
mM7Wzg3PYM+1z/zPENDqz7vP1c810JXQ89BX0cHRHdJP0o3S1dLz0n/Tc9R71W3Vx9Sd1C/VtNXa
1ZzVpNXI1UfWLdfr12rYhdjI2GLZ99le2pXaz9ou21bbrNsB3GzcuNwK3Xrd/N2d3jXfz99a4OXg
UuHD4f/hUeLV4obj5OPu4yPksuQ25Xnlz+V55irns+cJ6IroNun86YDqy+r36hLrbOsO7KfsA+1b
7ejtiu4O75PvDfBm8KDwuPAP8YrxFvKp8iXzm/Pt8yL0jvQo9av1HvaS9gj3h/cZ+L/4IflD+TP5
m/kd+qP61/oz+7L7YPy4/Oz8V/0J/qz+2f7H/u7+vf+RACABRAE8AU4BtwFSAtACZQPvA0MENQQt
BJ0EGQVqBXoFxwUnBmgGfQbUBoIHQgikCJ8IjgjHCFoJ+gk+CisKIgqMCk8L+wssDBwMAAxCDMQM
Tg2FDY4Ntg0mDpgOxg7YDtcOHw+fDx4QPBAiEC8QSxBWEGgQxRBJEaQRzBHzER0SMBI1EjwSohII
E1ETSxNkE5oTCRRIFHYUghTKFCEVeBVvFUsVFBVTFb8VLxYpFgsWORaTFtAW4hYQF1sXlhepF9AX
AxgUGO8X0BfXFwQYJxhPGDsYQhi3GKwZhBqjGjUa6BkgGm0amxpPGiMaAxo9Gqwa6hrKGn4ashpc
G80bqxtWG0UblhvLG84bxRvEG+cb7Rv5G+Qb8hsHHEocahyXHMkcAh0PHeQclBxlHI0cER2tHQAe
8x2YHXkdkB2wHYkdJR0sHasdFx4THvIdFB5PHmQeHB7nHdYdAh4yHlYeHB7vHcMd8x00HkweTR5X
HoYelR55HlAe/x3PHdcd/x3aHWcdCB1MHaEdth1fHSYdJB0THd0chxyGHIocqByfHL4cwRylHH4c
QBwxHDkcQBwKHK0bLBvRGqkauBrQGqcaURoLGhoaJhonGvYZyBmQGUcZ1BhJGMgXlRemF+QX/Beo
FyMXsxZ2FjwW8RWrFWwVIRXeFJgUcBQeFL4TZhMZE+ESfxIbEq4RXxEfEcYQbBDxD4EPSA8vDwkP
0A5VDgMOlw1UDdsMdgweDOALlAsiC6cKPAq8CR0JqwiXCLEIcAjLByMHqAZVBhcG3gWaBQEFMwSE
AzYDBQPcApYCTQLTAVcB+gC4AGUA3f9S/+7+jf5E/u39ev0B/WP8/fub+0/77Pp++h76zvl0+QP5
l/g5+Mj3P/en9if20/WN9Vb13PRc9NfzhvNR8zjzHfP18qXyNPLS8WTx3/Bh8Pjvue9s7/3ule4v
7urtqu1+7T7tCu2q7FvsE+zz69TrnetB68XqXuoe6vTpxOl46TXp7ujK6J/odehE6Afo1+ew54nn
auc+5yrnB+e65m3mSOY45vPljeVR5W/lU+Ui5d3kCOU55Sfl0+SX5IrkiOSI5IzkluR65FTkIuQD
5OHjr+Nr41PjYeOb47vj5uPt4+LjvuOo46rju+PW4+bj5+PS47zj2eP84yHk/+P2497jBeQl5EXk
TuR65MHkAuUr5SrlQeVV5W7lZeV75X/lj+WG5b7l+uU35kvmduaL5sLm9eZI54jngudj53znzOcb
6D/oSeiE6Mno/Oga6UPpf+m/6eHpCOoq6lzqd+qR6p/q0+ot65jr1Ovv6wzsYuzB7PfsBu0i7Wft
ue3u7QvuLO517q7u9+4975/v7u808H7wr/C98MbwAfGD8eXxHvI88ory9PJC83bzi/PG8xf0cfSe
9L307vRt9dj1T/Z19qH2p/a19uX2OPek9w34UfiN+NX4Hvlz+ZH51PkC+mf6o/q7+tT6+/pF+4/7
2Psx/Ij8xvzk/BD9bf3U/Rv+RP5q/qz+6P4Z/0f/fP+4/w8AUwC2APkARwGIAc4BEgJhApsC3wIF
AyMDRANkA7ED6QMgBFMElQTtBCAFbwWzBf0FLQZNBnoGqgbyBh0HVQdyB60H+wdHCIAIeAiZCL4I
EQlpCZQJwQnhCR0KWgqYCsgK6goCCxgLYgvAC/ELAAz/Cx0MUgyDDKQMxgzjDAsNPA1yDZMNqg24
DeoNKg5wDoQOpg7LDv8OMA9ID2MPeg9/D2sPhA+5DxMQRBBsEIUQlBCPEI4QpxDFEPIQABElESUR
PRE+EVERVhFrEZURxhH0ERASGBItEkASZxJ7Eo4SgBJ+EnMScxJ9EoYSnBKyEtgS5RLqEsgSuhK8
Er4S2xL1Eg8TCRPvEuIS4xLzEvQS7xLoEtES0RK4EroSlhKQEokSlRKnEqgSqhKwEoESaxJNElkS
dRJbEi4S6hHHEcIRzxHLEdIRvxHGEb8RthGpEZURYRE4EfoQ7RDfENoQyxDDELQQpRBvEFQQHRAV
EAIQ8Q/sD9UPuA93Dz4PEg/5DucOyA7DDrgOnw5vDjoOCQ7bDbYNlw1zDTcN4wy0DLQMmgxoDBMM
3AvPC6oLkQtlCzsLFAvtCsoKmgp0Cl8KUAozCgUK1AmoCX0JSwkZCfcIyQiOCEsIFwj3B/EH1Qe3
B6EHZQcqB+MGvAakBoYGRQYKBs4FlQViBRwFDAXpBOMElQRPBAgE+gPsA+QDkgNgAyYDKwMdA/UC
tQJfAiYC7QHXAa0BdwE3AQIB7wDSAKEAYwAyABsA/v++/4r/Zv9G/x3/4P6o/mr+Qv4a/gr+3v3i
/cv9vP2A/UP9Ff36/OT8ufyM/E38LvwV/AP85/ut+3z7Tvsq+xD76PrI+oT6Zfo6+kD6LPoO+tv5
tvmK+Wj5Kvka+fX46fi3+Jv4iPh9+FT4MfgE+Pv31Pem93n3V/dM90D3LvcD9+r23fbg9sX2nvZy
9mf2TfYu9gT23/W99aX1ffVy9Uj1T/U+9UD1LfUK9f/07PTh9Mz0ofR89GH0WvRR9Er0MPQb9Pzz
6vPb89bz1/PF88LzuPO084zzb/NT81bzTfM28xXzGPMl8zDzNPMk8yfzCPPy8sryr/Kw8r3y3fLn
8uzy2fK78p/yfPJj8nfyhvKV8n7yafJz8nDygvJp8nTydfJ98mvyX/Jq8nryb/JP8j/yQ/JP8kfy
PPI88kHySvJO8kbyP/I38lrybvKE8oTyh/Ku8sby0/LJ8r3yzfLc8u3y9fL88v/yBfP28v7y8fIQ
8yDzQPNc817zbfNw83zzfvOi87jz4vPS883z0/Pu8yL0OvRR9Ev0YPRp9Hz0gvSk9MD03PTr9Af1
EPUn9Sz1RvVf9X/1lfWm9cb19/Uz9lD2XPZU9nT2i/a09sH25vb89hL3Jvc/92z3ivet97r34vcA
+Cj4UPiE+Lz47/gV+S/5P/lN+Vz5e/m2+eH5Ffoi+kL6XPqG+rv66foq+z37VPtV+5L7wPv6+wv8
I/ws/D78YPyV/M/8+/wr/Uf9a/18/ZP9uP3z/SD+Rv5q/pP+vf7i/hD/Of9h/5H/w//n//n/HQBe
AJ4AsADIANwAJwFRAYcBnwG0AcwBAAJHAoMCsALGAv8CMQNkA4YDqwPNA/QDAgQkBEIEcwSZBMoE
8gQuBVAFfgV8BZcFrAXoBRIGSgZ8BrAG2AbrBgwHFgc7B2QHiwe5B9IHBAgUCEAISwhtCHYIiQif
CNIICAlCCVAJdAmRCdIJ/wkbCiEKMQpKCmAKgQqPCqAKqgrNCuoKFws4C1QLgQuOC60LyQvoCwkM
CgwWDB0MNQxNDGMMdQyHDJ4Mpgy0DMEM1gzzDPcMDQ0YDSsNQw1MDWQNZw2IDYUNkQ1+DZANnw24
DbUNqQ2dDZsNuA3hDeUN5g3QDecN6Q33Dd0N6A3jDeIN6A3cDeYN5g3xDfEN8g3tDesN7Q3bDdcN
xg3DDbcNrg2gDZwNmA2NDZYNmw2SDYQNXQ1FDT0NPw1FDTMNGw0IDf8M3gzKDKUMnwyTDI0MeQx1
DGAMRgwuDBQM/gvoC7oLqQuTC5ALdgtMCyoLIwsRCwALxgqLCnEKSwpHChwKCgrmCcYJqwl5CV8J
GAkECdUIzQilCIcIZwhECBcI7wfHB7MHiQdNBwsHygaxBocGgAZWBkgGDQbmBbMFjQVbBTcFCQXa
BLEEfARcBCgEBATeA58DdgMuAxMD4QLPAqICfgJQAigCBALSAaIBXAEyAfAA2gCOAFkACADt/9H/
u/+F/1b/H/8P/9L+mf5T/hj++/3R/a79fP1Z/Sn9/fzF/Iz8Y/wt/Av82fu3+437b/s/+xf78Pq7
+oz6WPoi+vn5xPmw+Y/5fflL+R/54/jJ+KX4kPhu+EL4Ifj+99b3sPd291j3RPc49yz3CPfk9sT2
rfaX9nz2Q/YP9t/1w/W79an1mPV/9XX1ZvVX9TT1GPX29Nr0tfSa9HT0WfQ49Dn0NPQ99Dn0KfQl
9B30G/QQ9PDz1PPH87TzyvO887nzl/OR85Lzo/Oe85Xzm/OW85/zjPOT85bzpvOe867zpPO587Lz
wPPM89/z8vPx8+vz7PPu8wn0EvQe9CH0M/RQ9FX0Y/Re9Hj0ivSZ9K70s/TC9M703/T29AD1CvUk
9T/1a/WE9Z71q/XG9d/17vUM9ib2QvZW9mn2avaF9p/2zPb59h33PfdP93T3mPew98r30vfy9xv4
R/hy+In4pfi0+N347PgU+Tf5WvmG+aj5y/ni+QT6HvpR+mz6lfqx+t36DPs3+1X7bPuF+577xPva
+wf8Gvwx/E78a/ym/Mn8/vwE/Sz9R/13/aL9zf3t/QH+Gv4c/jX+S/58/qr+zf7w/gT/Mv9J/2n/
gf+j/8T/4f/9/xAAKQBAAFkAhQCqAM0A4AD2APwAEgEgAUABYQGHAZcBvwG7Ad8B9wEYAkACTQJu
AnUCiwKSArQCxgLcAuEC4AL4AgYDJAMzA08DagNyA3cDVgNsA2oDkwOsA8AD4QPoA/cD/wMFBAkE
FgQkBDsERQRLBEQEOwRABDwEPAQ0BDYEQARJBGIEbAR7BHAEagRnBGEEbARTBFYERgRHBFEEXARa
BFQESQRFBGcEZQR3BFwEUwRJBFAEWARVBEwERAQ/BEAELwQxBCEELAQnBCYEMAQiBAkE9gPeA+QD
1APCA7YDrQO+A7MDwQOrA64DpgOSA3wDYQNXA2cDbwNuA1UDPwMdAwoD8QLpAt8C3gLgAs8CzwK3
ArgCsgK5Ap8ClAKBAmcCYAJIAlICWgJeAlsCRwIwAiYCIgInAiICHAIVAgIC8wHdAc0BxwHAAcwB
wgHMAbMBpwGjAaQBqQGfAYUBhAFzAXwBeAF1AXkBcAF+AYEBhgF4AXMBYgFjAQ==

View file

@ -8,6 +8,8 @@
#include "sfizz/FileMetadata.h"
#include "sfizz/MathHelpers.h"
#include "catch2/catch.hpp"
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
@ -51,10 +53,10 @@ TEST_CASE("[Wavetables] Frequency ranges")
TEST_CASE("[Wavetables] Wavetable sound files: Surge")
{
sfz::FileMetadataReader reader;
sfz::FileMetadataReader reader { "tests/TestFiles/wavetables/surge.wav" };
sfz::WavetableInfo wt;
REQUIRE(reader.open("tests/TestFiles/wavetables/surge.wav"));
REQUIRE(reader.open());
REQUIRE(reader.extractWavetableInfo(wt));
REQUIRE(wt.tableSize == 256);
@ -62,10 +64,10 @@ TEST_CASE("[Wavetables] Wavetable sound files: Surge")
TEST_CASE("[Wavetables] Wavetable sound files: Clm")
{
sfz::FileMetadataReader reader;
sfz::FileMetadataReader reader { "tests/TestFiles/wavetables/clm.wav" };
sfz::WavetableInfo wt;
REQUIRE(reader.open("tests/TestFiles/wavetables/clm.wav"));
REQUIRE(reader.open());
REQUIRE(reader.extractWavetableInfo(wt));
REQUIRE(wt.tableSize == 256);
@ -73,9 +75,57 @@ TEST_CASE("[Wavetables] Wavetable sound files: Clm")
TEST_CASE("[Wavetables] Non-wavetable sound files")
{
sfz::FileMetadataReader reader;
sfz::FileMetadataReader reader { "tests/TestFiles/snare.wav" };
sfz::WavetableInfo wt;
REQUIRE(reader.open("tests/TestFiles/snare.wav"));
REQUIRE(reader.open());
REQUIRE(!reader.extractWavetableInfo(wt));
}
std::vector<char> readWholeFile(const fs::path& path)
{
std::ifstream file(path, std::ios::binary | std::ios::ate);
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> buffer(size);
if (!file.read(buffer.data(), size))
buffer.clear();
return buffer;
}
TEST_CASE("[Wavetables] Wavetable sound files: Surge, from memory")
{
auto file = readWholeFile("tests/TestFiles/wavetables/surge.wav");
sfz::MemoryMetadataReader reader { file.data(), file.size() };
sfz::WavetableInfo wt;
REQUIRE(reader.open());
REQUIRE(reader.extractWavetableInfo(wt));
REQUIRE(wt.tableSize == 256);
}
TEST_CASE("[Wavetables] Wavetable sound files: Clm, from memory")
{
auto file = readWholeFile("tests/TestFiles/wavetables/clm.wav");
sfz::MemoryMetadataReader reader { file.data(), file.size() };
sfz::WavetableInfo wt;
REQUIRE(reader.open());
REQUIRE(reader.extractWavetableInfo(wt));
REQUIRE(wt.tableSize == 256);
}
TEST_CASE("[Wavetables] Non-wavetable sound files, from memory")
{
auto file = readWholeFile("tests/TestFiles/snare.wav");
sfz::MemoryMetadataReader reader { file.data(), file.size() };
sfz::WavetableInfo wt;
REQUIRE(reader.open());
REQUIRE(!reader.extractWavetableInfo(wt));
}