Merge pull request #353 from paulfd/runtime-configs

Add runtime configs for loading in ram and voice stealing
This commit is contained in:
Paul Ferrand 2020-10-06 10:24:54 +02:00 committed by GitHub
commit 0e2eacaa65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 165 additions and 10 deletions

View file

@ -33,6 +33,7 @@ namespace config {
constexpr int stereoBufferPoolSize { 4 }; constexpr int stereoBufferPoolSize { 4 };
constexpr int indexBufferPoolSize { 2 }; constexpr int indexBufferPoolSize { 2 };
constexpr int preloadSize { 8192 }; constexpr int preloadSize { 8192 };
constexpr bool loadInRam { false };
constexpr int loggerQueueSize { 256 }; constexpr int loggerQueueSize { 256 };
constexpr int voiceLoggerQueueSize { 256 }; constexpr int voiceLoggerQueueSize { 256 };
constexpr bool loggingEnabled { false }; constexpr bool loggingEnabled { false };

View file

@ -261,13 +261,13 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
if (!fileInformation) if (!fileInformation)
return false; return false;
fileInformation->maxOffset = maxOffset;
const fs::path file { rootDirectory / fileId.filename() }; const fs::path file { rootDirectory / fileId.filename() };
AudioReaderPtr reader = createAudioReader(file, fileId.isReverse()); AudioReaderPtr reader = createAudioReader(file, fileId.isReverse());
// FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same
const auto frames = static_cast<uint32_t>(reader->frames()); const auto frames = static_cast<uint32_t>(reader->frames());
const auto framesToLoad = [&]() { const auto framesToLoad = [&]() {
if (preloadSize == 0) if (loadInRam)
return frames; return frames;
else else
return min(frames, maxOffset + preloadSize); return min(frames, maxOffset + preloadSize);
@ -276,6 +276,7 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
const auto existingFile = preloadedFiles.find(fileId); 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[fileId].information.maxOffset = maxOffset;
preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad, oversamplingFactor); preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad, oversamplingFactor);
} }
} else { } else {
@ -350,15 +351,17 @@ sfz::FilePromisePtr sfz::FilePool::getFilePromise(const FileId& fileId) noexcept
void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept
{ {
this->preloadSize = preloadSize;
if (loadInRam)
return;
// Update all the preloaded sizes // Update all the preloaded sizes
for (auto& preloadedFile : preloadedFiles) { for (auto& preloadedFile : preloadedFiles) {
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(oversamplingFactor); const auto maxOffset = preloadedFile.second.information.maxOffset;
const auto maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0;
fs::path file { rootDirectory / preloadedFile.first.filename() }; fs::path file { rootDirectory / preloadedFile.first.filename() };
AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse()); AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse());
preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset, oversamplingFactor); preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset, oversamplingFactor);
} }
this->preloadSize = preloadSize;
} }
void sfz::FilePool::tryToClearPromises() void sfz::FilePool::tryToClearPromises()
@ -473,11 +476,18 @@ void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept
{ {
float samplerateChange { static_cast<float>(factor) / static_cast<float>(this->oversamplingFactor) }; float samplerateChange { static_cast<float>(factor) / static_cast<float>(this->oversamplingFactor) };
for (auto& preloadedFile : preloadedFiles) { for (auto& preloadedFile : preloadedFiles) {
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(this->oversamplingFactor); const auto framesToLoad = [&]() {
const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(numFrames) - this->preloadSize : 0; if (loadInRam)
return preloadedFile.second.information.end;
else
return min(
preloadedFile.second.information.end,
preloadedFile.second.information.maxOffset + preloadSize
);
}();
fs::path file { rootDirectory / preloadedFile.first.filename() }; fs::path file { rootDirectory / preloadedFile.first.filename() };
AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse()); AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse());
preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset, factor); preloadedFile.second.preloadedData = readFromFile(*reader, framesToLoad, factor);
preloadedFile.second.information.sampleRate *= samplerateChange; preloadedFile.second.information.sampleRate *= samplerateChange;
} }
@ -547,3 +557,25 @@ void sfz::FilePool::raiseCurrentThreadPriority() noexcept
} }
#endif #endif
} }
void sfz::FilePool::setRamLoading(bool loadInRam) noexcept
{
if (loadInRam == this->loadInRam)
return;
this->loadInRam = loadInRam;
if (loadInRam) {
for (auto& preloadedFile : preloadedFiles) {
fs::path file { rootDirectory / preloadedFile.first.filename() };
AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse());
preloadedFile.second.preloadedData = readFromFile(
*reader,
preloadedFile.second.information.end,
oversamplingFactor
);
}
} else {
setPreloadSize(preloadSize);
}
}

View file

@ -51,6 +51,7 @@ using FileAudioBufferPtr = std::shared_ptr<FileAudioBuffer>;
struct FileInformation { struct FileInformation {
uint32_t end { Default::sampleEndRange.getEnd() }; uint32_t end { Default::sampleEndRange.getEnd() };
uint32_t maxOffset { 0 };
uint32_t loopBegin { Default::loopRange.getStart() }; uint32_t loopBegin { Default::loopRange.getStart() };
uint32_t loopEnd { Default::loopRange.getEnd() }; uint32_t loopEnd { Default::loopRange.getEnd() };
bool hasLoop { false }; bool hasLoop { false };
@ -269,6 +270,13 @@ public:
* for background sample file processing. * for background sample file processing.
*/ */
static void raiseCurrentThreadPriority() noexcept; static void raiseCurrentThreadPriority() noexcept;
/**
* @brief Change whether all samples are loaded in ram.
* This will trigger a purge and reloading.
*
* @param loadInRam
*/
void setRamLoading(bool loadInRam) noexcept;
private: private:
Logger& logger; Logger& logger;
fs::path rootDirectory; fs::path rootDirectory;
@ -279,6 +287,7 @@ private:
atomic_queue::AtomicQueue2<FilePromisePtr, config::maxVoices> promiseQueue; atomic_queue::AtomicQueue2<FilePromisePtr, config::maxVoices> promiseQueue;
atomic_queue::AtomicQueue2<FilePromisePtr, config::maxVoices> filledPromiseQueue; atomic_queue::AtomicQueue2<FilePromisePtr, config::maxVoices> filledPromiseQueue;
RTSemaphore semFilledPromiseQueueAvailable { config::maxVoices }; RTSemaphore semFilledPromiseQueueAvailable { config::maxVoices };
bool loadInRam { config::loadInRam };
uint32_t preloadSize { config::preloadSize }; uint32_t preloadSize { config::preloadSize };
Oversampling oversamplingFactor { config::defaultOversamplingFactor }; Oversampling oversamplingFactor { config::defaultOversamplingFactor };
// Signals // Signals

View file

@ -223,6 +223,9 @@ void sfz::Synth::clear()
defaultSwitch = absl::nullopt; defaultSwitch = absl::nullopt;
defaultPath = ""; defaultPath = "";
resources.midiState.reset(); resources.midiState.reset();
resources.filePool.clear();
resources.filePool.setRamLoading(config::loadInRam);
stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest);
ccLabels.clear(); ccLabels.clear();
keyLabels.clear(); keyLabels.clear();
keyswitchLabels.clear(); keyswitchLabels.clear();
@ -362,6 +365,37 @@ void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
case hash("octave_offset"): case hash("octave_offset"):
setValueFromOpcode(member, octaveOffset, Default::octaveOffsetRange); setValueFromOpcode(member, octaveOffset, Default::octaveOffsetRange);
break; break;
case hash("hint_ram_based"):
if (member.value == "1")
resources.filePool.setRamLoading(true);
else if (member.value == "0")
resources.filePool.setRamLoading(false);
else
DBG("Unsupported value for hint_ram_based: " << member.value);
break;
case hash("hint_stealing"):
switch(hash(member.value)) {
case hash("first"):
for (auto& voice : voices)
voice->disablePowerFollower();
stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::First);
break;
case hash("oldest"):
for (auto& voice : voices)
voice->disablePowerFollower();
stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest);
break;
case hash("envelope_and_age"):
for (auto& voice : voices)
voice->enablePowerFollower();
stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::EnvelopeAndAge);
break;
default:
DBG("Unsupported value for hint_stealing: " << member.value);
}
default: default:
// Unsupported control opcode // Unsupported control opcode
DBG("Unsupported control opcode: " << member.opcode); DBG("Unsupported control opcode: " << member.opcode);

View file

@ -869,7 +869,10 @@ void sfz::Voice::removeVoiceFromRing() noexcept
float sfz::Voice::getAveragePower() const noexcept float sfz::Voice::getAveragePower() const noexcept
{ {
return powerFollower.getAveragePower(); if (followPower)
return powerFollower.getAveragePower();
else
return 0.0f;
} }
bool sfz::Voice::releasedOrFree() const noexcept bool sfz::Voice::releasedOrFree() const noexcept
@ -1057,3 +1060,14 @@ void sfz::Voice::saveModulationTargets(const Region* region) noexcept
oscillatorDetuneTarget = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorDetune, region->getId())); oscillatorDetuneTarget = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorDetune, region->getId()));
oscillatorModDepthTarget = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorModDepth, region->getId())); oscillatorModDepthTarget = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorModDepth, region->getId()));
} }
void sfz::Voice::enablePowerFollower() noexcept
{
followPower = true;
powerFollower.clear();
}
void sfz::Voice::disablePowerFollower() noexcept
{
followPower = false;
}

View file

@ -248,6 +248,19 @@ public:
* @return float * @return float
*/ */
float getAveragePower() const noexcept; float getAveragePower() const noexcept;
/**
* @brief Enable the power follower
*
*/
void enablePowerFollower() noexcept;
/**
* @brief Disable the power follower
*
*/
void disablePowerFollower() noexcept;
/** /**
* Returns the region that is currently playing. May be null if the voice is not active! * Returns the region that is currently playing. May be null if the voice is not active!
* *
@ -524,6 +537,7 @@ private:
ModMatrix::TargetId oscillatorDetuneTarget; ModMatrix::TargetId oscillatorDetuneTarget;
ModMatrix::TargetId oscillatorModDepthTarget; ModMatrix::TargetId oscillatorModDepthTarget;
bool followPower { false };
PowerFollower powerFollower; PowerFollower powerFollower;
LEAK_DETECTOR(Voice); LEAK_DETECTOR(Voice);

View file

@ -10,7 +10,35 @@ sfz::Voice* sfz::VoiceStealing::steal(absl::Span<sfz::Voice*> voices) noexcept
if (voices.empty()) if (voices.empty())
return {}; return {};
// Start of the voice stealing algorithm switch(stealingAlgorithm) {
case StealingAlgorithm::First:
return stealFirst(voices);
case StealingAlgorithm::EnvelopeAndAge:
return stealEnvelopeAndAge(voices);
case StealingAlgorithm::Oldest:
default:
return stealOldest(voices);
}
}
void sfz::VoiceStealing::setStealingAlgorithm(StealingAlgorithm algorithm) noexcept
{
stealingAlgorithm = algorithm;
}
sfz::Voice* sfz::VoiceStealing::stealFirst(absl::Span<Voice*> voices) noexcept
{
return voices.front();
}
sfz::Voice* sfz::VoiceStealing::stealOldest(absl::Span<Voice*> voices) noexcept
{
absl::c_stable_sort(voices, voiceOrdering);
return voices.front();
}
sfz::Voice* sfz::VoiceStealing::stealEnvelopeAndAge(absl::Span<Voice*> voices) noexcept
{
absl::c_stable_sort(voices, voiceOrdering); absl::c_stable_sort(voices, voiceOrdering);
const auto sumPower = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) { const auto sumPower = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) {

View file

@ -17,7 +17,25 @@ namespace sfz
class VoiceStealing class VoiceStealing
{ {
public: public:
enum class StealingAlgorithm {
First,
Oldest,
EnvelopeAndAge
};
VoiceStealing(); VoiceStealing();
/**
* @brief Get the current stealing algorithm
*
* @return StealingAlgorithm
*/
StealingAlgorithm getStealingAlgorithm() const noexcept { return stealingAlgorithm; }
/**
* @brief Set a default stealing algorithm
*
* @param algorithm
*/
void setStealingAlgorithm(StealingAlgorithm algorithm) noexcept;
/** /**
* @brief Propose a voice to steal from a set of voices * @brief Propose a voice to steal from a set of voices
* *
@ -26,6 +44,11 @@ public:
*/ */
Voice* steal(absl::Span<Voice*> voices) noexcept; Voice* steal(absl::Span<Voice*> voices) noexcept;
private: private:
StealingAlgorithm stealingAlgorithm { StealingAlgorithm::Oldest };
Voice* stealFirst(absl::Span<Voice*> voices) noexcept;
Voice* stealOldest(absl::Span<Voice*> voices) noexcept;
Voice* stealEnvelopeAndAge(absl::Span<Voice*> voices) noexcept;
struct VoiceScore struct VoiceScore
{ {
Voice* voice; Voice* voice;