From 49c9e43687b4230c5baf64c5508ef4ced17764ab Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 11 Aug 2020 01:26:07 +0200 Subject: [PATCH 1/2] Add runtime configs for loading in ram and voice stealing --- src/sfizz/Config.h | 1 + src/sfizz/FilePool.cpp | 48 ++++++++++++++++++++++++++++++------- src/sfizz/FilePool.h | 9 +++++++ src/sfizz/Synth.cpp | 25 +++++++++++++++++++ src/sfizz/VoiceStealing.cpp | 30 ++++++++++++++++++++++- src/sfizz/VoiceStealing.h | 23 ++++++++++++++++++ 6 files changed, 127 insertions(+), 9 deletions(-) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 442a165c..e99c83b3 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -33,6 +33,7 @@ namespace config { constexpr int stereoBufferPoolSize { 4 }; constexpr int indexBufferPoolSize { 2 }; constexpr int preloadSize { 8192 }; + constexpr bool loadInRam { false }; constexpr int loggerQueueSize { 256 }; constexpr int voiceLoggerQueueSize { 256 }; constexpr bool loggingEnabled { false }; diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 0fed17a0..c92ebcbd 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -261,13 +261,13 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce if (!fileInformation) return false; + fileInformation->maxOffset = maxOffset; const fs::path file { rootDirectory / fileId.filename() }; 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(reader->frames()); const auto framesToLoad = [&]() { - if (preloadSize == 0) + if (loadInRam) return frames; else 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); if (existingFile != preloadedFiles.end()) { if (framesToLoad > existingFile->second.preloadedData->getNumFrames()) { + preloadedFiles[fileId].information.maxOffset = maxOffset; preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad, oversamplingFactor); } } else { @@ -350,15 +351,17 @@ sfz::FilePromisePtr sfz::FilePool::getFilePromise(const FileId& fileId) noexcept void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept { + this->preloadSize = preloadSize; + if (loadInRam) + return; + // Update all the preloaded sizes for (auto& preloadedFile : preloadedFiles) { - const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast(oversamplingFactor); - const auto maxOffset = numFrames > this->preloadSize ? static_cast(numFrames) - this->preloadSize : 0; + const auto maxOffset = preloadedFile.second.information.maxOffset; fs::path file { rootDirectory / preloadedFile.first.filename() }; AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse()); preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset, oversamplingFactor); } - this->preloadSize = preloadSize; } void sfz::FilePool::tryToClearPromises() @@ -473,11 +476,18 @@ void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept { float samplerateChange { static_cast(factor) / static_cast(this->oversamplingFactor) }; for (auto& preloadedFile : preloadedFiles) { - const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast(this->oversamplingFactor); - const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast(numFrames) - this->preloadSize : 0; + const auto framesToLoad = [&]() { + 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() }; 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; } @@ -547,3 +557,25 @@ void sfz::FilePool::raiseCurrentThreadPriority() noexcept } #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); + } +} diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index f05712fd..ad357ff6 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -51,6 +51,7 @@ using FileAudioBufferPtr = std::shared_ptr; struct FileInformation { uint32_t end { Default::sampleEndRange.getEnd() }; + uint32_t maxOffset { 0 }; uint32_t loopBegin { Default::loopRange.getStart() }; uint32_t loopEnd { Default::loopRange.getEnd() }; bool hasLoop { false }; @@ -269,6 +270,13 @@ public: * for background sample file processing. */ 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: Logger& logger; fs::path rootDirectory; @@ -279,6 +287,7 @@ private: atomic_queue::AtomicQueue2 promiseQueue; atomic_queue::AtomicQueue2 filledPromiseQueue; RTSemaphore semFilledPromiseQueueAvailable { config::maxVoices }; + bool loadInRam { config::loadInRam }; uint32_t preloadSize { config::preloadSize }; Oversampling oversamplingFactor { config::defaultOversamplingFactor }; // Signals diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 58c360ab..0041ef7d 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -213,6 +213,9 @@ void sfz::Synth::clear() defaultSwitch = absl::nullopt; defaultPath = ""; resources.midiState.reset(); + resources.filePool.clear(); + resources.filePool.setRamLoading(config::loadInRam); + stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest); ccLabels.clear(); keyLabels.clear(); keyswitchLabels.clear(); @@ -352,6 +355,28 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) case hash("octave_offset"): setValueFromOpcode(member, octaveOffset, Default::octaveOffsetRange); 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"): + stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::First); + break; + case hash("oldest"): + stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest); + break; + case hash("envelope_and_age"): + stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::EnvelopeAndAge); + break; + default: + DBG("Unsupported value for hint_stealing: " << member.value); + } default: // Unsupported control opcode DBG("Unsupported control opcode: " << member.opcode); diff --git a/src/sfizz/VoiceStealing.cpp b/src/sfizz/VoiceStealing.cpp index 7ea7c867..1c425ac7 100644 --- a/src/sfizz/VoiceStealing.cpp +++ b/src/sfizz/VoiceStealing.cpp @@ -10,7 +10,35 @@ sfz::Voice* sfz::VoiceStealing::steal(absl::Span voices) noexcept if (voices.empty()) 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 voices) noexcept +{ + return voices.front(); +} + +sfz::Voice* sfz::VoiceStealing::stealOldest(absl::Span voices) noexcept +{ + absl::c_stable_sort(voices, voiceOrdering); + return voices.front(); +} + +sfz::Voice* sfz::VoiceStealing::stealEnvelopeAndAge(absl::Span voices) noexcept +{ absl::c_stable_sort(voices, voiceOrdering); const auto sumPower = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) { diff --git a/src/sfizz/VoiceStealing.h b/src/sfizz/VoiceStealing.h index 7251f37c..47eb8692 100644 --- a/src/sfizz/VoiceStealing.h +++ b/src/sfizz/VoiceStealing.h @@ -17,7 +17,25 @@ namespace sfz class VoiceStealing { public: + enum class StealingAlgorithm { + First, + Oldest, + EnvelopeAndAge + }; + 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 * @@ -26,6 +44,11 @@ public: */ Voice* steal(absl::Span voices) noexcept; private: + StealingAlgorithm stealingAlgorithm { StealingAlgorithm::Oldest }; + Voice* stealFirst(absl::Span voices) noexcept; + Voice* stealOldest(absl::Span voices) noexcept; + Voice* stealEnvelopeAndAge(absl::Span voices) noexcept; + struct VoiceScore { Voice* voice; From 0142c385dcc50733879db91f587ac525276a41b8 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 29 Aug 2020 14:21:25 +0200 Subject: [PATCH 2/2] Enable and disable the power follower depending on the chosen algorithm --- src/sfizz/Synth.cpp | 9 +++++++++ src/sfizz/Voice.cpp | 16 +++++++++++++++- src/sfizz/Voice.h | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 0041ef7d..0243583a 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -366,12 +366,21 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) 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: diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 27323c6d..1bc55580 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -848,7 +848,10 @@ void sfz::Voice::removeVoiceFromRing() 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 @@ -1030,3 +1033,14 @@ void sfz::Voice::saveModulationTargets(const Region* region) noexcept oscillatorDetuneTarget = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorDetune, 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; +} diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 70e5fb74..e9c41bf7 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -248,6 +248,19 @@ public: * @return float */ 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! * @@ -519,6 +532,7 @@ private: ModMatrix::TargetId oscillatorDetuneTarget; ModMatrix::TargetId oscillatorModDepthTarget; + bool followPower { false }; PowerFollower powerFollower; LEAK_DETECTOR(Voice);