Merge branch 'develop' of github.com:sfztools/sfizz into develop
This commit is contained in:
commit
38b1eaaaa4
4 changed files with 128 additions and 48 deletions
|
|
@ -40,6 +40,7 @@ namespace config {
|
||||||
constexpr int numBackgroundThreads { 4 };
|
constexpr int numBackgroundThreads { 4 };
|
||||||
constexpr int numVoices { 64 };
|
constexpr int numVoices { 64 };
|
||||||
constexpr int maxVoices { 256 };
|
constexpr int maxVoices { 256 };
|
||||||
|
constexpr int maxFilePromises { maxVoices * 2 };
|
||||||
constexpr int sustainCC { 64 };
|
constexpr int sustainCC { 64 };
|
||||||
constexpr int allSoundOffCC { 120 };
|
constexpr int allSoundOffCC { 120 };
|
||||||
constexpr int resetCC { 121 };
|
constexpr int resetCC { 121 };
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,24 @@ void streamFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversamplin
|
||||||
oversampler.stream(*baseBuffer, output, filledFrames);
|
oversampler.stream(*baseBuffer, output, filledFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sfz::FilePool::FilePool()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < config::numBackgroundThreads; ++i)
|
||||||
|
threadPool.emplace_back( &FilePool::loadingThread, this );
|
||||||
|
|
||||||
|
threadPool.emplace_back( &FilePool::clearingThread, this );
|
||||||
|
|
||||||
|
for (int i = 0; i < config::maxFilePromises; ++i)
|
||||||
|
emptyPromises.push_back(std::make_shared<FilePromise>());
|
||||||
|
}
|
||||||
|
|
||||||
|
sfz::FilePool::~FilePool()
|
||||||
|
{
|
||||||
|
quitThread = true;
|
||||||
|
for (auto& thread: threadPool)
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
absl::optional<sfz::FilePool::FileInformation> sfz::FilePool::getFileInformation(const std::string& filename) noexcept
|
absl::optional<sfz::FilePool::FileInformation> sfz::FilePool::getFileInformation(const std::string& filename) noexcept
|
||||||
{
|
{
|
||||||
fs::path file { rootDirectory / filename };
|
fs::path file { rootDirectory / filename };
|
||||||
|
|
@ -147,15 +165,21 @@ bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset)
|
||||||
|
|
||||||
sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept
|
sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept
|
||||||
{
|
{
|
||||||
auto promise = std::make_shared<FilePromise>();
|
if (emptyPromises.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
const auto preloaded = preloadedFiles.find(filename);
|
const auto preloaded = preloadedFiles.find(filename);
|
||||||
if (preloaded != preloadedFiles.end()) {
|
if (preloaded == preloadedFiles.end())
|
||||||
promise->filename = preloaded->first;
|
return {};
|
||||||
promise->preloadedData = preloaded->second.preloadedData;
|
|
||||||
promise->sampleRate = preloaded->second.sampleRate;
|
auto promise = emptyPromises.back();
|
||||||
promise->oversamplingFactor = oversamplingFactor;
|
promise->filename = preloaded->first;
|
||||||
promiseQueue.try_enqueue(promise);
|
promise->preloadedData = preloaded->second.preloadedData;
|
||||||
}
|
promise->sampleRate = preloaded->second.sampleRate;
|
||||||
|
promise->oversamplingFactor = oversamplingFactor;
|
||||||
|
promiseQueue.try_enqueue(promise);
|
||||||
|
emptyPromises.pop_back();
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +203,10 @@ void sfz::FilePool::tryToClearPromises()
|
||||||
while (addingPromisesToClear)
|
while (addingPromisesToClear)
|
||||||
std::this_thread::sleep_for(1ms);
|
std::this_thread::sleep_for(1ms);
|
||||||
|
|
||||||
promisesToClear.clear();
|
for (auto& promise: promisesToClear) {
|
||||||
|
if (promise->dataReady)
|
||||||
|
promise->reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::FilePool::clearingThread()
|
void sfz::FilePool::clearingThread()
|
||||||
|
|
@ -227,6 +254,7 @@ void sfz::FilePool::loadingThread() noexcept
|
||||||
DBG("Error enqueuing the file for " << promise->filename << " in the filledPromiseQueue");
|
DBG("Error enqueuing the file for " << promise->filename << " in the filledPromiseQueue");
|
||||||
std::this_thread::sleep_for(1ms);
|
std::this_thread::sleep_for(1ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
promise.reset();
|
promise.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -246,21 +274,37 @@ void sfz::FilePool::cleanupPromises() noexcept
|
||||||
if (!canAddPromisesToClear)
|
if (!canAddPromisesToClear)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// The garbage collection cleared the data from these so we can move them
|
||||||
|
// back to the empty queue
|
||||||
|
auto clearedIterator = promisesToClear.begin();
|
||||||
|
auto clearedSentinel = promisesToClear.end() - 1;
|
||||||
|
while (clearedIterator != promisesToClear.end()) {
|
||||||
|
if (clearedIterator->get()->dataReady == false) {
|
||||||
|
emptyPromises.push_back(*clearedIterator);
|
||||||
|
std::iter_swap(clearedIterator, clearedSentinel);
|
||||||
|
clearedSentinel--;
|
||||||
|
promisesToClear.pop_back();
|
||||||
|
} else {
|
||||||
|
clearedIterator++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FilePromisePtr promise;
|
FilePromisePtr promise;
|
||||||
// Remove stuff from the filled queue and put them in a linear storage
|
// Remove the promises from the filled queue and put them in a linear
|
||||||
|
// storage
|
||||||
while (filledPromiseQueue.try_dequeue(promise))
|
while (filledPromiseQueue.try_dequeue(promise))
|
||||||
temporaryFilePromises.push_back(promise);
|
temporaryFilePromises.push_back(promise);
|
||||||
|
|
||||||
auto promiseIterator = temporaryFilePromises.begin();
|
auto filledIterator = temporaryFilePromises.begin();
|
||||||
auto sentinel = temporaryFilePromises.end() - 1;
|
auto filledSentinel = temporaryFilePromises.end() - 1;
|
||||||
while (promiseIterator != temporaryFilePromises.end()) {
|
while (filledIterator != temporaryFilePromises.end()) {
|
||||||
if (promiseIterator->use_count() == 1) {
|
if (filledIterator->use_count() == 1) {
|
||||||
promisesToClear.push_back(*promiseIterator);
|
promisesToClear.push_back(*filledIterator);
|
||||||
std::iter_swap(promiseIterator, sentinel);
|
std::iter_swap(filledIterator, filledSentinel);
|
||||||
sentinel--;
|
filledSentinel--;
|
||||||
temporaryFilePromises.pop_back();
|
temporaryFilePromises.pop_back();
|
||||||
} else {
|
} else {
|
||||||
promiseIterator++;
|
filledIterator++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,50 +58,60 @@ struct FilePromise
|
||||||
return AudioSpan<const float>(*preloadedData);
|
return AudioSpan<const float>(*preloadedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
fileData.reset();
|
||||||
|
preloadedData.reset();
|
||||||
|
filename = "";
|
||||||
|
availableFrames = 0;
|
||||||
|
dataReady = false;
|
||||||
|
oversamplingFactor = config::defaultOversamplingFactor;
|
||||||
|
sampleRate = config::defaultSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
absl::string_view filename {};
|
absl::string_view filename {};
|
||||||
AudioBufferPtr preloadedData {};
|
AudioBufferPtr preloadedData {};
|
||||||
AudioBuffer<float> fileData {};
|
AudioBuffer<float> fileData {};
|
||||||
float sampleRate { config::defaultSampleRate };
|
float sampleRate { config::defaultSampleRate };
|
||||||
|
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
|
||||||
std::atomic_size_t availableFrames { 0 };
|
std::atomic_size_t availableFrames { 0 };
|
||||||
std::atomic<bool> dataReady { false };
|
std::atomic<bool> dataReady { false };
|
||||||
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
|
|
||||||
|
LEAK_DETECTOR(FilePromise);
|
||||||
};
|
};
|
||||||
|
|
||||||
using FilePromisePtr = std::shared_ptr<FilePromise>;
|
using FilePromisePtr = std::shared_ptr<FilePromise>;
|
||||||
/**
|
/**
|
||||||
* @brief This is a singleton-designed class that holds all the preloaded
|
* @brief This is a singleton-designed class that holds all the preloaded data
|
||||||
* data as well as functions to request new file data and collect the file
|
* as well as functions to request new file data and collect the file handles to
|
||||||
* handles to close after they are read.
|
* close after they are read.
|
||||||
*
|
*
|
||||||
* This object caches the file data that was already preloaded in case it is asked
|
* This object caches the file data that was already preloaded in case it is
|
||||||
* again by a region using the same sample. In this situation, both regions have a
|
* asked again by a region using the same sample. In this situation, both
|
||||||
* handle on the same preloaded data.
|
* regions have a handle on the same preloaded data.
|
||||||
*
|
*
|
||||||
* The file request is immediately served using the preloaded data. A ticket is then
|
* The file request is immediately served using the preloaded data. A promise is
|
||||||
* provided to the voice that requested the file, and the file loading happens in the
|
* then provided to the voice that requested the file, and the file loading
|
||||||
* background. When the file is fully loaded, the background makes the full data available
|
* happens in the background. File reads happen on whole samples but
|
||||||
* to the voice and consumes the ticket, while conserving a handle on this file. When the
|
* oversampling is done in chunks, and the promise contains a counter for the
|
||||||
* voice dies it releases its handle on the files, which should decrease the reference count
|
* frames that are loaded. When the voice dies it releases its handle on the
|
||||||
* to 1. A garbage collection thread then runs regularly to clear the memory of all file
|
* promise, which should decrease the reference count to 1. A garbage
|
||||||
* handles with a reference count of 1.
|
* collection thread then runs regularly to clear the memory of all file handles
|
||||||
|
* with a reference count of 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
class FilePool {
|
class FilePool {
|
||||||
public:
|
public:
|
||||||
FilePool()
|
/**
|
||||||
{
|
* @brief Construct a new File Pool object.
|
||||||
for (int i = 0; i < config::numBackgroundThreads; ++i)
|
*
|
||||||
fileLoadingThreadPool.emplace_back( &FilePool::loadingThread, this );
|
* This creates the background threads based on config::numBackgroundThreads
|
||||||
fileLoadingThreadPool.emplace_back( &FilePool::clearingThread, this );
|
* as well as the garbage collection thread.
|
||||||
}
|
*/
|
||||||
|
FilePool();
|
||||||
|
|
||||||
~FilePool()
|
~FilePool();
|
||||||
{
|
|
||||||
quitThread = true;
|
|
||||||
for (auto& thread: fileLoadingThreadPool)
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the root directory from which to search for files to load
|
* @brief Set the root directory from which to search for files to load
|
||||||
*
|
*
|
||||||
|
|
@ -214,13 +224,15 @@ private:
|
||||||
bool emptyQueue { false };
|
bool emptyQueue { false };
|
||||||
std::atomic<int> threadsLoading { 0 };
|
std::atomic<int> threadsLoading { 0 };
|
||||||
|
|
||||||
|
// File promises data structures along with their guards.
|
||||||
|
std::vector<FilePromisePtr> emptyPromises;
|
||||||
std::vector<FilePromisePtr> temporaryFilePromises;
|
std::vector<FilePromisePtr> temporaryFilePromises;
|
||||||
std::vector<FilePromisePtr> promisesToClear;
|
std::vector<FilePromisePtr> promisesToClear;
|
||||||
std::atomic<bool> addingPromisesToClear { false };
|
std::atomic<bool> addingPromisesToClear { false };
|
||||||
std::atomic<bool> canAddPromisesToClear { true };
|
std::atomic<bool> canAddPromisesToClear { true };
|
||||||
|
|
||||||
absl::flat_hash_map<absl::string_view, PreloadedFileHandle> preloadedFiles;
|
absl::flat_hash_map<absl::string_view, PreloadedFileHandle> preloadedFiles;
|
||||||
std::vector<std::thread> fileLoadingThreadPool { };
|
std::vector<std::thread> threadPool { };
|
||||||
LEAK_DETECTOR(FilePool);
|
LEAK_DETECTOR(FilePool);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,18 +31,41 @@
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
/**
|
||||||
|
* @brief Wraps the internal oversampler in a single function that takes an
|
||||||
|
* AudioBuffer and oversamples it in another pre-allocated one. The
|
||||||
|
* Oversampler processes the file in chunks and can signal the frames
|
||||||
|
* processes using an atomic counter.
|
||||||
|
*/
|
||||||
class Oversampler
|
class Oversampler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Oversampler() = delete;
|
/**
|
||||||
|
* @brief Construct a new Oversampler object
|
||||||
|
*
|
||||||
|
* @param factor
|
||||||
|
* @param chunkSize
|
||||||
|
*/
|
||||||
Oversampler(Oversampling factor = Oversampling::x1, size_t chunkSize = config::chunkSize);
|
Oversampler(Oversampling factor = Oversampling::x1, size_t chunkSize = config::chunkSize);
|
||||||
|
/**
|
||||||
|
* @brief Stream the oversampling of an input AudioBuffer into an output
|
||||||
|
* one, possibly signaling the caller along the way of the number of
|
||||||
|
* frames that are written.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* @param output
|
||||||
|
* @param framesReady an atomic counter for the ready frames. If null no signaling is done.
|
||||||
|
*/
|
||||||
|
void stream(const AudioBuffer<float>& input, AudioBuffer<float>& output, std::atomic<size_t>* framesReady = nullptr);
|
||||||
|
|
||||||
|
Oversampler() = delete;
|
||||||
Oversampler(const Oversampler&) = delete;
|
Oversampler(const Oversampler&) = delete;
|
||||||
Oversampler(Oversampler&&) = delete;
|
Oversampler(Oversampler&&) = delete;
|
||||||
void stream(const AudioBuffer<float>& input, AudioBuffer<float>& output, std::atomic<size_t>* framesReady = nullptr);
|
|
||||||
private:
|
private:
|
||||||
Oversampling factor;
|
Oversampling factor;
|
||||||
size_t chunkSize;
|
size_t chunkSize;
|
||||||
|
|
||||||
|
LEAK_DETECTOR(Oversampler);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue