Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter and EQ modulation targets #422

Merged
merged 10 commits into from
Sep 21, 2020
4 changes: 2 additions & 2 deletions src/sfizz/Defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ namespace Default
constexpr uint8_t filterKeycenter { 60 };
constexpr int filterRandom { 0 };
constexpr int filterVeltrack { 0 };
constexpr int filterCutoffCC { 0 };
constexpr float filterCutoffCC { 0 };
constexpr float filterResonanceCC { 0 };
constexpr float filterGainCC { 0 };
constexpr Range<float> filterCutoffRange { 0.0f, 20000.0f };
constexpr Range<int> filterCutoffModRange { -9600, 9600 };
constexpr Range<float> filterCutoffModRange { -9600, 9600 };
constexpr Range<float> filterGainRange { -96.0f, 96.0f };
constexpr Range<float> filterGainModRange { -96.0f, 96.0f };
constexpr Range<int> filterKeytrackRange { 0, 1200 };
Expand Down
3 changes: 0 additions & 3 deletions src/sfizz/EQDescription.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,5 @@ struct EQDescription
float vel2frequency { Default::eqVel2frequency };
float vel2gain { Default::eqVel2gain };
EqType type { EqType::kEqPeak };
CCMap<float> bandwidthCC { Default::eqBandwidthCC };
CCMap<float> frequencyCC { Default::eqFrequencyCC };
CCMap<float> gainCC { Default::eqGainCC };
};
}
168 changes: 53 additions & 115 deletions src/sfizz/EQPool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,147 +4,85 @@
#include "SIMDHelpers.h"
#include "SwapAndPop.h"

sfz::EQHolder::EQHolder(const MidiState& state)
:midiState(state)
sfz::EQHolder::EQHolder(Resources& resources)
: resources(resources)
{

eq = absl::make_unique<FilterEq>();
eq->init(config::defaultSampleRate);
}

void sfz::EQHolder::reset()
{
eq.clear();
eq->clear();
prepared = false;
}

void sfz::EQHolder::setup(const EQDescription& description, unsigned numChannels, float velocity)
void sfz::EQHolder::setup(const Region& region, unsigned eqId, float velocity)
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
eq.setType(description.type);
eq.setChannels(numChannels);
this->description = &description;
ASSERT(eqId < region.equalizers.size());

this->description = &region.equalizers[eqId];
eq->setType(description->type);
eq->setChannels(region.isStereo() ? 2 : 1);

// Setup the base values
baseFrequency = description.frequency + velocity * description.vel2frequency;
baseBandwidth = description.bandwidth;
baseGain = description.gain + velocity * description.vel2gain;

// Setup the modulated values
lastFrequency = baseFrequency;
for (const auto& mod : description.frequencyCC)
lastFrequency += midiState.getCCValue(mod.cc) * mod.data;
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);

lastBandwidth = baseBandwidth;
for (const auto& mod : description.bandwidthCC)
lastBandwidth += midiState.getCCValue(mod.cc) * mod.data;
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);

lastGain = baseGain;
for (const auto& mod : description.gainCC)
lastGain += midiState.getCCValue(mod.cc) * mod.data;
lastGain = Default::filterGainRange.clamp(lastGain);

// Initialize the EQ
eq.prepare(lastFrequency, lastBandwidth, lastGain);
baseFrequency = description->frequency + velocity * description->vel2frequency;
baseBandwidth = description->bandwidth;
baseGain = description->gain + velocity * description->vel2gain;

gainTarget = resources.modMatrix.findTarget(ModKey::createNXYZ(ModId::EqGain, region.id, eqId));
bandwidthTarget = resources.modMatrix.findTarget(ModKey::createNXYZ(ModId::EqBandwidth, region.id, eqId));
frequencyTarget = resources.modMatrix.findTarget(ModKey::createNXYZ(ModId::EqFrequency, region.id, eqId));

// Disables smoothing of the parameters on the first call
prepared = false;
}

void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numFrames)
{
auto justCopy = [&]() {
for (unsigned channelIdx = 0; channelIdx < eq.channels(); channelIdx++)
copy<float>({ inputs[channelIdx], numFrames }, { outputs[channelIdx], numFrames });
};

if (description == nullptr) {
justCopy();
return;
}

// TODO: Once the midistate envelopes are done, add modulation in there!
// For now we take the last value
lastFrequency = baseFrequency;
for (const auto& mod : description->frequencyCC)
lastFrequency += midiState.getCCValue(mod.cc) * mod.data;
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);

lastBandwidth = baseBandwidth;
for (const auto& mod : description->bandwidthCC)
lastBandwidth += midiState.getCCValue(mod.cc) * mod.data;
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);

lastGain = baseGain;
for (const auto& mod : description->gainCC)
lastGain += midiState.getCCValue(mod.cc) * mod.data;
lastGain = Default::filterGainRange.clamp(lastGain);

