diff --git a/src/sfizz/FilterPool.cpp b/src/sfizz/FilterPool.cpp index d43ec3e0..6922d6ef 100644 --- a/src/sfizz/FilterPool.cpp +++ b/src/sfizz/FilterPool.cpp @@ -1,6 +1,10 @@ #include "FilterPool.h" #include "SIMDHelpers.h" #include "absl/algorithm/container.h" +#include "AtomicGuard.h" +#include +#include +using namespace std::chrono_literals; sfz::FilterHolder::FilterHolder(const MidiState& midiState) : midiState(midiState) @@ -43,24 +47,36 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned // TODO: Once the midistate envelopes are done, add modulation in there! // For now we take the last value - auto cutoff = baseCutoff; + lastCutoff = baseCutoff; for (auto& mod: description->cutoffCC) { - cutoff *= centsFactor(midiState.getCCValue(mod.first) * mod.second); + lastCutoff *= centsFactor(midiState.getCCValue(mod.first) * mod.second); } - auto resonance = baseResonance; + lastResonance = baseResonance; for (auto& mod: description->resonanceCC) { - resonance += midiState.getCCValue(mod.first) * mod.second; + lastResonance += midiState.getCCValue(mod.first) * mod.second; } - auto gain = baseGain; + lastGain = baseGain; for (auto& mod: description->gainCC) { - gain += midiState.getCCValue(mod.first) * mod.second; + lastResonance += midiState.getCCValue(mod.first) * mod.second; } - filter.process(inputs, outputs, cutoff, resonance, gain,numFrames); + filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames); } +float sfz::FilterHolder::getLastCutoff() const +{ + return lastCutoff; +} +float sfz::FilterHolder::getLastResonance() const +{ + return lastResonance; +} +float sfz::FilterHolder::getLastGain() const +{ + return lastGain; +} sfz::FilterPool::FilterPool(const MidiState& state, int numFilters) : midiState(state) @@ -70,6 +86,10 @@ sfz::FilterPool::FilterPool(const MidiState& state, int numFilters) sfz::FilterHolderPtr sfz::FilterPool::getFilter(const FilterDescription& description, int noteNumber, uint8_t velocity) { + AtomicGuard guard { givingOutFilters }; + if (!canGiveOutFilters) + return {}; + auto filter = absl::c_find_if(filters, [](const FilterHolderPtr& holder) { return holder.use_count() == 1; }); @@ -81,17 +101,34 @@ sfz::FilterHolderPtr sfz::FilterPool::getFilter(const FilterDescription& descrip return *filter; } -int sfz::FilterPool::getActiveFilters() const +size_t sfz::FilterPool::getActiveFilters() const { return absl::c_count_if(filters, [](const FilterHolderPtr& holder) { return holder.use_count() > 1; }); } -void sfz::FilterPool::setNumFilters(int numFilters) +size_t sfz::FilterPool::setNumFilters(size_t numFilters) { - filters.clear(); - filters.reserve(numFilters); - for (int i = 0; i < numFilters; ++i) + AtomicDisabler disabler { canGiveOutFilters }; + + while(givingOutFilters) + std::this_thread::sleep_for(1ms); + + auto filterIterator = filters.begin(); + auto filterSentinel = filters.rbegin(); + while (filterIterator < filterSentinel.base()) { + if (filterIterator->use_count() == 1) { + std::iter_swap(filterIterator, filterSentinel); + ++filterSentinel; + } else { + ++filterIterator; + } + } + + filters.resize(std::distance(filters.begin(), filterSentinel.base())); + for (size_t i = filters.size(); i < numFilters; ++i) filters.emplace_back(std::make_shared(midiState)); + + return filters.size(); } diff --git a/src/sfizz/FilterPool.h b/src/sfizz/FilterPool.h index 8bd3f70e..f7d910f7 100644 --- a/src/sfizz/FilterPool.h +++ b/src/sfizz/FilterPool.h @@ -13,16 +13,54 @@ class FilterHolder public: FilterHolder() = delete; FilterHolder(const MidiState& state); - void reset(); + /** + * @brief Setup a new filter based on a filter description, and a triggering note parameters. + * + * @param description the filter description + * @param noteNumber the triggering note number + * @param velocity the triggering note velocity + */ void setup(const FilterDescription& description, int noteNumber = static_cast(Default::filterKeycenter), uint8_t velocity = 0); + /** + * @brief Process a block of stereo inputs + * + * @param inputs + * @param outputs + * @param numFrames + */ void process(const float** inputs, float** outputs, unsigned numFrames); + /** + * @brief Returns the last value of the cutoff for the filter + * + * @return float + */ + float getLastCutoff() const; + /** + * @brief Returns the last value of the resonance for the filter + * + * @return float + */ + float getLastResonance() const; + /** + * @brief Returns the last value of the gain for the filter + * + * @return float + */ + float getLastGain() const; private: + /** + * Reset the filter. Is called internally when using setup(). + */ + void reset(); const MidiState& midiState; const FilterDescription* description; Filter filter; float baseCutoff { Default::filterCutoff }; float baseResonance { Default::filterResonance }; float baseGain { Default::filterGain }; + float lastCutoff { Default::filterCutoff }; + float lastResonance { Default::filterResonance }; + float lastGain { Default::filterGain }; using filterRandomDist = std::uniform_int_distribution; filterRandomDist dist { 0, sfz::Default::filterRandom }; }; @@ -33,11 +71,42 @@ class FilterPool { public: FilterPool() = delete; + /** + * @brief Construct a new Filter Pool object + * + * @param state the associated midi state + * @param numFilters the number of inactive filters to hold in the pool + */ FilterPool(const MidiState& state, int numFilters = config::defaultNumFilters); + /** + * @brief Get a filter object to use in Voices + * + * @param description the filter description to bind to the filter + * @param noteNumber the triggering note number + * @param velocity the triggering note velocity + * @return FilterHolderPtr release this when done with the filter; no deallocation will be done + */ FilterHolderPtr getFilter(const FilterDescription& description, int noteNumber = static_cast(Default::filterKeycenter), uint8_t velocity = 0); - int getActiveFilters() const; - void setNumFilters(int numFilters); + /** + * @brief Get the number of active filters + * + * @return size_t + */ + size_t getActiveFilters() const; + /** + * @brief Set the number of filters in the pool. This function may sleep and should be called from a background thread. + * No filters will be distributed during the reallocation of filters. Existing running filters are kept. If the target + * number of filters is less that the number of active filters, the function will not remove them and you may need + * to call it again after existing filters have run out. + * + * @param numFilters + * @return size_t the actual number of filters in the pool + */ + size_t setNumFilters(size_t numFilters); private: + std::atomic givingOutFilters { false }; + std::atomic canGiveOutFilters { true }; + const MidiState& midiState; std::vector filters; };