Merge pull request #185 from jpcima/reverse
Support the direction opcode
This commit is contained in:
commit
69badd9473
11 changed files with 256 additions and 131 deletions
|
|
@ -5,6 +5,7 @@ add_subdirectory(external/cpuid)
|
||||||
|
|
||||||
set (SFIZZ_SOURCES
|
set (SFIZZ_SOURCES
|
||||||
sfizz/Synth.cpp
|
sfizz/Synth.cpp
|
||||||
|
sfizz/FileId.cpp
|
||||||
sfizz/FilePool.cpp
|
sfizz/FilePool.cpp
|
||||||
sfizz/FilterPool.cpp
|
sfizz/FilterPool.cpp
|
||||||
sfizz/EQPool.cpp
|
sfizz/EQPool.cpp
|
||||||
|
|
|
||||||
24
src/sfizz/FileId.cpp
Normal file
24
src/sfizz/FileId.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#include "FileId.h"
|
||||||
|
#include "StringViewHelpers.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
size_t std::hash<sfz::FileId>::operator()(const sfz::FileId &id) const
|
||||||
|
{
|
||||||
|
uint64_t h = ::hash(id.filename);
|
||||||
|
h = ::hash(id.reverse ? "!" : "", h);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream &operator<<(std::ostream &os, const sfz::FileId &fileId)
|
||||||
|
{
|
||||||
|
os << fileId.filename;
|
||||||
|
if (fileId.reverse)
|
||||||
|
os << " (reverse)";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
67
src/sfizz/FileId.h
Normal file
67
src/sfizz/FileId.h
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <iosfwd>
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sample file identifier within a file pool.
|
||||||
|
*/
|
||||||
|
struct FileId {
|
||||||
|
std::string filename;
|
||||||
|
bool reverse = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a null identifier.
|
||||||
|
*/
|
||||||
|
FileId()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a file identifier, optionally reversed.
|
||||||
|
*
|
||||||
|
* @param filename
|
||||||
|
* @param reverse
|
||||||
|
*/
|
||||||
|
FileId(std::string filename, bool reverse = false)
|
||||||
|
: filename(std::move(filename)), reverse(reverse)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check equality with another identifier.
|
||||||
|
*
|
||||||
|
* @param other
|
||||||
|
*/
|
||||||
|
bool operator==(const FileId &other) const
|
||||||
|
{
|
||||||
|
return reverse == other.reverse && filename == other.filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check inequality with another identifier.
|
||||||
|
*
|
||||||
|
* @param other
|
||||||
|
*/
|
||||||
|
bool operator!=(const FileId &other) const
|
||||||
|
{
|
||||||
|
return !operator==(other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template <> struct hash<sfz::FileId> {
|
||||||
|
size_t operator()(const sfz::FileId &id) const;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream &operator<<(std::ostream &os, const sfz::FileId &fileId);
|
||||||
|
|
@ -32,32 +32,47 @@
|
||||||
#include "absl/types/span.h"
|
#include "absl/types/span.h"
|
||||||
#include "absl/strings/match.h"
|
#include "absl/strings/match.h"
|
||||||
#include "absl/memory/memory.h"
|
#include "absl/memory/memory.h"
|
||||||
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sndfile.hh>
|
#include <sndfile.hh>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer<T>& output, uint32_t numFrames)
|
void readBaseFile(SndfileHandle& sndFile, sfz::AudioBuffer<T>& output, uint32_t numFrames, bool reverse)
|
||||||
{
|
{
|
||||||
output.reset();
|
output.reset();
|
||||||
output.resize(numFrames);
|
output.resize(numFrames);
|
||||||
if (sndFile.channels() == 1) {
|
|
||||||
|
if (reverse)
|
||||||
|
sndFile.seek(-static_cast<sf_count_t>(numFrames), SEEK_END);
|
||||||
|
|
||||||
|
const unsigned channels = sndFile.channels();
|
||||||
|
|
||||||
|
if (channels == 1) {
|
||||||
output.addChannel();
|
output.addChannel();
|
||||||
sndFile.readf(output.channelWriter(0), numFrames);
|
sndFile.readf(output.channelWriter(0), numFrames);
|
||||||
} else if (sndFile.channels() == 2) {
|
} else if (channels == 2) {
|
||||||
output.addChannel();
|
output.addChannel();
|
||||||
output.addChannel();
|
output.addChannel();
|
||||||
sfz::Buffer<T> tempReadBuffer { 2 * numFrames };
|
sfz::Buffer<T> tempReadBuffer { 2 * numFrames };
|
||||||
sndFile.readf(tempReadBuffer.data(), numFrames);
|
sndFile.readf(tempReadBuffer.data(), numFrames);
|
||||||
sfz::readInterleaved<T>(tempReadBuffer, output.getSpan(0), output.getSpan(1));
|
sfz::readInterleaved<T>(tempReadBuffer, output.getSpan(0), output.getSpan(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reverse) {
|
||||||
|
for (unsigned c = 0; c < channels; ++c) {
|
||||||
|
// TODO: consider optimizing with SIMD
|
||||||
|
absl::Span<float> channel = output.getSpan(c);
|
||||||
|
std::reverse(channel.begin(), channel.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
std::unique_ptr<sfz::AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor)
|
std::unique_ptr<sfz::AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse)
|
||||||
{
|
{
|
||||||
auto baseBuffer = absl::make_unique<sfz::AudioBuffer<T>>();
|
auto baseBuffer = absl::make_unique<sfz::AudioBuffer<T>>();
|
||||||
readBaseFile(sndFile, *baseBuffer, numFrames);
|
readBaseFile(sndFile, *baseBuffer, numFrames, reverse);
|
||||||
|
|
||||||
if (factor == sfz::Oversampling::x1)
|
if (factor == sfz::Oversampling::x1)
|
||||||
return baseBuffer;
|
return baseBuffer;
|
||||||
|
|
@ -69,16 +84,16 @@ std::unique_ptr<sfz::AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, sfz::AudioBuffer<float>& output, std::atomic<size_t>* filledFrames=nullptr)
|
void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor, bool reverse, sfz::AudioBuffer<float>& output, std::atomic<size_t>* filledFrames = nullptr)
|
||||||
{
|
{
|
||||||
if (factor == sfz::Oversampling::x1) {
|
if (factor == sfz::Oversampling::x1) {
|
||||||
readBaseFile(sndFile, output, numFrames);
|
readBaseFile(sndFile, output, numFrames, reverse);
|
||||||
if (filledFrames != nullptr)
|
if (filledFrames != nullptr)
|
||||||
filledFrames->store(numFrames);
|
filledFrames->store(numFrames);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto baseBuffer = readFromFile<T>(sndFile, numFrames, sfz::Oversampling::x1);
|
auto baseBuffer = readFromFile<T>(sndFile, numFrames, sfz::Oversampling::x1, reverse);
|
||||||
output.reset();
|
output.reset();
|
||||||
output.addChannels(baseBuffer->getNumChannels());
|
output.addChannels(baseBuffer->getNumChannels());
|
||||||
output.resize(numFrames * static_cast<int>(factor));
|
output.resize(numFrames * static_cast<int>(factor));
|
||||||
|
|
@ -175,16 +190,16 @@ bool sfz::FilePool::checkSample(std::string& filename) const noexcept
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const std::string& filename) noexcept
|
absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const FileId& fileId) noexcept
|
||||||
{
|
{
|
||||||
fs::path file { rootDirectory / filename };
|
const fs::path file { rootDirectory / fileId.filename };
|
||||||
|
|
||||||
if (!fs::exists(file))
|
if (!fs::exists(file))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
SndfileHandle sndFile(file.string().c_str());
|
SndfileHandle sndFile(file.string().c_str());
|
||||||
if (sndFile.channels() != 1 && sndFile.channels() != 2) {
|
if (sndFile.channels() != 1 && sndFile.channels() != 2) {
|
||||||
DBG("[sfizz] Missing logic for " << sndFile.channels() << " channels, discarding sample " << filename);
|
DBG("[sfizz] Missing logic for " << sndFile.channels() << " channels, discarding sample " << fileId);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,23 +208,28 @@ absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const std
|
||||||
returnedValue.sampleRate = static_cast<double>(sndFile.samplerate());
|
returnedValue.sampleRate = static_cast<double>(sndFile.samplerate());
|
||||||
returnedValue.numChannels = sndFile.channels();
|
returnedValue.numChannels = sndFile.channels();
|
||||||
|
|
||||||
SF_INSTRUMENT instrumentInfo;
|
if (!fileId.reverse) {
|
||||||
sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo));
|
SF_INSTRUMENT instrumentInfo;
|
||||||
if (instrumentInfo.loop_count > 0) {
|
sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo));
|
||||||
returnedValue.loopBegin = instrumentInfo.loops[0].start;
|
if (instrumentInfo.loop_count > 0) {
|
||||||
returnedValue.loopEnd = min(returnedValue.end, instrumentInfo.loops[0].end - 1);
|
returnedValue.loopBegin = instrumentInfo.loops[0].start;
|
||||||
|
returnedValue.loopEnd = min(returnedValue.end, instrumentInfo.loops[0].end - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO loops ignored when reversed
|
||||||
|
// prehaps it can make use of SF_LOOP_BACKWARD?
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnedValue;
|
return returnedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) noexcept
|
bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexcept
|
||||||
{
|
{
|
||||||
fs::path file { rootDirectory / filename };
|
auto fileInformation = getFileInformation(fileId);
|
||||||
auto fileInformation = getFileInformation(filename);
|
|
||||||
if (!fileInformation)
|
if (!fileInformation)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
const fs::path file { rootDirectory / fileId.filename };
|
||||||
SndfileHandle sndFile(file.string().c_str());
|
SndfileHandle sndFile(file.string().c_str());
|
||||||
|
|
||||||
// FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same
|
// FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same
|
||||||
|
|
@ -221,69 +241,69 @@ bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset)
|
||||||
return min(frames, maxOffset + preloadSize);
|
return min(frames, maxOffset + preloadSize);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
const auto existingFile = preloadedFiles.find(filename);
|
const auto existingFile = preloadedFiles.find(fileId);
|
||||||
if (existingFile != preloadedFiles.end()) {
|
if (existingFile != preloadedFiles.end()) {
|
||||||
if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) {
|
if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) {
|
||||||
preloadedFiles[filename].preloadedData = readFromFile<float>(sndFile, framesToLoad, oversamplingFactor);
|
preloadedFiles[fileId].preloadedData = readFromFile<float>(sndFile, framesToLoad, oversamplingFactor, fileId.reverse);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate());
|
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate());
|
||||||
FileDataHandle handle {
|
FileDataHandle handle {
|
||||||
readFromFile<float>(sndFile, framesToLoad, oversamplingFactor),
|
readFromFile<float>(sndFile, framesToLoad, oversamplingFactor, fileId.reverse),
|
||||||
*fileInformation
|
*fileInformation
|
||||||
};
|
};
|
||||||
preloadedFiles.insert_or_assign(filename, handle);
|
preloadedFiles.insert_or_assign(fileId, handle);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::optional<sfz::FileDataHandle> sfz::FilePool::loadFile(const std::string& filename) noexcept
|
absl::optional<sfz::FileDataHandle> sfz::FilePool::loadFile(const FileId& fileId) noexcept
|
||||||
{
|
{
|
||||||
fs::path file { rootDirectory / filename };
|
auto fileInformation = getFileInformation(fileId);
|
||||||
auto fileInformation = getFileInformation(filename);
|
|
||||||
if (!fileInformation)
|
if (!fileInformation)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
const fs::path file { rootDirectory / fileId.filename };
|
||||||
SndfileHandle sndFile(file.string().c_str());
|
SndfileHandle sndFile(file.string().c_str());
|
||||||
|
|
||||||
// FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same
|
// FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same
|
||||||
const auto frames = static_cast<uint32_t>(sndFile.frames());
|
const auto frames = static_cast<uint32_t>(sndFile.frames());
|
||||||
const auto existingFile = loadedFiles.find(filename);
|
const auto existingFile = loadedFiles.find(fileId);
|
||||||
if (existingFile != loadedFiles.end()) {
|
if (existingFile != loadedFiles.end()) {
|
||||||
return existingFile->second;
|
return existingFile->second;
|
||||||
} else {
|
} else {
|
||||||
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate());
|
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate());
|
||||||
FileDataHandle handle {
|
FileDataHandle handle {
|
||||||
readFromFile<float>(sndFile, frames, oversamplingFactor),
|
readFromFile<float>(sndFile, frames, oversamplingFactor, fileId.reverse),
|
||||||
*fileInformation
|
*fileInformation
|
||||||
};
|
};
|
||||||
loadedFiles.insert_or_assign(filename, handle);
|
loadedFiles.insert_or_assign(fileId, handle);
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept
|
sfz::FilePromisePtr sfz::FilePool::getFilePromise(const FileId& fileId) noexcept
|
||||||
{
|
{
|
||||||
if (emptyPromises.empty()) {
|
if (emptyPromises.empty()) {
|
||||||
DBG("[sfizz] No empty promises left to honor the one for " << filename);
|
DBG("[sfizz] No empty promises left to honor the one for " << fileId);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto preloaded = preloadedFiles.find(filename);
|
const auto preloaded = preloadedFiles.find(fileId);
|
||||||
if (preloaded == preloadedFiles.end()) {
|
if (preloaded == preloadedFiles.end()) {
|
||||||
DBG("[sfizz] File not found in the preloaded files: " << filename);
|
DBG("[sfizz] File not found in the preloaded files: " << fileId);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto promise = emptyPromises.back();
|
auto promise = emptyPromises.back();
|
||||||
promise->filename = preloaded->first;
|
promise->fileId = preloaded->first;
|
||||||
promise->preloadedData = preloaded->second.preloadedData;
|
promise->preloadedData = preloaded->second.preloadedData;
|
||||||
promise->sampleRate = static_cast<float>(preloaded->second.information.sampleRate);
|
promise->sampleRate = static_cast<float>(preloaded->second.information.sampleRate);
|
||||||
promise->oversamplingFactor = oversamplingFactor;
|
promise->oversamplingFactor = oversamplingFactor;
|
||||||
promise->creationTime = std::chrono::high_resolution_clock::now();
|
promise->creationTime = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
if (!promiseQueue.try_push(promise)) {
|
if (!promiseQueue.try_push(promise)) {
|
||||||
DBG("[sfizz] Could not enqueue the promise for " << filename << " (queue capacity " << promiseQueue.capacity() << ")");
|
DBG("[sfizz] Could not enqueue the promise for " << fileId << " (queue capacity " << promiseQueue.capacity() << ")");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,9 +322,9 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept
|
||||||
for (auto& preloadedFile : preloadedFiles) {
|
for (auto& preloadedFile : preloadedFiles) {
|
||||||
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(oversamplingFactor);
|
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(oversamplingFactor);
|
||||||
const auto maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0;
|
const auto maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0;
|
||||||
fs::path file { rootDirectory / std::string(preloadedFile.first) };
|
fs::path file { rootDirectory / preloadedFile.first.filename };
|
||||||
SndfileHandle sndFile(file.string().c_str());
|
SndfileHandle sndFile(file.string().c_str());
|
||||||
preloadedFile.second.preloadedData = readFromFile<float>(sndFile, preloadSize + maxOffset, oversamplingFactor);
|
preloadedFile.second.preloadedData = readFromFile<float>(sndFile, preloadSize + maxOffset, oversamplingFactor, preloadedFile.first.reverse);
|
||||||
}
|
}
|
||||||
this->preloadSize = preloadSize;
|
this->preloadSize = preloadSize;
|
||||||
}
|
}
|
||||||
|
|
@ -352,23 +372,23 @@ void sfz::FilePool::loadingThread() noexcept
|
||||||
const auto loadStartTime = std::chrono::high_resolution_clock::now();
|
const auto loadStartTime = std::chrono::high_resolution_clock::now();
|
||||||
const auto waitDuration = loadStartTime - promise->creationTime;
|
const auto waitDuration = loadStartTime - promise->creationTime;
|
||||||
|
|
||||||
fs::path file { rootDirectory / std::string(promise->filename) };
|
const fs::path file { rootDirectory / promise->fileId.filename };
|
||||||
SndfileHandle sndFile(file.string().c_str());
|
SndfileHandle sndFile(file.string().c_str());
|
||||||
if (sndFile.error() != 0) {
|
if (sndFile.error() != 0) {
|
||||||
DBG("[sfizz] libsndfile errored for " << promise->filename << " with message " << sndFile.strError());
|
DBG("[sfizz] libsndfile errored for " << promise->fileId << " with message " << sndFile.strError());
|
||||||
promise->dataStatus = FilePromise::DataStatus::Error;
|
promise->dataStatus = FilePromise::DataStatus::Error;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto frames = static_cast<uint32_t>(sndFile.frames());
|
const auto frames = static_cast<uint32_t>(sndFile.frames());
|
||||||
streamFromFile<float>(sndFile, frames, oversamplingFactor, promise->fileData, &promise->availableFrames);
|
streamFromFile<float>(sndFile, frames, oversamplingFactor, promise->fileId.reverse, promise->fileData, &promise->availableFrames);
|
||||||
promise->dataStatus = FilePromise::DataStatus::Ready;
|
promise->dataStatus = FilePromise::DataStatus::Ready;
|
||||||
const auto loadDuration = std::chrono::high_resolution_clock::now() - loadStartTime;
|
const auto loadDuration = std::chrono::high_resolution_clock::now() - loadStartTime;
|
||||||
logger.logFileTime(waitDuration, loadDuration, frames, promise->filename);
|
logger.logFileTime(waitDuration, loadDuration, frames, promise->fileId.filename);
|
||||||
|
|
||||||
threadsLoading--;
|
threadsLoading--;
|
||||||
|
|
||||||
while (!filledPromiseQueue.try_push(promise)) {
|
while (!filledPromiseQueue.try_push(promise)) {
|
||||||
DBG("[sfizz] Error enqueuing the promise for " << promise->filename << " in the filledPromiseQueue");
|
DBG("[sfizz] Error enqueuing the promise for " << promise->fileId << " in the filledPromiseQueue");
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -431,9 +451,9 @@ void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept
|
||||||
for (auto& preloadedFile : preloadedFiles) {
|
for (auto& preloadedFile : preloadedFiles) {
|
||||||
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(this->oversamplingFactor);
|
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(this->oversamplingFactor);
|
||||||
const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0;
|
const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0;
|
||||||
fs::path file { rootDirectory / std::string(preloadedFile.first) };
|
fs::path file { rootDirectory / preloadedFile.first.filename };
|
||||||
SndfileHandle sndFile(file.string().c_str());
|
SndfileHandle sndFile(file.string().c_str());
|
||||||
preloadedFile.second.preloadedData = readFromFile<float>(sndFile, preloadSize + maxOffset, factor);
|
preloadedFile.second.preloadedData = readFromFile<float>(sndFile, preloadSize + maxOffset, factor, preloadedFile.first.reverse);
|
||||||
preloadedFile.second.information.sampleRate *= samplerateChange;
|
preloadedFile.second.information.sampleRate *= samplerateChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#include "RTSemaphore.h"
|
#include "RTSemaphore.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
#include "AudioSpan.h"
|
#include "AudioSpan.h"
|
||||||
|
#include "FileId.h"
|
||||||
#include "SIMDHelpers.h"
|
#include "SIMDHelpers.h"
|
||||||
#include "ghc/fs_std.hpp"
|
#include "ghc/fs_std.hpp"
|
||||||
#include <absl/container/flat_hash_map.h>
|
#include <absl/container/flat_hash_map.h>
|
||||||
|
|
@ -44,7 +45,6 @@
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>;
|
using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>;
|
||||||
|
|
||||||
|
|
||||||
struct FileInformation {
|
struct FileInformation {
|
||||||
uint32_t end { Default::sampleEndRange.getEnd() };
|
uint32_t end { Default::sampleEndRange.getEnd() };
|
||||||
uint32_t loopBegin { Default::loopRange.getStart() };
|
uint32_t loopBegin { Default::loopRange.getStart() };
|
||||||
|
|
@ -76,7 +76,7 @@ struct FilePromise
|
||||||
{
|
{
|
||||||
fileData.reset();
|
fileData.reset();
|
||||||
preloadedData.reset();
|
preloadedData.reset();
|
||||||
filename = "";
|
fileId = FileId {};
|
||||||
availableFrames = 0;
|
availableFrames = 0;
|
||||||
dataStatus = DataStatus::Wait;
|
dataStatus = DataStatus::Wait;
|
||||||
oversamplingFactor = config::defaultOversamplingFactor;
|
oversamplingFactor = config::defaultOversamplingFactor;
|
||||||
|
|
@ -95,7 +95,7 @@ struct FilePromise
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
absl::string_view filename {};
|
FileId fileId {};
|
||||||
AudioBufferPtr preloadedData {};
|
AudioBufferPtr preloadedData {};
|
||||||
AudioBuffer<float> fileData {};
|
AudioBuffer<float> fileData {};
|
||||||
float sampleRate { config::defaultSampleRate };
|
float sampleRate { config::defaultSampleRate };
|
||||||
|
|
@ -155,30 +155,30 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Get metadata information about a file.
|
* @brief Get metadata information about a file.
|
||||||
*
|
*
|
||||||
* @param filename
|
* @param fileId
|
||||||
* @return absl::optional<FileInformation>
|
* @return absl::optional<FileInformation>
|
||||||
*/
|
*/
|
||||||
absl::optional<FileInformation> getFileInformation(const std::string& filename) noexcept;
|
absl::optional<FileInformation> getFileInformation(const FileId& fileId) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Preload a file with the proper offset bounds
|
* @brief Preload a file with the proper offset bounds
|
||||||
*
|
*
|
||||||
* @param filename
|
* @param fileId
|
||||||
* @param offset the maximum offset to consider for preloading. The total preloaded
|
* @param maxOffset the maximum offset to consider for preloading. The total preloaded
|
||||||
* size will be preloadSize + offset
|
* size will be preloadSize + offset
|
||||||
* @return true if the preloading went fine
|
* @return true if the preloading went fine
|
||||||
* @return false if something went wrong ()
|
* @return false if something went wrong ()
|
||||||
*/
|
*/
|
||||||
bool preloadFile(const std::string& filename, uint32_t maxOffset) noexcept;
|
bool preloadFile(const FileId& fileId, uint32_t maxOffset) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Load a file and return its information. The file pool will store this
|
* @brief Load a file and return its information. The file pool will store this
|
||||||
* data for future requests so use this function responsibly.
|
* data for future requests so use this function responsibly.
|
||||||
*
|
*
|
||||||
* @param filename
|
* @param fileId
|
||||||
* @return A handle on the file data
|
* @return A handle on the file data
|
||||||
*/
|
*/
|
||||||
absl::optional<sfz::FileDataHandle> loadFile(const std::string& filename) noexcept;
|
absl::optional<sfz::FileDataHandle> loadFile(const FileId& fileId) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check that the sample exists. If not, try to find it in a case insensitive way.
|
* @brief Check that the sample exists. If not, try to find it in a case insensitive way.
|
||||||
|
|
@ -204,10 +204,10 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Get a file promise
|
* @brief Get a file promise
|
||||||
*
|
*
|
||||||
* @param filename the file to preload
|
* @param fileId the file to preload
|
||||||
* @return FilePromisePtr a file promise
|
* @return FilePromisePtr a file promise
|
||||||
*/
|
*/
|
||||||
FilePromisePtr getFilePromise(const std::string& filename) noexcept;
|
FilePromisePtr getFilePromise(const FileId& fileId) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Change the preloading size. This will trigger a full
|
* @brief Change the preloading size. This will trigger a full
|
||||||
* reload of all samples, so don't call it on the audio thread.
|
* reload of all samples, so don't call it on the audio thread.
|
||||||
|
|
@ -270,8 +270,8 @@ private:
|
||||||
std::mutex promiseGuard;
|
std::mutex promiseGuard;
|
||||||
|
|
||||||
// Preloaded data
|
// Preloaded data
|
||||||
absl::flat_hash_map<absl::string_view, FileDataHandle> preloadedFiles;
|
absl::flat_hash_map<FileId, FileDataHandle> preloadedFiles;
|
||||||
absl::flat_hash_map<absl::string_view, FileDataHandle> loadedFiles;
|
absl::flat_hash_map<FileId, FileDataHandle> loadedFiles;
|
||||||
std::vector<std::thread> threadPool { };
|
std::vector<std::thread> threadPool { };
|
||||||
LEAK_DETECTOR(FilePool);
|
LEAK_DETECTOR(FilePool);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,14 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (trimmedSample[0] == '*')
|
if (trimmedSample[0] == '*')
|
||||||
sample = std::string(trimmedSample);
|
sampleId.filename = std::string(trimmedSample);
|
||||||
else
|
else
|
||||||
sample = absl::StrCat(defaultPath, absl::StrReplaceAll(trimmedSample, { { "\\", "/" } }));
|
sampleId.filename = absl::StrCat(defaultPath, absl::StrReplaceAll(trimmedSample, { { "\\", "/" } }));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case hash("direction"):
|
||||||
|
sampleId.reverse = opcode.value == "reverse";
|
||||||
|
break;
|
||||||
case hash("delay"):
|
case hash("delay"):
|
||||||
setValueFromOpcode(opcode, delay, Default::delayRange);
|
setValueFromOpcode(opcode, delay, Default::delayRange);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include "Opcode.h"
|
#include "Opcode.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
#include "MidiState.h"
|
#include "MidiState.h"
|
||||||
|
#include "FileId.h"
|
||||||
#include "absl/types/optional.h"
|
#include "absl/types/optional.h"
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -57,7 +58,7 @@ struct Region {
|
||||||
* @return true
|
* @return true
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
bool isGenerator() const noexcept { return sample.size() > 0 ? sample[0] == '*' : false; }
|
bool isGenerator() const noexcept { return sampleId.filename.size() > 0 ? sampleId.filename[0] == '*' : false; }
|
||||||
/**
|
/**
|
||||||
* @brief Is stereo (has stereo sample or is unison oscillator)?
|
* @brief Is stereo (has stereo sample or is unison oscillator)?
|
||||||
*
|
*
|
||||||
|
|
@ -227,7 +228,7 @@ struct Region {
|
||||||
float getGainToEffectBus(unsigned number) const noexcept;
|
float getGainToEffectBus(unsigned number) const noexcept;
|
||||||
|
|
||||||
// Sound source: sample playback
|
// Sound source: sample playback
|
||||||
std::string sample {}; // Sample
|
FileId sampleId {}; // Sample
|
||||||
float delay { Default::delay }; // delay
|
float delay { Default::delay }; // delay
|
||||||
float delayRandom { Default::delayRandom }; // delay_random
|
float delayRandom { Default::delayRandom }; // delay_random
|
||||||
int64_t offset { Default::offset }; // offset
|
int64_t offset { Default::offset }; // offset
|
||||||
|
|
|
||||||
|
|
@ -332,7 +332,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||||
if (currentRegion->get() == nullptr)
|
if (currentRegion->get() == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DBG("Removing the region with sample " << currentRegion->get()->sample);
|
DBG("Removing the region with sample " << currentRegion->get()->sampleId);
|
||||||
std::iter_swap(currentRegion, lastRegion);
|
std::iter_swap(currentRegion, lastRegion);
|
||||||
++lastRegion;
|
++lastRegion;
|
||||||
};
|
};
|
||||||
|
|
@ -344,12 +344,12 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||||
auto region = currentRegion->get();
|
auto region = currentRegion->get();
|
||||||
|
|
||||||
if (!region->oscillator && !region->isGenerator()) {
|
if (!region->oscillator && !region->isGenerator()) {
|
||||||
if (!resources.filePool.checkSample(region->sample)) {
|
if (!resources.filePool.checkSample(region->sampleId.filename)) {
|
||||||
removeCurrentRegion();
|
removeCurrentRegion();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto fileInformation = resources.filePool.getFileInformation(region->sample);
|
const auto fileInformation = resources.filePool.getFileInformation(region->sampleId);
|
||||||
if (!fileInformation) {
|
if (!fileInformation) {
|
||||||
removeCurrentRegion();
|
removeCurrentRegion();
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -381,16 +381,16 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||||
return Default::offsetCCRange.clamp(sumOffsetCC);
|
return Default::offsetCCRange.clamp(sumOffsetCC);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
if (!resources.filePool.preloadFile(region->sample, maxOffset))
|
if (!resources.filePool.preloadFile(region->sampleId, maxOffset))
|
||||||
removeCurrentRegion();
|
removeCurrentRegion();
|
||||||
}
|
}
|
||||||
else if (region->oscillator && !region->isGenerator()) {
|
else if (region->oscillator && !region->isGenerator()) {
|
||||||
if (!resources.filePool.checkSample(region->sample)) {
|
if (!resources.filePool.checkSample(region->sampleId.filename)) {
|
||||||
removeCurrentRegion();
|
removeCurrentRegion();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resources.wavePool.createFileWave(resources.filePool, region->sample)) {
|
if (!resources.wavePool.createFileWave(resources.filePool, region->sampleId.filename)) {
|
||||||
removeCurrentRegion();
|
removeCurrentRegion();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
|
||||||
|
|
||||||
if (region->isGenerator()) {
|
if (region->isGenerator()) {
|
||||||
const WavetableMulti* wave = nullptr;
|
const WavetableMulti* wave = nullptr;
|
||||||
switch (hash(region->sample)) {
|
switch (hash(region->sampleId.filename)) {
|
||||||
default:
|
default:
|
||||||
case hash("*silence"):
|
case hash("*silence"):
|
||||||
break;
|
break;
|
||||||
|
|
@ -64,14 +64,14 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
|
||||||
}
|
}
|
||||||
setupOscillatorUnison();
|
setupOscillatorUnison();
|
||||||
} else if (region->oscillator) {
|
} else if (region->oscillator) {
|
||||||
const WavetableMulti* wave = resources.wavePool.getFileWave(region->sample);
|
const WavetableMulti* wave = resources.wavePool.getFileWave(region->sampleId.filename);
|
||||||
for (WavetableOscillator& osc : waveOscillators) {
|
for (WavetableOscillator& osc : waveOscillators) {
|
||||||
osc.setWavetable(wave);
|
osc.setWavetable(wave);
|
||||||
osc.setPhase(region->getPhase());
|
osc.setPhase(region->getPhase());
|
||||||
}
|
}
|
||||||
setupOscillatorUnison();
|
setupOscillatorUnison();
|
||||||
} else {
|
} else {
|
||||||
currentPromise = resources.filePool.getFilePromise(region->sample);
|
currentPromise = resources.filePool.getFilePromise(region->sampleId);
|
||||||
if (currentPromise == nullptr) {
|
if (currentPromise == nullptr) {
|
||||||
reset();
|
reset();
|
||||||
return;
|
return;
|
||||||
|
|
@ -483,7 +483,7 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
|
||||||
DBG("[sfizz] Underflow: source available samples "
|
DBG("[sfizz] Underflow: source available samples "
|
||||||
<< source.getNumFrames() << "/"
|
<< source.getNumFrames() << "/"
|
||||||
<< region->trueSampleEnd(currentPromise->oversamplingFactor)
|
<< region->trueSampleEnd(currentPromise->oversamplingFactor)
|
||||||
<< " for sample " << region->sample);
|
<< " for sample " << region->sampleId);
|
||||||
}
|
}
|
||||||
fill<int>(indices->last(remainingElements), sampleEnd);
|
fill<int>(indices->last(remainingElements), sampleEnd);
|
||||||
fill<float>(leftCoeffs->last(remainingElements), 0.0f);
|
fill<float>(leftCoeffs->last(remainingElements), 0.0f);
|
||||||
|
|
@ -529,7 +529,7 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
|
||||||
const auto leftSpan = buffer.getSpan(0);
|
const auto leftSpan = buffer.getSpan(0);
|
||||||
const auto rightSpan = buffer.getSpan(1);
|
const auto rightSpan = buffer.getSpan(1);
|
||||||
|
|
||||||
if (region->sample == "*noise") {
|
if (region->sampleId.filename == "*noise") {
|
||||||
absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
||||||
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
112
tests/FilesT.cpp
112
tests/FilesT.cpp
|
|
@ -19,7 +19,7 @@ TEST_CASE("[Files] Single region (regions_one.sfz)")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_one.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_one.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 1);
|
REQUIRE(synth.getNumRegions() == 1);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -28,9 +28,9 @@ TEST_CASE("[Files] Multiple regions (regions_many.sfz)")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_many.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_many.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 3);
|
REQUIRE(synth.getNumRegions() == 3);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "dummy.1.wav");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy.1.wav");
|
||||||
REQUIRE(synth.getRegionView(2)->sample == "dummy.2.wav");
|
REQUIRE(synth.getRegionView(2)->sampleId.filename == "dummy.2.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Basic opcodes (regions_opcodes.sfz)")
|
TEST_CASE("[Files] Basic opcodes (regions_opcodes.sfz)")
|
||||||
|
|
@ -54,8 +54,8 @@ TEST_CASE("[Files] (regions_bad.sfz)")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_bad.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_bad.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 2);
|
REQUIRE(synth.getNumRegions() == 2);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "dummy.wav");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Local include")
|
TEST_CASE("[Files] Local include")
|
||||||
|
|
@ -63,7 +63,7 @@ TEST_CASE("[Files] Local include")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_local.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_local.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 1);
|
REQUIRE(synth.getNumRegions() == 1);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Multiple includes")
|
TEST_CASE("[Files] Multiple includes")
|
||||||
|
|
@ -71,8 +71,8 @@ TEST_CASE("[Files] Multiple includes")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/multiple_includes.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/multiple_includes.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 2);
|
REQUIRE(synth.getNumRegions() == 2);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "dummy2.wav");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy2.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Multiple includes with comments")
|
TEST_CASE("[Files] Multiple includes with comments")
|
||||||
|
|
@ -80,8 +80,8 @@ TEST_CASE("[Files] Multiple includes with comments")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/multiple_includes_with_comments.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/multiple_includes_with_comments.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 2);
|
REQUIRE(synth.getNumRegions() == 2);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "dummy2.wav");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy2.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Subdir include")
|
TEST_CASE("[Files] Subdir include")
|
||||||
|
|
@ -89,7 +89,7 @@ TEST_CASE("[Files] Subdir include")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_subdir.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_subdir.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 1);
|
REQUIRE(synth.getNumRegions() == 1);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy_subdir.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy_subdir.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Subdir include Win")
|
TEST_CASE("[Files] Subdir include Win")
|
||||||
|
|
@ -97,7 +97,7 @@ TEST_CASE("[Files] Subdir include Win")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_subdir_win.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_subdir_win.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 1);
|
REQUIRE(synth.getNumRegions() == 1);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy_subdir.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy_subdir.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Recursive include (with include guard)")
|
TEST_CASE("[Files] Recursive include (with include guard)")
|
||||||
|
|
@ -107,8 +107,8 @@ TEST_CASE("[Files] Recursive include (with include guard)")
|
||||||
parser.setRecursiveIncludeGuardEnabled(true);
|
parser.setRecursiveIncludeGuardEnabled(true);
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_recursive.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_recursive.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 2);
|
REQUIRE(synth.getNumRegions() == 2);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy_recursive2.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy_recursive2.wav");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "dummy_recursive1.wav");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy_recursive1.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Include loops (with include guard)")
|
TEST_CASE("[Files] Include loops (with include guard)")
|
||||||
|
|
@ -118,8 +118,8 @@ TEST_CASE("[Files] Include loops (with include guard)")
|
||||||
parser.setRecursiveIncludeGuardEnabled(true);
|
parser.setRecursiveIncludeGuardEnabled(true);
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_loop.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_loop.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 2);
|
REQUIRE(synth.getNumRegions() == 2);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy_loop2.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy_loop2.wav");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "dummy_loop1.wav");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "dummy_loop1.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Define test")
|
TEST_CASE("[Files] Define test")
|
||||||
|
|
@ -205,28 +205,28 @@ TEST_CASE("[Files] Full hierarchy with antislashes")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 8);
|
REQUIRE(synth.getNumRegions() == 8);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "Regions/dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "Regions/dummy.1.wav");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "Regions/dummy.1.wav");
|
||||||
REQUIRE(synth.getRegionView(2)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(2)->sampleId.filename == "Regions/dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(3)->sample == "Regions/dummy.1.wav");
|
REQUIRE(synth.getRegionView(3)->sampleId.filename == "Regions/dummy.1.wav");
|
||||||
REQUIRE(synth.getRegionView(4)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(4)->sampleId.filename == "Regions/dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(5)->sample == "Regions/dummy.1.wav");
|
REQUIRE(synth.getRegionView(5)->sampleId.filename == "Regions/dummy.1.wav");
|
||||||
REQUIRE(synth.getRegionView(6)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(6)->sampleId.filename == "Regions/dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(7)->sample == "Regions/dummy.1.wav");
|
REQUIRE(synth.getRegionView(7)->sampleId.filename == "Regions/dummy.1.wav");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy_antislash.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy_antislash.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 8);
|
REQUIRE(synth.getNumRegions() == 8);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "Regions/dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "Regions/dummy.1.wav");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "Regions/dummy.1.wav");
|
||||||
REQUIRE(synth.getRegionView(2)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(2)->sampleId.filename == "Regions/dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(3)->sample == "Regions/dummy.1.wav");
|
REQUIRE(synth.getRegionView(3)->sampleId.filename == "Regions/dummy.1.wav");
|
||||||
REQUIRE(synth.getRegionView(4)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(4)->sampleId.filename == "Regions/dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(5)->sample == "Regions/dummy.1.wav");
|
REQUIRE(synth.getRegionView(5)->sampleId.filename == "Regions/dummy.1.wav");
|
||||||
REQUIRE(synth.getRegionView(6)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(6)->sampleId.filename == "Regions/dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(7)->sample == "Regions/dummy.1.wav");
|
REQUIRE(synth.getRegionView(7)->sampleId.filename == "Regions/dummy.1.wav");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,10 +245,10 @@ TEST_CASE("[Files] Pizz basic")
|
||||||
REQUIRE(synth.getRegionView(1)->randRange == sfz::Range<float>(0.25, 0.5));
|
REQUIRE(synth.getRegionView(1)->randRange == sfz::Range<float>(0.25, 0.5));
|
||||||
REQUIRE(synth.getRegionView(2)->randRange == sfz::Range<float>(0.5, 0.75));
|
REQUIRE(synth.getRegionView(2)->randRange == sfz::Range<float>(0.5, 0.75));
|
||||||
REQUIRE(synth.getRegionView(3)->randRange == sfz::Range<float>(0.75, 1.0));
|
REQUIRE(synth.getRegionView(3)->randRange == sfz::Range<float>(0.75, 1.0));
|
||||||
REQUIRE(synth.getRegionView(0)->sample == R"(../Samples/pizz/a0_vl4_rr1.wav)");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == R"(../Samples/pizz/a0_vl4_rr1.wav)");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == R"(../Samples/pizz/a0_vl4_rr2.wav)");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == R"(../Samples/pizz/a0_vl4_rr2.wav)");
|
||||||
REQUIRE(synth.getRegionView(2)->sample == R"(../Samples/pizz/a0_vl4_rr3.wav)");
|
REQUIRE(synth.getRegionView(2)->sampleId.filename == R"(../Samples/pizz/a0_vl4_rr3.wav)");
|
||||||
REQUIRE(synth.getRegionView(3)->sample == R"(../Samples/pizz/a0_vl4_rr4.wav)");
|
REQUIRE(synth.getRegionView(3)->sampleId.filename == R"(../Samples/pizz/a0_vl4_rr4.wav)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Channels (channels.sfz)")
|
TEST_CASE("[Files] Channels (channels.sfz)")
|
||||||
|
|
@ -256,9 +256,9 @@ TEST_CASE("[Files] Channels (channels.sfz)")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 2);
|
REQUIRE(synth.getNumRegions() == 2);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "mono_sample.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "mono_sample.wav");
|
||||||
REQUIRE(!synth.getRegionView(0)->isStereo());
|
REQUIRE(!synth.getRegionView(0)->isStereo());
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "stereo_sample.wav");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "stereo_sample.wav");
|
||||||
REQUIRE(synth.getRegionView(1)->isStereo());
|
REQUIRE(synth.getRegionView(1)->isStereo());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,32 +268,32 @@ TEST_CASE("[Files] Channels (channels_multi.sfz)")
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels_multi.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels_multi.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 6);
|
REQUIRE(synth.getNumRegions() == 6);
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "*sine");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "*sine");
|
||||||
REQUIRE(!synth.getRegionView(0)->isStereo());
|
REQUIRE(!synth.getRegionView(0)->isStereo());
|
||||||
REQUIRE(synth.getRegionView(0)->isGenerator());
|
REQUIRE(synth.getRegionView(0)->isGenerator());
|
||||||
REQUIRE(!synth.getRegionView(0)->oscillator);
|
REQUIRE(!synth.getRegionView(0)->oscillator);
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "*sine");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "*sine");
|
||||||
REQUIRE(synth.getRegionView(1)->isStereo());
|
REQUIRE(synth.getRegionView(1)->isStereo());
|
||||||
REQUIRE(synth.getRegionView(1)->isGenerator());
|
REQUIRE(synth.getRegionView(1)->isGenerator());
|
||||||
REQUIRE(!synth.getRegionView(1)->oscillator);
|
REQUIRE(!synth.getRegionView(1)->oscillator);
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(2)->sample == "ramp_wave.wav");
|
REQUIRE(synth.getRegionView(2)->sampleId.filename == "ramp_wave.wav");
|
||||||
REQUIRE(!synth.getRegionView(2)->isStereo());
|
REQUIRE(!synth.getRegionView(2)->isStereo());
|
||||||
REQUIRE(!synth.getRegionView(2)->isGenerator());
|
REQUIRE(!synth.getRegionView(2)->isGenerator());
|
||||||
REQUIRE(synth.getRegionView(2)->oscillator);
|
REQUIRE(synth.getRegionView(2)->oscillator);
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(3)->sample == "ramp_wave.wav");
|
REQUIRE(synth.getRegionView(3)->sampleId.filename == "ramp_wave.wav");
|
||||||
REQUIRE(synth.getRegionView(3)->isStereo());
|
REQUIRE(synth.getRegionView(3)->isStereo());
|
||||||
REQUIRE(!synth.getRegionView(3)->isGenerator());
|
REQUIRE(!synth.getRegionView(3)->isGenerator());
|
||||||
REQUIRE(synth.getRegionView(3)->oscillator);
|
REQUIRE(synth.getRegionView(3)->oscillator);
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(4)->sample == "*sine");
|
REQUIRE(synth.getRegionView(4)->sampleId.filename == "*sine");
|
||||||
REQUIRE(!synth.getRegionView(4)->isStereo());
|
REQUIRE(!synth.getRegionView(4)->isStereo());
|
||||||
REQUIRE(synth.getRegionView(4)->isGenerator());
|
REQUIRE(synth.getRegionView(4)->isGenerator());
|
||||||
REQUIRE(!synth.getRegionView(4)->oscillator);
|
REQUIRE(!synth.getRegionView(4)->oscillator);
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(5)->sample == "*sine");
|
REQUIRE(synth.getRegionView(5)->sampleId.filename == "*sine");
|
||||||
REQUIRE(!synth.getRegionView(5)->isStereo());
|
REQUIRE(!synth.getRegionView(5)->isStereo());
|
||||||
REQUIRE(synth.getRegionView(5)->isGenerator());
|
REQUIRE(synth.getRegionView(5)->isGenerator());
|
||||||
REQUIRE(!synth.getRegionView(5)->oscillator);
|
REQUIRE(!synth.getRegionView(5)->oscillator);
|
||||||
|
|
@ -359,7 +359,7 @@ TEST_CASE("[Files] Specific bug: relative path with backslashes")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/SpecificBugs/win_backslashes.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/SpecificBugs/win_backslashes.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 1);
|
REQUIRE(synth.getNumRegions() == 1);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == R"(Xylo/Subfolder/closedhat.wav)");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == R"(Xylo/Subfolder/closedhat.wav)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Default path")
|
TEST_CASE("[Files] Default path")
|
||||||
|
|
@ -367,10 +367,10 @@ TEST_CASE("[Files] Default path")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 4);
|
REQUIRE(synth.getNumRegions() == 4);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == R"(DefaultPath/SubPath1/sample1.wav)");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == R"(DefaultPath/SubPath1/sample1.wav)");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == R"(DefaultPath/SubPath2/sample2.wav)");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == R"(DefaultPath/SubPath2/sample2.wav)");
|
||||||
REQUIRE(synth.getRegionView(2)->sample == R"(DefaultPath/SubPath1/sample1.wav)");
|
REQUIRE(synth.getRegionView(2)->sampleId.filename == R"(DefaultPath/SubPath1/sample1.wav)");
|
||||||
REQUIRE(synth.getRegionView(3)->sample == R"(DefaultPath/SubPath2/sample2.wav)");
|
REQUIRE(synth.getRegionView(3)->sampleId.filename == R"(DefaultPath/SubPath2/sample2.wav)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Default path reset when calling loadSfzFile again")
|
TEST_CASE("[Files] Default path reset when calling loadSfzFile again")
|
||||||
|
|
@ -380,7 +380,7 @@ TEST_CASE("[Files] Default path reset when calling loadSfzFile again")
|
||||||
REQUIRE(synth.getNumRegions() == 4);
|
REQUIRE(synth.getNumRegions() == 4);
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path_reset.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path_reset.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 1);
|
REQUIRE(synth.getNumRegions() == 1);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == R"(DefaultPath/SubPath2/sample2.wav)");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == R"(DefaultPath/SubPath2/sample2.wav)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Default path is ignored for generators")
|
TEST_CASE("[Files] Default path is ignored for generators")
|
||||||
|
|
@ -388,7 +388,7 @@ TEST_CASE("[Files] Default path is ignored for generators")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path_generator.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path_generator.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 1);
|
REQUIRE(synth.getNumRegions() == 1);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == R"(*sine)");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == R"(*sine)");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Set CC applies properly")
|
TEST_CASE("[Files] Set CC applies properly")
|
||||||
|
|
@ -547,10 +547,10 @@ TEST_CASE("[Files] Case sentitiveness")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.loadSfzFile(sfzFilePath);
|
synth.loadSfzFile(sfzFilePath);
|
||||||
REQUIRE(synth.getNumRegions() == 4);
|
REQUIRE(synth.getNumRegions() == 4);
|
||||||
REQUIRE(synth.getRegionView(0)->sample == "dummy1.wav");
|
REQUIRE(synth.getRegionView(0)->sampleId.filename == "dummy1.wav");
|
||||||
REQUIRE(synth.getRegionView(1)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(1)->sampleId.filename == "Regions/dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(2)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(2)->sampleId.filename == "Regions/dummy.wav");
|
||||||
REQUIRE(synth.getRegionView(3)->sample == "Regions/dummy.wav");
|
REQUIRE(synth.getRegionView(3)->sampleId.filename == "Regions/dummy.wav");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,18 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
|
|
||||||
SECTION("sample")
|
SECTION("sample")
|
||||||
{
|
{
|
||||||
REQUIRE(region.sample == "");
|
REQUIRE(region.sampleId.filename == "");
|
||||||
region.parseOpcode({ "sample", "dummy.wav" });
|
region.parseOpcode({ "sample", "dummy.wav" });
|
||||||
REQUIRE(region.sample == "dummy.wav");
|
REQUIRE(region.sampleId.filename == "dummy.wav");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("direction")
|
||||||
|
{
|
||||||
|
REQUIRE(!region.sampleId.reverse);
|
||||||
|
region.parseOpcode({ "direction", "reverse" });
|
||||||
|
REQUIRE(region.sampleId.reverse);
|
||||||
|
region.parseOpcode({ "direction", "forward" });
|
||||||
|
REQUIRE(!region.sampleId.reverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("delay")
|
SECTION("delay")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue