Merge pull request #353 from paulfd/runtime-configs
Add runtime configs for loading in ram and voice stealing
This commit is contained in:
commit
0e2eacaa65
8 changed files with 165 additions and 10 deletions
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue