Added EQPool

This commit is contained in:
Paul Fd 2020-02-09 18:17:15 +01:00
parent 3d60de459f
commit ad798c38df
3 changed files with 259 additions and 0 deletions

View file

@ -4,6 +4,7 @@ set (SFIZZ_SOURCES
sfizz/Synth.cpp
sfizz/FilePool.cpp
sfizz/FilterPool.cpp
sfizz/EQPool.cpp
sfizz/Region.cpp
sfizz/Voice.cpp
sfizz/ScopedFTZ.cpp

137
src/sfizz/EQPool.cpp Normal file
View file

@ -0,0 +1,137 @@
#include "EQPool.h"
#include "AtomicGuard.h"
#include <thread>
#include "absl/algorithm/container.h"
#include "SIMDHelpers.h"
using namespace std::chrono_literals;
sfz::EQHolder::EQHolder(const MidiState& state)
:midiState(state)
{
eq.setChannels(2);
}
void sfz::EQHolder::reset()
{
eq.clear();
}
void sfz::EQHolder::setup(const EQDescription& description, uint8_t velocity)
{
reset();
this->description = &description;
const auto normalizedVelocity = normalizeVelocity(velocity);
baseBandwidth = description.bandwidth;
baseGain = Default::eqGainRange.clamp(description.gain + normalizedVelocity * description.vel2gain);
baseFrequency = Default::eqFrequencyRange.clamp(description.frequency + normalizedVelocity * description.vel2frequency);
}
void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numFrames)
{
if (description == nullptr) {
for (unsigned channelIdx = 0; channelIdx < eq.channels(); channelIdx++)
copy<float>({ inputs[channelIdx], numFrames }, { outputs[channelIdx], numFrames });
return;
}
// TODO: Once the midistate envelopes are done, add modulation in there!
// For now we take the last value
lastFrequency = baseFrequency;
for (auto& mod: description->frequencyCC) {
lastFrequency += midiState.getCCValue(mod.first) * mod.second;
}
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
lastBandwidth = baseBandwidth;
for (auto& mod: description->bandwidthCC) {
lastBandwidth += midiState.getCCValue(mod.first) * mod.second;
}
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);
lastGain = baseGain;
for (auto& mod: description->gainCC) {
lastGain += midiState.getCCValue(mod.first) * mod.second;
}
lastGain = Default::eqGainRange.clamp(lastGain);
eq.process(inputs, outputs, lastFrequency, lastBandwidth, lastGain, numFrames);
}
float sfz::EQHolder::getLastFrequency() const
{
return lastFrequency;
}
float sfz::EQHolder::getLastBandwidth() const
{
return lastBandwidth;
}
float sfz::EQHolder::getLastGain() const
{
return lastGain;
}
void sfz::EQHolder::setSampleRate(float sampleRate)
{
eq.init(static_cast<double>(sampleRate));
}
sfz::EQPool::EQPool(const MidiState& state, int numEQs)
: midiState(state)
{
setnumEQs(numEQs);
}
sfz::EQHolderPtr sfz::EQPool::getEQ(const EQDescription& description, uint8_t velocity)
{
AtomicGuard guard { givingOutEQs };
if (!canGiveOutEQs)
return {};
auto eq = absl::c_find_if(eqs, [](const EQHolderPtr& holder) {
return holder.use_count() == 1;
});
if (eq == eqs.end())
return {};
(**eq).setup(description, velocity);
return *eq;
}
size_t sfz::EQPool::getActiveEQs() const
{
return absl::c_count_if(eqs, [](const EQHolderPtr& holder) {
return holder.use_count() > 1;
});
}
size_t sfz::EQPool::setnumEQs(size_t numEQs)
{
AtomicDisabler disabler { canGiveOutEQs };
while(givingOutEQs)
std::this_thread::sleep_for(1ms);
auto eqIterator = eqs.begin();
auto eqSentinel = eqs.rbegin();
while (eqIterator < eqSentinel.base()) {
if (eqIterator->use_count() == 1) {
std::iter_swap(eqIterator, eqSentinel);
++eqSentinel;
} else {
++eqIterator;
}
}
eqs.resize(std::distance(eqs.begin(), eqSentinel.base()));
for (size_t i = eqs.size(); i < numEQs; ++i) {
eqs.emplace_back(std::make_shared<EQHolder>(midiState));
eqs.back()->setSampleRate(sampleRate);
}
return eqs.size();
}
void sfz::EQPool::setSampleRate(float sampleRate)
{
for (auto& eq: eqs)
eq->setSampleRate(sampleRate);
}

121
src/sfizz/EQPool.h Normal file
View file

@ -0,0 +1,121 @@
#pragma once
#include "SfzFilter.h"
#include "EQDescription.h"
#include "MidiState.h"
#include <vector>
#include <memory>
namespace sfz
{
class EQHolder
{
public:
EQHolder() = delete;
EQHolder(const MidiState& state);
/**
* @brief Setup a new EQ based on an EQ description.
*
* @param description the EQ description
*/
void setup(const EQDescription& description, uint8_t velocity);
/**
* @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 frequency for the EQ
*
* @return float
*/
float getLastFrequency() const;
/**
* @brief Returns the last value of the bandwitdh for the EQ
*
* @return float
*/
float getLastBandwidth() const;
/**
* @brief Returns the last value of the gain for the EQ
*
* @return float
*/
float getLastGain() const;
/**
* @brief Set the sample rate for the EQ
*
* @param sampleRate
*/
void setSampleRate(float sampleRate);
private:
/**
* Reset the filter. Is called internally when using setup().
*/
void reset();
const MidiState& midiState;
const EQDescription* description;
FilterEq eq;
float baseBandwidth { Default::eqBandwidth };
float baseFrequency { Default::eqFrequency1 };
float baseGain { Default::eqGain };
float lastBandwidth { Default::eqBandwidth };
float lastFrequency { Default::eqFrequency1 };
float lastGain { Default::eqGain };
};
using EQHolderPtr = std::shared_ptr<EQHolder>;
class EQPool
{
public:
EQPool() = delete;
/**
* @brief Construct a new EQPool object
*
* @param state the associated midi state
* @param numEQs the number of inactive EQs to hold in the pool
*/
EQPool(const MidiState& state, int numEQs = config::filtersInPool);
/**
* @brief Get an EQ object to use in Voices
*
* @param description the filter description to bind to the EQ
* @param noteNumber the triggering note number
* @param velocity the triggering note velocity
* @return EQHolderPtr release this when done with the filter; no deallocation will be done
*/
EQHolderPtr getEQ(const EQDescription& description, uint8_t velocity);
/**
* @brief Get the number of active EQs
*
* @return size_t
*/
size_t getActiveEQs() const;
/**
* @brief Set the number of EQs in the pool. This function may sleep and should be called from a background thread.
* No EQs will be distributed during the reallocation of EQs. Existing running EQs are kept. If the target
* number of EQs is less that the number of active EQs, the function will not remove them and you may need
* to call it again after existing EQs have run out.
*
* @param numEQs
* @return size_t the actual number of EQs in the pool
*/
size_t setnumEQs(size_t numEQs);
/**
* @brief Set the sample rate for all EQs
*
* @param sampleRate
*/
void setSampleRate(float sampleRate);
private:
std::atomic<bool> givingOutEQs { false };
std::atomic<bool> canGiveOutEQs { true };
float sampleRate { config::defaultSampleRate };
const MidiState& midiState;
std::vector<EQHolderPtr> eqs;
};
}