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 indexBufferPoolSize { 2 };
constexpr int preloadSize { 8192 };
constexpr bool loadInRam { false };
constexpr int loggerQueueSize { 256 };
constexpr int voiceLoggerQueueSize { 256 };
constexpr bool loggingEnabled { false };

View file

@ -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<uint32_t>(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<int>(oversamplingFactor);
const auto maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(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<float>(factor) / static_cast<float>(this->oversamplingFactor) };
for (auto& preloadedFile : preloadedFiles) {
const auto numFrames = preloadedFile.second.preloadedData->getNumFrames() / static_cast<int>(this->oversamplingFactor);
const uint32_t maxOffset = numFrames > this->preloadSize ? static_cast<uint32_t>(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);
}
}

View file

@ -51,6 +51,7 @@ using FileAudioBufferPtr = std::shared_ptr<FileAudioBuffer>;
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<FilePromisePtr, config::maxVoices> promiseQueue;
atomic_queue::AtomicQueue2<FilePromisePtr, config::maxVoices> filledPromiseQueue;
RTSemaphore semFilledPromiseQueueAvailable { config::maxVoices };
bool loadInRam { config::loadInRam };
uint32_t preloadSize { config::preloadSize };
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
// Signals

View file

@ -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<Opcode>& 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);

View file

@ -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;
}

View file

@ -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);

View file

@ -10,7 +10,35 @@ sfz::Voice* sfz::VoiceStealing::steal(absl::Span<sfz::Voice*> 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<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);
const auto sumPower = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) {

View file

@ -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<Voice*> voices) noexcept;
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
{
Voice* voice;