Merge branch 'develop' of github.com:sfztools/sfizz into develop

This commit is contained in:
redtide 2019-12-23 05:19:55 -08:00
commit 38b1eaaaa4
4 changed files with 128 additions and 48 deletions

View file

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

View file

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

View file

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

View file

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