Change the FilePool to load files and not only preload

This commit is contained in:
Paul Ferrand 2020-03-28 19:45:34 +01:00
parent 2add056a51
commit 62b68b2af3
2 changed files with 65 additions and 25 deletions

View file

@ -170,13 +170,16 @@ bool sfz::FilePool::checkSample(std::string& filename) const noexcept
#endif #endif
} }
absl::optional<sfz::FilePool::FileInformation> sfz::FilePool::getFileInformation(const std::string& filename) noexcept absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const std::string& filename) noexcept
{ {
fs::path file { rootDirectory / filename }; fs::path file { rootDirectory / filename };
if (!fs::exists(file))
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("Missing logic for " << sndFile.channels() << " channels, discarding sample " << filename); DBG("[sfizz] Missing logic for " << sndFile.channels() << " channels, discarding sample " << filename);
return {}; return {};
} }
@ -198,13 +201,11 @@ absl::optional<sfz::FilePool::FileInformation> sfz::FilePool::getFileInformation
bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) noexcept bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) noexcept
{ {
fs::path file { rootDirectory / filename }; fs::path file { rootDirectory / filename };
auto fileInformation = getFileInformation(filename);
if (!fs::exists(file)) if (!fileInformation)
return false; return false;
SndfileHandle sndFile(file.string().c_str()); SndfileHandle sndFile(file.string().c_str());
if (sndFile.channels() != 1 && sndFile.channels() != 2)
return false;
// 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());
@ -215,19 +216,47 @@ bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset)
return min(frames, maxOffset + preloadSize); return min(frames, maxOffset + preloadSize);
}(); }();
if (preloadedFiles.contains(filename)) { const auto existingFile = preloadedFiles.find(filename);
if (framesToLoad > preloadedFiles[filename].preloadedData->getNumFrames()) { if (existingFile != preloadedFiles.end()) {
if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) {
preloadedFiles[filename].preloadedData = readFromFile<float>(sndFile, framesToLoad, oversamplingFactor); preloadedFiles[filename].preloadedData = readFromFile<float>(sndFile, framesToLoad, oversamplingFactor);
} }
} else { } else {
const float sourceSampleRate { static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate()) }; fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate());
PreloadedFileHandle handle { readFromFile<float>(sndFile, framesToLoad, oversamplingFactor), sourceSampleRate }; FileDataHandle handle {
readFromFile<float>(sndFile, framesToLoad, oversamplingFactor),
*fileInformation
};
preloadedFiles.insert_or_assign(filename, handle); preloadedFiles.insert_or_assign(filename, handle);
} }
return true; return true;
} }
absl::optional<sfz::FileDataHandle> sfz::FilePool::loadFile(const std::string& filename) noexcept
{
fs::path file { rootDirectory / filename };
auto fileInformation = getFileInformation(filename);
if (!fileInformation)
return {};
SndfileHandle sndFile(file.string().c_str());
// 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 existingFile = loadedFiles.find(filename);
if (existingFile != loadedFiles.end()) {
return existingFile->second;
} else {
fileInformation->sampleRate = static_cast<float>(oversamplingFactor) * static_cast<float>(sndFile.samplerate());
FileDataHandle handle {
readFromFile<float>(sndFile, frames, oversamplingFactor),
*fileInformation
};
loadedFiles.insert_or_assign(filename, handle);
return handle;
}
}
sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept
{ {
if (emptyPromises.empty()) { if (emptyPromises.empty()) {
@ -244,7 +273,7 @@ sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) n
auto promise = emptyPromises.back(); auto promise = emptyPromises.back();
promise->filename = preloaded->first; promise->filename = preloaded->first;
promise->preloadedData = preloaded->second.preloadedData; promise->preloadedData = preloaded->second.preloadedData;
promise->sampleRate = preloaded->second.sampleRate; promise->sampleRate = 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();
@ -396,7 +425,7 @@ void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept
fs::path file { rootDirectory / std::string(preloadedFile.first) }; fs::path file { rootDirectory / std::string(preloadedFile.first) };
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.second.sampleRate *= samplerateChange; preloadedFile.second.information.sampleRate *= samplerateChange;
} }
this->oversamplingFactor = factor; this->oversamplingFactor = factor;

View file

@ -43,11 +43,19 @@ namespace sfz {
using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>; using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>;
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 };
};
// Strict C++11 disallows member initialization if aggregate initialization is to be used... // Strict C++11 disallows member initialization if aggregate initialization is to be used...
struct PreloadedFileHandle struct FileDataHandle
{ {
std::shared_ptr<AudioBuffer<float>> preloadedData; std::shared_ptr<AudioBuffer<float>> preloadedData;
float sampleRate; FileInformation information;
}; };
struct FilePromise struct FilePromise
@ -142,14 +150,6 @@ public:
*/ */
size_t getNumPreloadedSamples() const noexcept { return preloadedFiles.size(); } 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. * @brief Get metadata information about a file.
* *
@ -159,7 +159,7 @@ public:
absl::optional<FileInformation> getFileInformation(const std::string& filename) noexcept; absl::optional<FileInformation> getFileInformation(const std::string& filename) noexcept;
/** /**
* @brief Check that a file is preloaded with the proper offset bounds * @brief Preload a file with the proper offset bounds
* *
* @param filename * @param filename
* @param offset the maximum offset to consider for preloading. The total preloaded * @param offset the maximum offset to consider for preloading. The total preloaded
@ -169,6 +169,15 @@ public:
*/ */
bool preloadFile(const std::string& filename, uint32_t maxOffset) noexcept; bool preloadFile(const std::string& filename, uint32_t maxOffset) noexcept;
/**
* @brief Load a file and return its information. The file pool will store this
* data for future requests so use this function responsibly.
*
* @param filename
* @return A handle on the file data
*/
absl::optional<sfz::FileDataHandle> loadFile(const std::string& filename) 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.
* *
@ -258,7 +267,9 @@ private:
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; // Preloaded data
absl::flat_hash_map<absl::string_view, FileDataHandle> preloadedFiles;
absl::flat_hash_map<absl::string_view, FileDataHandle> loadedFiles;
std::vector<std::thread> threadPool { }; std::vector<std::thread> threadPool { };
LEAK_DETECTOR(FilePool); LEAK_DETECTOR(FilePool);
}; };