diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 67c3b36f..554915fe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,9 @@ set (SFIZZ_SOURCES sfizz/Smoothers.cpp sfizz/Wavetables.cpp sfizz/Tuning.cpp + sfizz/RegionSet.cpp + sfizz/PolyphonyGroup.cpp + sfizz/VoiceStealing.cpp sfizz/RTSemaphore.cpp sfizz/Panning.cpp sfizz/Effects.cpp diff --git a/src/sfizz/PolyphonyGroup.cpp b/src/sfizz/PolyphonyGroup.cpp new file mode 100644 index 00000000..7ac1e3d6 --- /dev/null +++ b/src/sfizz/PolyphonyGroup.cpp @@ -0,0 +1,18 @@ +#include "PolyphonyGroup.h" + +void sfz::PolyphonyGroup::setPolyphonyLimit(unsigned limit) noexcept +{ + polyphonyLimit = limit; + voices.reserve(limit); +} + +void sfz::PolyphonyGroup::registerVoice(Voice* voice) noexcept +{ + if (absl::c_find(voices, voice) == voices.end()) + voices.push_back(voice); +} + +void sfz::PolyphonyGroup::removeVoice(const Voice* voice) noexcept +{ + swapAndPopFirst(voices, [voice](const Voice* v) { return v == voice; }); +} diff --git a/src/sfizz/PolyphonyGroup.h b/src/sfizz/PolyphonyGroup.h index 74314da7..d3e1413b 100644 --- a/src/sfizz/PolyphonyGroup.h +++ b/src/sfizz/PolyphonyGroup.h @@ -15,23 +15,43 @@ namespace sfz { class PolyphonyGroup { public: - void setPolyphonyLimit(unsigned limit) - { - polyphonyLimit = limit; - voices.reserve(limit); - } - unsigned getPolyphonyLimit() const { return polyphonyLimit; } - void registerVoice(Voice* voice) - { - if (absl::c_find(voices, voice) == voices.end()) - voices.push_back(voice); - } - void removeVoice(const Voice* voice) - { - swapAndPopFirst(voices, [voice](const Voice* v) { return v == voice; }); - } - const std::vector& getActiveVoices() const { return voices; } - std::vector& getActiveVoices() { return voices; } + /** + * @brief Set the polyphony limit for this polyphony group. + * + * @param limit + */ + void setPolyphonyLimit(unsigned limit) noexcept; + /** + * @brief Register an active voice in this polyphony group. + * + * @param voice + */ + void registerVoice(Voice* voice) noexcept; + /** + * @brief Remove a voice from this polyphony group. + * If the voice was not registered before, this has no effect. + * + * @param voice + */ + void removeVoice(const Voice* voice) noexcept; + /** + * @brief Get the polyphony limit for this group + * + * @return unsigned + */ + unsigned getPolyphonyLimit() const noexcept { return polyphonyLimit; } + /** + * @brief Get the active voices + * + * @return const std::vector& + */ + const std::vector& getActiveVoices() const noexcept { return voices; } + /** + * @brief Get the active voices + * + * @return std::vector& + */ + std::vector& getActiveVoices() noexcept { return voices; } private: unsigned polyphonyLimit { config::maxVoices }; std::vector voices; diff --git a/src/sfizz/RegionSet.cpp b/src/sfizz/RegionSet.cpp new file mode 100644 index 00000000..d6f8a585 --- /dev/null +++ b/src/sfizz/RegionSet.cpp @@ -0,0 +1,48 @@ +#include "RegionSet.h" + +void sfz::RegionSet::setPolyphonyLimit(unsigned limit) noexcept +{ + polyphonyLimit = limit; + voices.reserve(limit); +} + +void sfz::RegionSet::addRegion(Region* region) noexcept +{ + if (absl::c_find(regions, region) == regions.end()) + regions.push_back(region); +} + +void sfz::RegionSet::addSubset(RegionSet* group) noexcept +{ + if (absl::c_find(subsets, group) == subsets.end()) + subsets.push_back(group); +} + +void sfz::RegionSet::registerVoice(Voice* voice) noexcept +{ + if (absl::c_find(voices, voice) == voices.end()) + voices.push_back(voice); +} + +void sfz::RegionSet::removeVoice(const Voice* voice) noexcept +{ + swapAndPopFirst(voices, [voice](const Voice* v) { return v == voice; }); +} + +void sfz::RegionSet::registerVoiceInHierarchy(const Region* region, Voice* voice) noexcept +{ + auto* parent = region->parent; + while (parent != nullptr) { + parent->registerVoice(voice); + parent = parent->getParent(); + } +} + +void sfz::RegionSet::removeVoiceFromHierarchy(const Region* region, const Voice* voice) noexcept +{ + auto* parent = region->parent; + while (parent != nullptr) { + parent->removeVoice(voice); + parent = parent->getParent(); + } +} diff --git a/src/sfizz/RegionSet.h b/src/sfizz/RegionSet.h index 5203442b..24120173 100644 --- a/src/sfizz/RegionSet.h +++ b/src/sfizz/RegionSet.h @@ -16,53 +16,93 @@ namespace sfz class RegionSet { public: - void setPolyphonyLimit(unsigned limit) - { - polyphonyLimit = limit; - voices.reserve(limit); - } - unsigned getPolyphonyLimit() const { return polyphonyLimit; } - void addRegion(Region* region) - { - if (absl::c_find(regions, region) == regions.end()) - regions.push_back(region); - } - void addSubset(RegionSet* group) - { - if (absl::c_find(subsets, group) == subsets.end()) - subsets.push_back(group); - } - void registerVoice(Voice* voice) - { - if (absl::c_find(voices, voice) == voices.end()) - voices.push_back(voice); - } - void removeVoice(const Voice* voice) - { - swapAndPopFirst(voices, [voice](const Voice* v) { return v == voice; }); - } - static void registerVoiceInHierarchy(const Region* region, Voice* voice) - { - auto* parent = region->parent; - while (parent != nullptr) { - parent->registerVoice(voice); - parent = parent->getParent(); - } - } - static void removeVoiceFromHierarchy(const Region* region, const Voice* voice) - { - auto* parent = region->parent; - while (parent != nullptr) { - parent->removeVoice(voice); - parent = parent->getParent(); - } - } - RegionSet* getParent() const { return parent; } - void setParent(RegionSet* parent) { this->parent = parent; } - const std::vector& getActiveVoices() const { return voices; } - std::vector& getActiveVoices() { return voices; } - const std::vector& getRegions() const { return regions; } - const std::vector& getSubsets() const { return subsets; } + /** + * @brief Set the polyphony limit for the set + * + * @param limit + */ + void setPolyphonyLimit(unsigned limit) noexcept; + /** + * @brief Add a region to the set + * + * @param region + */ + void addRegion(Region* region) noexcept; + /** + * @brief Add a subset to the set + * + * @param group + */ + void addSubset(RegionSet* group) noexcept; + /** + * @brief Register a voice as active in this set + * + * @param voice + */ + void registerVoice(Voice* voice) noexcept; + /** + * @brief Remove an active voice for this set. + * If the voice was not registered this has no effect. + * + * @param voice + */ + void removeVoice(const Voice* voice) noexcept; + /** + * @brief Register a voice in the whole parent hierarchy of the region + * + * @param region + * @param voice + */ + static void registerVoiceInHierarchy(const Region* region, Voice* voice) noexcept; + /** + * @brief Remove an active voice from the whole parent hierarchy of the region. + * + * @param region + * @param voice + */ + static void removeVoiceFromHierarchy(const Region* region, const Voice* voice) noexcept; + /** + * @brief Get the polyphony limit + * + * @return unsigned + */ + unsigned getPolyphonyLimit() const noexcept { return polyphonyLimit; } + /** + * @brief Get the parent set + * + * @return RegionSet* + */ + RegionSet* getParent() const noexcept { return parent; } + /** + * @brief Set the parent set + * + * @param parent + */ + void setParent(RegionSet* parent) noexcept { this->parent = parent; } + /** + * @brief Get the active voices + * + * @return const std::vector& + */ + const std::vector& getActiveVoices() const noexcept { return voices; } + /** + * @brief Get the active voices + * + * @return std::vector& + */ + std::vector& getActiveVoices() noexcept { return voices; } + /** + * @brief Get the regions in the set + * + * @return const std::vector& + */ + const std::vector& getRegions() const noexcept { return regions; } + /** + * @brief Get the region subsets in this set + * + * @return const std::vector& + */ + const std::vector& getSubsets() const noexcept { return subsets; } private: RegionSet* parent { nullptr }; std::vector regions; diff --git a/src/sfizz/SisterVoiceRing.h b/src/sfizz/SisterVoiceRing.h index 7bd4c0ef..4df6ec4d 100644 --- a/src/sfizz/SisterVoiceRing.h +++ b/src/sfizz/SisterVoiceRing.h @@ -14,7 +14,7 @@ namespace sfz struct SisterVoiceRing { template>::value, int> = 0> - static void applyToRing(T* voice, F&& lambda) + static void applyToRing(T* voice, F&& lambda) noexcept { auto v = voice->getNextSisterVoice(); while (v != voice) { @@ -25,7 +25,7 @@ struct SisterVoiceRing { lambda(voice); } - static unsigned countSisterVoices(const Voice* start) + static unsigned countSisterVoices(const Voice* start) noexcept { if (!start) return 0; @@ -50,7 +50,7 @@ struct SisterVoiceRing { */ class SisterVoiceRingBuilder { public: - ~SisterVoiceRingBuilder() { + ~SisterVoiceRingBuilder() noexcept { if (lastStartedVoice != nullptr) { ASSERT(firstStartedVoice); lastStartedVoice->setNextSisterVoice(firstStartedVoice); @@ -63,7 +63,7 @@ public: * * @param voice */ - void addVoiceToRing(Voice* voice) { + void addVoiceToRing(Voice* voice) noexcept { if (firstStartedVoice == nullptr) firstStartedVoice = voice; diff --git a/src/sfizz/VoiceStealing.cpp b/src/sfizz/VoiceStealing.cpp new file mode 100644 index 00000000..a90e4950 --- /dev/null +++ b/src/sfizz/VoiceStealing.cpp @@ -0,0 +1,45 @@ +#include "VoiceStealing.h" + +sfz::VoiceStealing::VoiceStealing() +{ + voiceScores.reserve(config::maxVoices); +} + +sfz::Voice* sfz::VoiceStealing::steal(absl::Span voices) noexcept +{ + // Start of the voice stealing algorithm + absl::c_sort(voices, voiceOrdering); + + const auto sumEnvelope = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) { + return sum + v->getAverageEnvelope(); + }); + const auto envThreshold = sumEnvelope + / static_cast(voices.size()) * config::stealingEnvelopeCoeff; + const auto ageThreshold = voices.front()->getAge() * config::stealingAgeCoeff; + + Voice* returnedVoice = voices.front(); + unsigned idx = 0; + while (idx < voices.size()) { + const auto ref = voices[idx]; + + if (ref->getAge() < ageThreshold) { + // Went too far, we'll kill the oldest note. + break; + } + + float maxEnvelope { 0.0f }; + SisterVoiceRing::applyToRing(ref, [&](Voice* v) { + maxEnvelope = max(maxEnvelope, v->getAverageEnvelope()); + }); + + if (maxEnvelope < envThreshold) { + returnedVoice = ref; + break; + } + + // Jump over the sister voices in the set + do { idx++; } + while (idx < voices.size() && sisterVoices(ref, voices[idx])); + } + return returnedVoice; +} diff --git a/src/sfizz/VoiceStealing.h b/src/sfizz/VoiceStealing.h index 6914d1b0..7251f37c 100644 --- a/src/sfizz/VoiceStealing.h +++ b/src/sfizz/VoiceStealing.h @@ -17,50 +17,14 @@ namespace sfz class VoiceStealing { public: - VoiceStealing() - { - voiceScores.reserve(config::maxVoices); - } - - Voice* steal(absl::Span voices) noexcept - { - // Start of the voice stealing algorithm - absl::c_sort(voices, voiceOrdering); - - const auto sumEnvelope = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) { - return sum + v->getAverageEnvelope(); - }); - const auto envThreshold = sumEnvelope - / static_cast(voices.size()) * config::stealingEnvelopeCoeff; - const auto ageThreshold = voices.front()->getAge() * config::stealingAgeCoeff; - - Voice* returnedVoice = voices.front(); - unsigned idx = 0; - while (idx < voices.size()) { - const auto ref = voices[idx]; - - if (ref->getAge() < ageThreshold) { - // Went too far, we'll kill the oldest note. - break; - } - - float maxEnvelope { 0.0f }; - SisterVoiceRing::applyToRing(ref, [&](Voice* v) { - maxEnvelope = max(maxEnvelope, v->getAverageEnvelope()); - }); - - if (maxEnvelope < envThreshold) { - returnedVoice = ref; - break; - } - - // Jump over the sister voices in the set - do { idx++; } - while (idx < voices.size() && sisterVoices(ref, voices[idx])); - } - return returnedVoice; - } - + VoiceStealing(); + /** + * @brief Propose a voice to steal from a set of voices + * + * @param voices + * @return Voice* + */ + Voice* steal(absl::Span voices) noexcept; private: struct VoiceScore {