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 645f6168..8ab7ec03 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -223,6 +223,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(); @@ -362,6 +365,37 @@ 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"): + 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: // Unsupported control opcode DBG("Unsupported control opcode: " << member.opcode); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 95921790..def9cde2 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -869,7 +869,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 @@ -1057,3 +1060,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 b70701cf..ffbdea4b 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! * @@ -524,6 +537,7 @@ private: ModMatrix::TargetId oscillatorDetuneTarget; ModMatrix::TargetId oscillatorModDepthTarget; + bool followPower { false }; PowerFollower powerFollower; LEAK_DETECTOR(Voice); 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;