Added EQPool
This commit is contained in:
parent
3d60de459f
commit
ad798c38df
3 changed files with 259 additions and 0 deletions
|
|
@ -4,6 +4,7 @@ set (SFIZZ_SOURCES
|
||||||
sfizz/Synth.cpp
|
sfizz/Synth.cpp
|
||||||
sfizz/FilePool.cpp
|
sfizz/FilePool.cpp
|
||||||
sfizz/FilterPool.cpp
|
sfizz/FilterPool.cpp
|
||||||
|
sfizz/EQPool.cpp
|
||||||
sfizz/Region.cpp
|
sfizz/Region.cpp
|
||||||
sfizz/Voice.cpp
|
sfizz/Voice.cpp
|
||||||
sfizz/ScopedFTZ.cpp
|
sfizz/ScopedFTZ.cpp
|
||||||
|
|
|
||||||
137
src/sfizz/EQPool.cpp
Normal file
137
src/sfizz/EQPool.cpp
Normal 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
121
src/sfizz/EQPool.h
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue