253 lines
8.8 KiB
C++
253 lines
8.8 KiB
C++
// SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
// Copyright (c) 2019-2020, Paul Ferrand, Andrea Zanellato
|
|
// All rights reserved.
|
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
|
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
|
// list of conditions and the following disclaimer.
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#pragma once
|
|
#include "Config.h"
|
|
#include "Defaults.h"
|
|
#include "LeakDetector.h"
|
|
#include "AudioBuffer.h"
|
|
#include "AudioSpan.h"
|
|
#include "SIMDHelpers.h"
|
|
#include "ghc/fs_std.hpp"
|
|
#include <absl/container/flat_hash_map.h>
|
|
#include <absl/types/optional.h>
|
|
#include "absl/strings/string_view.h"
|
|
#include "atomic_queue/atomic_queue.h"
|
|
#include "Logger.h"
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <sndfile.hh>
|
|
|
|
namespace sfz {
|
|
using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>;
|
|
|
|
|
|
struct PreloadedFileHandle
|
|
{
|
|
std::shared_ptr<AudioBuffer<float>> preloadedData {};
|
|
float sampleRate { config::defaultSampleRate };
|
|
};
|
|
|
|
struct FilePromise
|
|
{
|
|
auto getData()
|
|
{
|
|
if (dataReady)
|
|
return AudioSpan<const float>(fileData);
|
|
else if (availableFrames > preloadedData->getNumFrames())
|
|
return AudioSpan<const float>(fileData).first(availableFrames);
|
|
else
|
|
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 {};
|
|
AudioBufferPtr preloadedData {};
|
|
AudioBuffer<float> fileData {};
|
|
float sampleRate { config::defaultSampleRate };
|
|
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
|
|
std::atomic_size_t availableFrames { 0 };
|
|
std::atomic<bool> dataReady { false };
|
|
std::chrono::time_point<std::chrono::high_resolution_clock> creationTime;
|
|
|
|
LEAK_DETECTOR(FilePromise);
|
|
};
|
|
|
|
using FilePromisePtr = std::shared_ptr<FilePromise>;
|
|
/**
|
|
* @brief This is a singleton-designed class that holds all the preloaded data
|
|
* as well as functions to request new file data and collect the file handles to
|
|
* close after they are read.
|
|
*
|
|
* This object caches the file data that was already preloaded in case it is
|
|
* asked again by a region using the same sample. In this situation, both
|
|
* regions have a handle on the same preloaded data.
|
|
*
|
|
* The file request is immediately served using the preloaded data. A promise is
|
|
* then provided to the voice that requested the file, and the file loading
|
|
* happens in the background. File reads happen on whole samples but
|
|
* oversampling is done in chunks, and the promise contains a counter for the
|
|
* frames that are loaded. When the voice dies it releases its handle on the
|
|
* promise, which should decrease the reference count to 1. A garbage
|
|
* collection thread then runs regularly to clear the memory of all file handles
|
|
* with a reference count of 1.
|
|
*/
|
|
|
|
|
|
class FilePool {
|
|
public:
|
|
/**
|
|
* @brief Construct a new File Pool object.
|
|
*
|
|
* This creates the background threads based on config::numBackgroundThreads
|
|
* as well as the garbage collection thread.
|
|
*/
|
|
FilePool(Logger& logger);
|
|
|
|
~FilePool();
|
|
/**
|
|
* @brief Set the root directory from which to search for files to load
|
|
*
|
|
* @param directory
|
|
*/
|
|
void setRootDirectory(const fs::path& directory) noexcept { rootDirectory = directory; }
|
|
/**
|
|
* @brief Get the number of preloaded sample files
|
|
*
|
|
* @return size_t
|
|
*/
|
|
size_t getNumPreloadedSamples() const noexcept { return preloadedFiles.size(); }
|
|
|
|
struct FileInformation {
|
|
uint32_t end { Default::sampleEndRange.getEnd() };
|
|
uint32_t loopBegin { Default::loopRange.getStart() };
|
|
uint32_t loopEnd { Default::loopRange.getEnd() };
|
|
double sampleRate { config::defaultSampleRate };
|
|
int numChannels { 0 };
|
|
};
|
|
|
|
/**
|
|
* @brief Get metadata information about a file.
|
|
*
|
|
* @param filename
|
|
* @return absl::optional<FileInformation>
|
|
*/
|
|
absl::optional<FileInformation> getFileInformation(const std::string& filename) noexcept;
|
|
|
|
/**
|
|
* @brief Check that a file is preloaded with the proper offset bounds
|
|
*
|
|
* @param filename
|
|
* @param offset the maximum offset to consider for preloading. The total preloaded
|
|
* size will be preloadSize + offset
|
|
* @return true if the preloading went fine
|
|
* @return false if something went wrong ()
|
|
*/
|
|
bool preloadFile(const std::string& filename, uint32_t maxOffset) noexcept;
|
|
|
|
/**
|
|
* @brief Check that the sample exists. If not, try to find it in a case insensitive way.
|
|
*
|
|
* @param filename the sample filename; may be updated by the method
|
|
* @return true if the sample exists or was updated properly
|
|
* @return false if no sample was found even with a case insensitive search
|
|
*/
|
|
bool checkSample(std::string& filename) const noexcept;
|
|
|
|
/**
|
|
* @brief Clear all preloaded files.
|
|
*
|
|
*/
|
|
void clear();
|
|
/**
|
|
* @brief Moves the filled promises to a linear storage, and checks
|
|
* said linear storage for promises that are not used anymore.
|
|
*
|
|
* This function has to be called on the audio thread.
|
|
*/
|
|
void cleanupPromises() noexcept;
|
|
/**
|
|
* @brief Get a file promise
|
|
*
|
|
* @param filename the file to preload
|
|
* @return FilePromisePtr a file promise
|
|
*/
|
|
FilePromisePtr getFilePromise(const std::string& filename) noexcept;
|
|
/**
|
|
* @brief Change the preloading size. This will trigger a full
|
|
* reload of all samples, so don't call it on the audio thread.
|
|
*
|
|
* @param preloadSize
|
|
*/
|
|
void setPreloadSize(uint32_t preloadSize) noexcept;
|
|
/**
|
|
* @brief Get the current preload size.
|
|
*
|
|
* @return uint32_t
|
|
*/
|
|
uint32_t getPreloadSize() const noexcept;
|
|
/**
|
|
* @brief Set the oversampling factor. This will trigger a full
|
|
* reload of all samples so don't call it on the audio thread.
|
|
*
|
|
* @param factor
|
|
*/
|
|
void setOversamplingFactor(Oversampling factor) noexcept;
|
|
/**
|
|
* @brief Get the current oversampling factor
|
|
*
|
|
* @return Oversampling
|
|
*/
|
|
Oversampling getOversamplingFactor() const noexcept;
|
|
/**
|
|
* @brief Empty the file loading queues without actually loading
|
|
* the files. All promises will be unfulfilled. Don't call this
|
|
* method on the audio thread as it will spinlock.
|
|
*
|
|
*/
|
|
void emptyFileLoadingQueues() noexcept;
|
|
/**
|
|
* @brief Wait for the background loading to finish for all promises
|
|
* in the queue.
|
|
*/
|
|
void waitForBackgroundLoading() noexcept;
|
|
private:
|
|
Logger& logger;
|
|
fs::path rootDirectory;
|
|
void loadingThread() noexcept;
|
|
void clearingThread();
|
|
void tryToClearPromises();
|
|
|
|
atomic_queue::AtomicQueue2<FilePromisePtr, config::maxVoices> promiseQueue;
|
|
atomic_queue::AtomicQueue2<FilePromisePtr, config::maxVoices> filledPromiseQueue;
|
|
uint32_t preloadSize { config::preloadSize };
|
|
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
|
|
// Signals
|
|
bool quitThread { false };
|
|
bool emptyQueue { false };
|
|
std::atomic<int> threadsLoading { 0 };
|
|
|
|
// File promises data structures along with their guards.
|
|
std::vector<FilePromisePtr> emptyPromises;
|
|
std::vector<FilePromisePtr> temporaryFilePromises;
|
|
std::vector<FilePromisePtr> promisesToClear;
|
|
std::atomic<bool> addingPromisesToClear { false };
|
|
std::atomic<bool> canAddPromisesToClear { true };
|
|
|
|
absl::flat_hash_map<absl::string_view, PreloadedFileHandle> preloadedFiles;
|
|
std::vector<std::thread> threadPool { };
|
|
LEAK_DETECTOR(FilePool);
|
|
};
|
|
}
|