if (lastGain == 0.0f) {
justCopy();
for (unsigned channelIdx = 0; channelIdx < eq->channels(); channelIdx++)
copy<float>({ inputs[channelIdx], numFrames }, { outputs[channelIdx], numFrames });
return;
}

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, unsigned numChannels, float velocity)
{
const std::unique_lock<SpinMutex> lock { eqGuard, std::try_to_lock };
if (!lock.owns_lock())
return {};

auto eq = absl::c_find_if(eqs, [](const EQHolderPtr& holder) {
return holder.use_count() == 1;
});
ModMatrix& mm = resources.modMatrix;
auto frequencySpan = resources.bufferPool.getBuffer(numFrames);
auto bandwidthSpan = resources.bufferPool.getBuffer(numFrames);
auto gainSpan = resources.bufferPool.getBuffer(numFrames);

if (eq == eqs.end())
return {};

(**eq).setup(description, numChannels, velocity);
return *eq;
}
if (!frequencySpan || !bandwidthSpan || !gainSpan)
return;

size_t sfz::EQPool::getActiveEQs() const
{
return absl::c_count_if(eqs, [](const EQHolderPtr& holder) {
return holder.use_count() > 1;
});
}
fill<float>(*frequencySpan, baseFrequency);
if (float* mod = mm.getModulation(frequencyTarget))
add<float>(absl::Span<float>(mod, numFrames), *frequencySpan);

size_t sfz::EQPool::setnumEQs(size_t numEQs)
{
const std::lock_guard<SpinMutex> eqLock { eqGuard };
fill<float>(*bandwidthSpan, baseBandwidth);
if (float* mod = mm.getModulation(bandwidthTarget))
add<float>(absl::Span<float>(mod, numFrames), *bandwidthSpan);

swapAndPopAll(eqs, [](sfz::EQHolderPtr& eq) { return eq.use_count() == 1; });
fill<float>(*gainSpan, baseGain);
if (float* mod = mm.getModulation(gainTarget))
add<float>(absl::Span<float>(mod, numFrames), *gainSpan);

for (size_t i = eqs.size(); i < numEQs; ++i) {
eqs.emplace_back(std::make_shared<EQHolder>(midiState));
eqs.back()->setSampleRate(sampleRate);
if (!prepared) {
eq->prepare(frequencySpan->front(), bandwidthSpan->front(), gainSpan->front());
prepared = true;
}

return eqs.size();
eq->processModulated(
inputs,
outputs,
frequencySpan->data(),
bandwidthSpan->data(),
gainSpan->data(),
numFrames
);
}
void sfz::EQPool::setSampleRate(float sampleRate)

void sfz::EQHolder::setSampleRate(float sampleRate)
{
for (auto& eq: eqs)
eq->setSampleRate(sampleRate);
eq->init(static_cast<double>(sampleRate));
}
99 changes: 16 additions & 83 deletions src/sfizz/EQPool.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once
#include "SfzFilter.h"
#include "EQDescription.h"
#include "MidiState.h"
#include "Region.h"
#include "Resources.h"
#include "utility/SpinMutex.h"
#include <vector>
#include <memory>
Expand All @@ -14,15 +14,15 @@ class EQHolder
{
public:
EQHolder() = delete;
EQHolder(const MidiState& state);
EQHolder(Resources& resources);
/**
* @brief Setup a new EQ based on an EQ description.
* @brief Setup a new EQ from a region and an index
*
* @param description the EQ description
* @param numChannels the number of channels for the EQ
* @param description the triggering velocity/value
* @param description the region from which we take the EQ
* @param eqId the EQ index in the region
* @param description the triggering velocity/value
*/
void setup(const EQDescription& description, unsigned numChannels, float velocity);
void setup(const Region& region, unsigned eqId, float velocity);
/**
* @brief Process a block of stereo inputs
*
Expand All @@ -31,94 +31,27 @@ class EQHolder
* @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().
* Reset the filter.
*/
void reset();
const MidiState& midiState;
private:
Resources& resources;
const EQDescription* description;
FilterEq eq;
std::unique_ptr<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 };
bool prepared { false };
ModMatrix::TargetId gainTarget;
ModMatrix::TargetId frequencyTarget;
ModMatrix::TargetId bandwidthTarget;
};

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 numChannels the number of channels for the EQ
* @param velocity the triggering note velocity/value
* @return EQHolderPtr release this when done with the filter; no deallocation will be done
*/
EQHolderPtr getEQ(const EQDescription& description, unsigned numChannels, float 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:
SpinMutex eqGuard;
float sampleRate { config::defaultSampleRate };
const MidiState& midiState;
std::vector<EQHolderPtr> eqs;
};
}
3 changes: 0 additions & 3 deletions src/sfizz/FilterDescription.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,5 @@ struct FilterDescription
int veltrack { Default::filterVeltrack };
int random { Default::filterRandom };
FilterType type { FilterType::kFilterLpf2p };
CCMap<int> cutoffCC { Default::filterCutoffCC };
CCMap<float> resonanceCC { Default::filterResonanceCC };
CCMap<float> gainCC { Default::filterGainCC };
};
}
Loading