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 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 };
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue