Merge pull request #424 from paulfd/eg-lfo-filters-eq
EGs and LFOs sources for the filters and EQs
This commit is contained in:
commit
8012829000
20 changed files with 1618 additions and 1480 deletions
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = ®ion.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;
|
||||
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);
|
||||
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));
|
||||
|
||||
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);
|
||||
// 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();
|
||||
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 (const auto& mod : description->frequencyCC)
|
||||
lastFrequency += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
|
||||
ModMatrix& mm = resources.modMatrix;
|
||||
auto frequencySpan = resources.bufferPool.getBuffer(numFrames);
|
||||
auto bandwidthSpan = resources.bufferPool.getBuffer(numFrames);
|
||||
auto gainSpan = resources.bufferPool.getBuffer(numFrames);
|
||||
|
||||
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();
|
||||
if (!frequencySpan || !bandwidthSpan || !gainSpan)
|
||||
return;
|
||||
|
||||
fill<float>(*frequencySpan, baseFrequency);
|
||||
if (float* mod = mm.getModulation(frequencyTarget))
|
||||
add<float>(absl::Span<float>(mod, numFrames), *frequencySpan);
|
||||
|
||||
fill<float>(*bandwidthSpan, baseBandwidth);
|
||||
if (float* mod = mm.getModulation(bandwidthTarget))
|
||||
add<float>(absl::Span<float>(mod, numFrames), *bandwidthSpan);
|
||||
|
||||
fill<float>(*gainSpan, baseGain);
|
||||
if (float* mod = mm.getModulation(gainTarget))
|
||||
add<float>(absl::Span<float>(mod, numFrames), *gainSpan);
|
||||
|
||||
if (!prepared) {
|
||||
eq->prepare(frequencySpan->front(), bandwidthSpan->front(), gainSpan->front());
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
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;
|
||||
eq->processModulated(
|
||||
inputs,
|
||||
outputs,
|
||||
frequencySpan->data(),
|
||||
bandwidthSpan->data(),
|
||||
gainSpan->data(),
|
||||
numFrames
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
if (eq == eqs.end())
|
||||
return {};
|
||||
|
||||
(**eq).setup(description, numChannels, 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)
|
||||
{
|
||||
const std::lock_guard<SpinMutex> eqLock { eqGuard };
|
||||
|
||||
swapAndPopAll(eqs, [](sfz::EQHolderPtr& eq) { return eq.use_count() == 1; });
|
||||
|
||||
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);
|
||||
eq->init(static_cast<double>(sampleRate));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
*
|
||||
|
|
@ -31,94 +31,27 @@ public:
|
|||
* @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;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,153 +5,102 @@
|
|||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
sfz::FilterHolder::FilterHolder(const MidiState& midiState)
|
||||
: midiState(midiState)
|
||||
sfz::FilterHolder::FilterHolder(Resources& resources)
|
||||
: resources(resources)
|
||||
{
|
||||
|
||||
filter = absl::make_unique<Filter>();
|
||||
filter->init(config::defaultSampleRate);
|
||||
}
|
||||
|
||||
void sfz::FilterHolder::reset()
|
||||
{
|
||||
filter.clear();
|
||||
filter->clear();
|
||||
prepared = false;
|
||||
}
|
||||
|
||||
void sfz::FilterHolder::setup(const FilterDescription& description, unsigned numChannels, int noteNumber, float velocity)
|
||||
void sfz::FilterHolder::setup(const Region& region, unsigned filterId, int noteNumber, float velocity)
|
||||
{
|
||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||
ASSERT(filterId < region.filters.size());
|
||||
|
||||
this->description = &description;
|
||||
filter.setType(description.type);
|
||||
filter.setChannels(numChannels);
|
||||
this->description = ®ion.filters[filterId];
|
||||
filter->setType(description->type);
|
||||
filter->setChannels(region.isStereo() ? 2 : 1);
|
||||
|
||||
// Setup the base values
|
||||
baseCutoff = description.cutoff;
|
||||
if (description.random != 0) {
|
||||
dist.param(filterRandomDist::param_type(0, description.random));
|
||||
baseCutoff = description->cutoff;
|
||||
if (description->random != 0) {
|
||||
dist.param(filterRandomDist::param_type(0, description->random));
|
||||
baseCutoff *= centsFactor(dist(Random::randomGenerator));
|
||||
}
|
||||
const auto keytrack = description.keytrack * (noteNumber - description.keycenter);
|
||||
const auto keytrack = description->keytrack * (noteNumber - description->keycenter);
|
||||
baseCutoff *= centsFactor(keytrack);
|
||||
const auto veltrack = static_cast<float>(description.veltrack) * velocity;
|
||||
const auto veltrack = static_cast<float>(description->veltrack) * velocity;
|
||||
baseCutoff *= centsFactor(veltrack);
|
||||
baseCutoff = Default::filterCutoffRange.clamp(baseCutoff);
|
||||
|
||||
baseGain = description.gain;
|
||||
baseResonance = description.resonance;
|
||||
baseGain = description->gain;
|
||||
baseResonance = description->resonance;
|
||||
|
||||
// Setup the modulated values
|
||||
lastCutoff = baseCutoff;
|
||||
for (const auto& mod : description.cutoffCC)
|
||||
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.data);
|
||||
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
|
||||
ModMatrix& mm = resources.modMatrix;
|
||||
gainTarget = mm.findTarget(ModKey::createNXYZ(ModId::FilGain, region.id, filterId));
|
||||
cutoffTarget = mm.findTarget(ModKey::createNXYZ(ModId::FilCutoff, region.id, filterId));
|
||||
resonanceTarget = mm.findTarget(ModKey::createNXYZ(ModId::FilResonance, region.id, filterId));
|
||||
|
||||
lastResonance = baseResonance;
|
||||
for (const auto& mod : description.resonanceCC)
|
||||
lastResonance += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
|
||||
|
||||
lastGain = baseGain;
|
||||
for (const auto& mod : description.gainCC)
|
||||
lastGain += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||
|
||||
// Initialize the filter
|
||||
filter.prepare(lastCutoff, lastResonance, lastGain);
|
||||
// Disable smoothing of the parameters on the first call
|
||||
prepared = false;
|
||||
}
|
||||
|
||||
void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned numFrames)
|
||||
{
|
||||
if (numFrames == 0)
|
||||
return;
|
||||
|
||||
if (description == nullptr) {
|
||||
for (unsigned channelIdx = 0; channelIdx < filter.channels(); channelIdx++)
|
||||
for (unsigned channelIdx = 0; channelIdx < filter->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
|
||||
// TODO: the template deduction could be automatic here?
|
||||
lastCutoff = baseCutoff;
|
||||
for (const auto& mod : description->cutoffCC)
|
||||
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.data);
|
||||
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
|
||||
ModMatrix& mm = resources.modMatrix;
|
||||
auto cutoffSpan = resources.bufferPool.getBuffer(numFrames);
|
||||
auto resonanceSpan = resources.bufferPool.getBuffer(numFrames);
|
||||
auto gainSpan = resources.bufferPool.getBuffer(numFrames);
|
||||
|
||||
lastResonance = baseResonance;
|
||||
for (const auto& mod : description->resonanceCC)
|
||||
lastResonance += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
|
||||
if (!cutoffSpan || !resonanceSpan || !gainSpan)
|
||||
return;
|
||||
|
||||
lastGain = baseGain;
|
||||
for (const auto& mod : description->gainCC)
|
||||
lastGain += midiState.getCCValue(mod.cc) * mod.data;
|
||||
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||
|
||||
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)
|
||||
{
|
||||
setNumFilters(numFilters);
|
||||
}
|
||||
|
||||
sfz::FilterHolderPtr sfz::FilterPool::getFilter(const FilterDescription& description, unsigned numChannels, int noteNumber, float velocity)
|
||||
{
|
||||
const std::unique_lock<SpinMutex> lock { filterGuard, std::try_to_lock };
|
||||
if (!lock.owns_lock())
|
||||
return {};
|
||||
|
||||
auto filter = absl::c_find_if(filters, [](const FilterHolderPtr& holder) {
|
||||
return holder.use_count() == 1;
|
||||
});
|
||||
|
||||
if (filter == filters.end())
|
||||
return {};
|
||||
|
||||
(**filter).setup(description, numChannels, noteNumber, velocity);
|
||||
return *filter;
|
||||
}
|
||||
|
||||
size_t sfz::FilterPool::getActiveFilters() const
|
||||
{
|
||||
return absl::c_count_if(filters, [](const FilterHolderPtr& holder) {
|
||||
return holder.use_count() > 1;
|
||||
});
|
||||
}
|
||||
|
||||
size_t sfz::FilterPool::setNumFilters(size_t numFilters)
|
||||
{
|
||||
const std::lock_guard<SpinMutex> filterLock { filterGuard };
|
||||
|
||||
swapAndPopAll(filters, [](sfz::FilterHolderPtr& filter) { return filter.use_count() == 1; });
|
||||
|
||||
for (size_t i = filters.size(); i < numFilters; ++i) {
|
||||
filters.emplace_back(std::make_shared<FilterHolder>(midiState));
|
||||
filters.back()->setSampleRate(sampleRate);
|
||||
fill<float>(*cutoffSpan, baseCutoff);
|
||||
if (float* mod = mm.getModulation(cutoffTarget)) {
|
||||
for (size_t i = 0; i < numFrames; ++i)
|
||||
(*cutoffSpan)[i] *= centsFactor(mod[i]);
|
||||
}
|
||||
|
||||
return filters.size();
|
||||
fill<float>(*resonanceSpan, baseResonance);
|
||||
if (float* mod = mm.getModulation(resonanceTarget))
|
||||
add<float>(absl::Span<float>(mod, numFrames), *resonanceSpan);
|
||||
|
||||
fill<float>(*gainSpan, baseGain);
|
||||
if (float* mod = mm.getModulation(gainTarget))
|
||||
add<float>(absl::Span<float>(mod, numFrames), *gainSpan);
|
||||
|
||||
if (!prepared) {
|
||||
filter->prepare(cutoffSpan->front(), resonanceSpan->front(), gainSpan->front());
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
filter->processModulated(
|
||||
inputs,
|
||||
outputs,
|
||||
cutoffSpan->data(),
|
||||
resonanceSpan->data(),
|
||||
gainSpan->data(),
|
||||
numFrames
|
||||
);
|
||||
}
|
||||
|
||||
void sfz::FilterPool::setSampleRate(float sampleRate)
|
||||
{
|
||||
for (auto& filter: filters)
|
||||
filter->setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
void sfz::FilterHolder::setSampleRate(float sampleRate)
|
||||
{
|
||||
filter.init(static_cast<double>(sampleRate));
|
||||
filter->init(static_cast<double>(sampleRate));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
#include "SfzFilter.h"
|
||||
#include "FilterDescription.h"
|
||||
#include "MidiState.h"
|
||||
#include "Region.h"
|
||||
#include "Resources.h"
|
||||
#include "Defaults.h"
|
||||
#include "utility/SpinMutex.h"
|
||||
#include <vector>
|
||||
|
|
@ -15,16 +15,16 @@ class FilterHolder
|
|||
{
|
||||
public:
|
||||
FilterHolder() = delete;
|
||||
FilterHolder(const MidiState& state);
|
||||
FilterHolder(Resources& resources);
|
||||
/**
|
||||
* @brief Setup a new filter based on a filter description, and a triggering note parameters.
|
||||
*
|
||||
* @param description the filter description
|
||||
* @param numChannels the number of channels
|
||||
* @param noteNumber the triggering note number
|
||||
* @param velocity the triggering note velocity/value
|
||||
* @param description the region from which we take the filter
|
||||
* @param filterId the filter index in the region
|
||||
* @param noteNumber the triggering note number
|
||||
* @param velocity the triggering note velocity/value
|
||||
*/
|
||||
void setup(const FilterDescription& description, unsigned numChannels, int noteNumber = static_cast<int>(Default::filterKeycenter), float velocity = 0);
|
||||
void setup(const Region& region, unsigned filterId, int noteNumber = static_cast<int>(Default::filterKeycenter), float velocity = 0);
|
||||
/**
|
||||
* @brief Process a block of stereo inputs
|
||||
*
|
||||
|
|
@ -33,97 +33,29 @@ public:
|
|||
* @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;
|
||||
/**
|
||||
* @brief Set the sample rate for a filter
|
||||
*
|
||||
* @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 FilterDescription* description;
|
||||
Filter filter;
|
||||
std::unique_ptr<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 };
|
||||
ModMatrix::TargetId gainTarget;
|
||||
ModMatrix::TargetId cutoffTarget;
|
||||
ModMatrix::TargetId resonanceTarget;
|
||||
bool prepared { false };
|
||||
using filterRandomDist = std::uniform_int_distribution<int>;
|
||||
filterRandomDist dist { 0, sfz::Default::filterRandom };
|
||||
};
|
||||
|
||||
using FilterHolderPtr = std::shared_ptr<FilterHolder>;
|
||||
|
||||
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::filtersInPool);
|
||||
/**
|
||||
* @brief Get a filter object to use in Voices
|
||||
*
|
||||
* @param description the filter description to bind to the filter
|
||||
* @param numChannels the number of channels in the underlying 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, unsigned numChannels, int noteNumber = static_cast<int>(Default::filterKeycenter), float velocity = 0);
|
||||
/**
|
||||
* @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);
|
||||
/**
|
||||
* @brief Set the sample rate for all filters
|
||||
*
|
||||
* @param sampleRate
|
||||
*/
|
||||
void setSampleRate(float sampleRate);
|
||||
private:
|
||||
SpinMutex filterGuard;
|
||||
float sampleRate { config::defaultSampleRate };
|
||||
const MidiState& midiState;
|
||||
std::vector<FilterHolderPtr> filters;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -85,6 +85,7 @@ end_region_oncc:
|
|||
egV1 = "ampeg"|"fileg"|"pitcheg";
|
||||
eqV1 = "eq" number;
|
||||
lfoV2 = "lfo" number;
|
||||
egV2 = "eg" number;
|
||||
|
||||
(lfoV1) "_" ("depth"|"freq"|"fade") "cc" (number) END {
|
||||
opcode = absl::StrCat(group(1), "_", group(2), "_oncc", group(3));
|
||||
|
|
@ -102,7 +103,14 @@ end_region_oncc:
|
|||
opcode = absl::StrCat(group(1), "_", group(2), "1");
|
||||
goto end_region;
|
||||
}
|
||||
|
||||
(lfoV2) "_" ("cutoff"|"resonance") ("_" any)? END {
|
||||
opcode = absl::StrCat(group(1), "_", group(2), "1", group(3));
|
||||
goto end_region;
|
||||
}
|
||||
(egV2) "_" ("cutoff"|"resonance") ("_" any)? END {
|
||||
opcode = absl::StrCat(group(1), "_", group(2), "1", group(3));
|
||||
goto end_region;
|
||||
}
|
||||
"loop" ("mode"|"start"|"end") END {
|
||||
opcode = absl::StrCat("loop_", group(1));
|
||||
goto end_region;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,23 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
case hash(x "_stepcc&"): \
|
||||
case hash(x "_smoothcc&")
|
||||
|
||||
#define LFO_EG_filter_EQ_target(sourceKey, targetKey, range) \
|
||||
{ \
|
||||
const auto number = opcode.parameters.front(); \
|
||||
if (number == 0) \
|
||||
return false; \
|
||||
\
|
||||
const auto index = opcode.parameters.size() == 2 ? opcode.parameters.back() - 1 : 0; \
|
||||
if (!extendIfNecessary(filters, index + 1, Default::numFilters)) \
|
||||
return false; \
|
||||
\
|
||||
if (auto value = readOpcode(opcode.value, range)) { \
|
||||
const ModKey source = ModKey::createNXYZ(sourceKey, id, number - 1); \
|
||||
const ModKey target = ModKey::createNXYZ(targetKey, id, index); \
|
||||
getOrCreateConnection(source, target).sourceDepth = *value; \
|
||||
} \
|
||||
}
|
||||
|
||||
// Sound source: sample playback
|
||||
case hash("sample"):
|
||||
{
|
||||
|
|
@ -579,30 +596,22 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
setValueFromOpcode(opcode, filters[filterIndex].resonance, Default::filterResonanceRange);
|
||||
}
|
||||
break;
|
||||
case hash("cutoff&_oncc&"): // also cutoff_oncc&, cutoff_cc&, cutoff&_cc&
|
||||
case_any_ccN("cutoff&"): // also cutoff_oncc&, cutoff_cc&, cutoff&_cc&
|
||||
{
|
||||
const auto filterIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
|
||||
return false;
|
||||
|
||||
setValueFromOpcode(
|
||||
opcode,
|
||||
filters[filterIndex].cutoffCC[opcode.parameters.back()],
|
||||
Default::filterCutoffModRange
|
||||
);
|
||||
processGenericCc(opcode, Default::filterCutoffModRange, ModKey::createNXYZ(ModId::FilCutoff, id, filterIndex));
|
||||
}
|
||||
break;
|
||||
case hash("resonance&_oncc&"): // also resonance_oncc&, resonance_cc&, resonance&_cc&
|
||||
case_any_ccN("resonance&"): // also resonance_oncc&, resonance_cc&, resonance&_cc&
|
||||
{
|
||||
const auto filterIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
|
||||
return false;
|
||||
|
||||
setValueFromOpcode(
|
||||
opcode,
|
||||
filters[filterIndex].resonanceCC[opcode.parameters.back()],
|
||||
Default::filterResonanceModRange
|
||||
);
|
||||
processGenericCc(opcode, Default::filterResonanceModRange, ModKey::createNXYZ(ModId::FilResonance, id, filterIndex));
|
||||
}
|
||||
break;
|
||||
case hash("fil&_keytrack"): // also fil_keytrack
|
||||
|
|
@ -650,17 +659,13 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
setValueFromOpcode(opcode, filters[filterIndex].gain, Default::filterGainRange);
|
||||
}
|
||||
break;
|
||||
case hash("fil&_gain_oncc&"): // also fil_gain_oncc&
|
||||
case_any_ccN("fil&_gain"): // also fil_gain_oncc&
|
||||
{
|
||||
const auto filterIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
|
||||
return false;
|
||||
|
||||
setValueFromOpcode(
|
||||
opcode,
|
||||
filters[filterIndex].gainCC[opcode.parameters.back()],
|
||||
Default::filterGainModRange
|
||||
);
|
||||
processGenericCc(opcode, Default::filterGainModRange, ModKey::createNXYZ(ModId::FilGain, id, filterIndex));
|
||||
}
|
||||
break;
|
||||
case hash("fil&_type"): // also fil_type, filtype
|
||||
|
|
@ -683,107 +688,90 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
// Performance parameters: EQ
|
||||
case hash("eq&_bw"):
|
||||
{
|
||||
const auto eqNumber = opcode.parameters.front();
|
||||
if (eqNumber == 0)
|
||||
return false;
|
||||
if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
|
||||
return false;
|
||||
setValueFromOpcode(opcode, equalizers[eqNumber - 1].bandwidth, Default::eqBandwidthRange);
|
||||
}
|
||||
break;
|
||||
case hash("eq&_bw_oncc&"): // also eq&_bwcc&
|
||||
{
|
||||
const auto eqNumber = opcode.parameters.front();
|
||||
if (eqNumber == 0)
|
||||
return false;
|
||||
if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
|
||||
const auto eqIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
|
||||
return false;
|
||||
|
||||
setValueFromOpcode(opcode, equalizers[eqNumber - 1].bandwidthCC[opcode.parameters.back()], Default::eqBandwidthModRange);
|
||||
setValueFromOpcode(opcode, equalizers[eqIndex].bandwidth, Default::eqBandwidthRange);
|
||||
}
|
||||
break;
|
||||
case_any_ccN("eq&_bw"): // also eq&_bwcc&
|
||||
{
|
||||
const auto eqIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
|
||||
return false;
|
||||
|
||||
processGenericCc(opcode, Default::eqBandwidthModRange, ModKey::createNXYZ(ModId::EqBandwidth, id, eqIndex));
|
||||
}
|
||||
break;
|
||||
case hash("eq&_freq"):
|
||||
{
|
||||
const auto eqNumber = opcode.parameters.front();
|
||||
if (eqNumber == 0)
|
||||
const auto eqIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
|
||||
return false;
|
||||
if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
|
||||
return false;
|
||||
setValueFromOpcode(opcode, equalizers[eqNumber - 1].frequency, Default::eqFrequencyRange);
|
||||
setValueFromOpcode(opcode, equalizers[eqIndex].frequency, Default::eqFrequencyRange);
|
||||
}
|
||||
break;
|
||||
case hash("eq&_freq_oncc&"): // also eq&_freqcc&
|
||||
case_any_ccN("eq&_freq"): // also eq&_freqcc&
|
||||
{
|
||||
const auto eqNumber = opcode.parameters.front();
|
||||
if (eqNumber == 0)
|
||||
return false;
|
||||
if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
|
||||
const auto eqIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
|
||||
return false;
|
||||
|
||||
setValueFromOpcode(opcode, equalizers[eqNumber - 1].frequencyCC[opcode.parameters.back()], Default::eqFrequencyModRange);
|
||||
processGenericCc(opcode, Default::eqFrequencyModRange, ModKey::createNXYZ(ModId::EqFrequency, id, eqIndex));
|
||||
}
|
||||
break;
|
||||
case hash("eq&_vel&freq"):
|
||||
{
|
||||
const auto eqNumber = opcode.parameters.front();
|
||||
if (eqNumber == 0)
|
||||
return false;
|
||||
const auto eqIndex = opcode.parameters.front() - 1;
|
||||
if (opcode.parameters[1] != 2)
|
||||
return false; // was eqN_vel3freq or something else than eqN_vel2freq
|
||||
if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
|
||||
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
|
||||
return false;
|
||||
|
||||
setValueFromOpcode(opcode, equalizers[eqNumber - 1].vel2frequency, Default::eqFrequencyModRange);
|
||||
setValueFromOpcode(opcode, equalizers[eqIndex].vel2frequency, Default::eqFrequencyModRange);
|
||||
}
|
||||
break;
|
||||
case hash("eq&_gain"):
|
||||
{
|
||||
const auto eqNumber = opcode.parameters.front();
|
||||
if (eqNumber == 0)
|
||||
const auto eqIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
|
||||
return false;
|
||||
if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
|
||||
return false;
|
||||
setValueFromOpcode(opcode, equalizers[eqNumber - 1].gain, Default::eqGainRange);
|
||||
setValueFromOpcode(opcode, equalizers[eqIndex].gain, Default::eqGainRange);
|
||||
}
|
||||
break;
|
||||
case hash("eq&_gain_oncc&"): // also eq&_gaincc&
|
||||
case_any_ccN("eq&_gain"): // also eq&_gaincc&
|
||||
{
|
||||
const auto eqNumber = opcode.parameters.front();
|
||||
if (eqNumber == 0)
|
||||
return false;
|
||||
if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
|
||||
const auto eqIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
|
||||
return false;
|
||||
|
||||
setValueFromOpcode(opcode, equalizers[eqNumber - 1].gainCC[opcode.parameters.back()], Default::eqGainModRange);
|
||||
processGenericCc(opcode, Default::eqGainModRange, ModKey::createNXYZ(ModId::EqGain, id, eqIndex));
|
||||
}
|
||||
break;
|
||||
case hash("eq&_vel&gain"):
|
||||
{
|
||||
const auto eqNumber = opcode.parameters.front();
|
||||
if (eqNumber == 0)
|
||||
return false;
|
||||
const auto eqIndex = opcode.parameters.front() - 1;
|
||||
if (opcode.parameters[1] != 2)
|
||||
return false; // was eqN_vel3gain or something else than eqN_vel2gain
|
||||
if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
|
||||
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
|
||||
return false;
|
||||
|
||||
setValueFromOpcode(opcode, equalizers[eqNumber - 1].vel2gain, Default::eqGainModRange);
|
||||
setValueFromOpcode(opcode, equalizers[eqIndex].vel2gain, Default::eqGainModRange);
|
||||
}
|
||||
break;
|
||||
case hash("eq&_type"):
|
||||
{
|
||||
const auto eqNumber = opcode.parameters.front();
|
||||
if (eqNumber == 0)
|
||||
return false;
|
||||
if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs))
|
||||
const auto eqIndex = opcode.parameters.front() - 1;
|
||||
if (!extendIfNecessary(equalizers, eqIndex + 1, Default::numEQs))
|
||||
return false;
|
||||
|
||||
absl::optional<EqType> ftype = FilterEq::typeFromName(opcode.value);
|
||||
|
||||
if (ftype)
|
||||
equalizers[eqNumber - 1].type = *ftype;
|
||||
equalizers[eqIndex].type = *ftype;
|
||||
else {
|
||||
equalizers[eqNumber - 1].type = EqType::kEqNone;
|
||||
equalizers[eqIndex].type = EqType::kEqNone;
|
||||
DBG("Unknown EQ type: " << opcode.value);
|
||||
}
|
||||
}
|
||||
|
|
@ -1045,6 +1033,24 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
}
|
||||
}
|
||||
break;
|
||||
case hash("lfo&_cutoff&"):
|
||||
LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilCutoff, Default::filterCutoffModRange);
|
||||
break;
|
||||
case hash("lfo&_resonance&"):
|
||||
LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilResonance, Default::filterResonanceModRange);
|
||||
break;
|
||||
case hash("lfo&_fil&_gain"):
|
||||
LFO_EG_filter_EQ_target(ModId::LFO, ModId::FilGain, Default::filterGainModRange);
|
||||
break;
|
||||
case hash("lfo&_eq&gain"):
|
||||
LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqGain, Default::eqGainModRange);
|
||||
break;
|
||||
case hash("lfo&_eq&freq"):
|
||||
LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqFrequency, Default::eqFrequencyModRange);
|
||||
break;
|
||||
case hash("lfo&_eq&bw"):
|
||||
LFO_EG_filter_EQ_target(ModId::LFO, ModId::EqBandwidth, Default::eqBandwidthModRange);
|
||||
break;
|
||||
|
||||
// Modulation: Flex EG (targets)
|
||||
case hash("eg&_amplitude"):
|
||||
|
|
@ -1119,6 +1125,24 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
}
|
||||
}
|
||||
break;
|
||||
case hash("eg&_cutoff&"):
|
||||
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilCutoff, Default::filterCutoffModRange);
|
||||
break;
|
||||
case hash("eg&_resonance&"):
|
||||
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilResonance, Default::filterResonanceModRange);
|
||||
break;
|
||||
case hash("eg&_fil&_gain"):
|
||||
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::FilGain, Default::filterGainModRange);
|
||||
break;
|
||||
case hash("eg&_eq&gain"):
|
||||
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqGain, Default::eqGainModRange);
|
||||
break;
|
||||
case hash("eg&_eq&freq"):
|
||||
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqFrequency, Default::eqFrequencyModRange);
|
||||
break;
|
||||
case hash("eg&_eq&bw"):
|
||||
LFO_EG_filter_EQ_target(ModId::Envelope, ModId::EqBandwidth, Default::eqBandwidthModRange);
|
||||
break;
|
||||
|
||||
// Amplitude Envelope
|
||||
case hash("ampeg_attack"):
|
||||
|
|
@ -1321,6 +1345,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
return false;
|
||||
|
||||
#undef case_any_ccN
|
||||
#undef LFO_EG_filter_EQ_target
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@
|
|||
|
||||
#pragma once
|
||||
#include "SynthConfig.h"
|
||||
#include "MidiState.h"
|
||||
#include "FilePool.h"
|
||||
#include "BufferPool.h"
|
||||
#include "FilterPool.h"
|
||||
#include "EQPool.h"
|
||||
#include "Logger.h"
|
||||
#include "Wavetables.h"
|
||||
#include "Curve.h"
|
||||
|
|
@ -29,8 +28,6 @@ struct Resources
|
|||
Logger logger;
|
||||
CurveSet curves;
|
||||
FilePool filePool { logger };
|
||||
FilterPool filterPool { midiState };
|
||||
EQPool eqPool { midiState };
|
||||
WavetablePool wavePool;
|
||||
Tuning tuning;
|
||||
absl::optional<StretchTuning> stretch;
|
||||
|
|
@ -39,8 +36,6 @@ struct Resources
|
|||
void setSampleRate(float samplerate)
|
||||
{
|
||||
midiState.setSampleRate(samplerate);
|
||||
filterPool.setSampleRate(samplerate);
|
||||
eqPool.setSampleRate(samplerate);
|
||||
modMatrix.setSampleRate(samplerate);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,11 @@
|
|||
sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources)
|
||||
: id{voiceNumber}, stateListener(nullptr), resources(resources)
|
||||
{
|
||||
filters.reserve(config::filtersPerVoice);
|
||||
equalizers.reserve(config::eqsPerVoice);
|
||||
for (unsigned i = 0; i < config::filtersPerVoice; ++i)
|
||||
filters.emplace_back(resources);
|
||||
|
||||
for (unsigned i = 0; i < config::eqsPerVoice; ++i)
|
||||
equalizers.emplace_back(resources);
|
||||
|
||||
for (WavetableOscillator& osc : waveOscillators)
|
||||
osc.init(sampleRate);
|
||||
|
|
@ -118,21 +121,12 @@ void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event
|
|||
gainSmoother.reset();
|
||||
resetCrossfades();
|
||||
|
||||
// Check that we can handle the number of filters; filters should be cleared here
|
||||
ASSERT((filters.capacity() - filters.size()) >= region->filters.size());
|
||||
ASSERT((equalizers.capacity() - equalizers.size()) >= region->equalizers.size());
|
||||
|
||||
const unsigned numChannels = region->isStereo() ? 2 : 1;
|
||||
for (auto& filter: region->filters) {
|
||||
auto newFilter = resources.filterPool.getFilter(filter, numChannels, triggerEvent.number, triggerEvent.value);
|
||||
if (newFilter)
|
||||
filters.push_back(newFilter);
|
||||
for (unsigned i = 0; i < region->filters.size(); ++i) {
|
||||
filters[i].setup(*region, i, triggerEvent.number, triggerEvent.value);
|
||||
}
|
||||
|
||||
for (auto& eq: region->equalizers) {
|
||||
auto newEQ = resources.eqPool.getEQ(eq, numChannels, triggerEvent.value);
|
||||
if (newEQ)
|
||||
equalizers.push_back(newEQ);
|
||||
for (unsigned i = 0; i < region->equalizers.size(); ++i) {
|
||||
equalizers[i].setup(*region, i, triggerEvent.value);
|
||||
}
|
||||
|
||||
sourcePosition = region->getOffset();
|
||||
|
|
@ -253,6 +247,12 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept
|
|||
for (auto& lfo : lfos)
|
||||
lfo->setSampleRate(sampleRate);
|
||||
|
||||
for (auto& filter : filters)
|
||||
filter.setSampleRate(sampleRate);
|
||||
|
||||
for (auto& eq : equalizers)
|
||||
eq.setSampleRate(sampleRate);
|
||||
|
||||
powerFollower.setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
|
|
@ -498,12 +498,12 @@ void sfz::Voice::filterStageMono(AudioSpan<float> buffer) noexcept
|
|||
const auto leftBuffer = buffer.getSpan(0);
|
||||
const float* inputChannel[1] { leftBuffer.data() };
|
||||
float* outputChannel[1] { leftBuffer.data() };
|
||||
for (auto& filter : filters) {
|
||||
filter->process(inputChannel, outputChannel, numSamples);
|
||||
for (unsigned i = 0; i < region->filters.size(); ++i) {
|
||||
filters[i].process(inputChannel, outputChannel, numSamples);
|
||||
}
|
||||
|
||||
for (auto& eq : equalizers) {
|
||||
eq->process(inputChannel, outputChannel, numSamples);
|
||||
for (unsigned i = 0; i < region->equalizers.size(); ++i) {
|
||||
equalizers[i].process(inputChannel, outputChannel, numSamples);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -517,12 +517,12 @@ void sfz::Voice::filterStageStereo(AudioSpan<float> buffer) noexcept
|
|||
const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() };
|
||||
float* outputChannels[2] { leftBuffer.data(), rightBuffer.data() };
|
||||
|
||||
for (auto& filter : filters) {
|
||||
filter->process(inputChannels, outputChannels, numSamples);
|
||||
for (unsigned i = 0; i < region->filters.size(); ++i) {
|
||||
filters[i].process(inputChannels, outputChannels, numSamples);
|
||||
}
|
||||
|
||||
for (auto& eq : equalizers) {
|
||||
eq->process(inputChannels, outputChannels, numSamples);
|
||||
for (unsigned i = 0; i < region->equalizers.size(); ++i) {
|
||||
equalizers[i].process(inputChannels, outputChannels, numSamples);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -611,7 +611,7 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
|
|||
sourcePosition = indices->back();
|
||||
floatPositionOffset = coeffs->back();
|
||||
|
||||
#if 0
|
||||
#if 1
|
||||
ASSERT(!hasNanInf(buffer.getConstSpan(0)));
|
||||
ASSERT(!hasNanInf(buffer.getConstSpan(1)));
|
||||
SFIZZ_CHECK(isReasonableAudio(buffer.getConstSpan(0)));
|
||||
|
|
@ -731,8 +731,11 @@ void sfz::Voice::reset() noexcept
|
|||
|
||||
powerFollower.clear();
|
||||
|
||||
filters.clear();
|
||||
equalizers.clear();
|
||||
for (auto& filter : filters)
|
||||
filter.reset();
|
||||
|
||||
for (auto& eq : equalizers)
|
||||
eq.reset();
|
||||
|
||||
removeVoiceFromRing();
|
||||
}
|
||||
|
|
@ -776,16 +779,22 @@ uint32_t sfz::Voice::getSourcePosition() const noexcept
|
|||
|
||||
void sfz::Voice::setMaxFiltersPerVoice(size_t numFilters)
|
||||
{
|
||||
// There are filters in there, this call is unexpected
|
||||
ASSERT(filters.size() == 0);
|
||||
filters.reserve(numFilters);
|
||||
if (numFilters == filters.size())
|
||||
return;
|
||||
|
||||
filters.clear();
|
||||
for (unsigned i = 0; i < numFilters; ++i)
|
||||
filters.emplace_back(resources);
|
||||
}
|
||||
|
||||
void sfz::Voice::setMaxEQsPerVoice(size_t numFilters)
|
||||
{
|
||||
// There are filters in there, this call is unexpected
|
||||
ASSERT(equalizers.size() == 0);
|
||||
equalizers.reserve(numFilters);
|
||||
if (numFilters == equalizers.size())
|
||||
return;
|
||||
|
||||
equalizers.clear();
|
||||
for (unsigned i = 0; i < numFilters; ++i)
|
||||
equalizers.emplace_back(resources);
|
||||
}
|
||||
|
||||
void sfz::Voice::setMaxLFOsPerVoice(size_t numLFOs)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
#include "Region.h"
|
||||
#include "AudioBuffer.h"
|
||||
#include "Resources.h"
|
||||
#include "FilterPool.h"
|
||||
#include "EQPool.h"
|
||||
#include "Smoothers.h"
|
||||
#include "AudioSpan.h"
|
||||
#include "LeakDetector.h"
|
||||
|
|
@ -454,8 +456,8 @@ private:
|
|||
|
||||
Resources& resources;
|
||||
|
||||
std::vector<FilterHolderPtr> filters;
|
||||
std::vector<EQHolderPtr> equalizers;
|
||||
std::vector<FilterHolder> filters;
|
||||
std::vector<EQHolder> equalizers;
|
||||
std::vector<std::unique_ptr<LFO>> lfos;
|
||||
std::vector<std::unique_ptr<FlexEnvelope>> flexEGs;
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,18 @@ int ModIds::flags(ModId id) noexcept
|
|||
return kModIsPerVoice|kModIsAdditive;
|
||||
case ModId::Volume:
|
||||
return kModIsPerVoice|kModIsAdditive;
|
||||
case ModId::FilGain:
|
||||
return kModIsPerVoice|kModIsAdditive;
|
||||
case ModId::FilCutoff:
|
||||
return kModIsPerVoice|kModIsAdditive;
|
||||
case ModId::FilResonance:
|
||||
return kModIsPerVoice|kModIsAdditive;
|
||||
case ModId::EqGain:
|
||||
return kModIsPerVoice|kModIsAdditive;
|
||||
case ModId::EqFrequency:
|
||||
return kModIsPerVoice|kModIsAdditive;
|
||||
case ModId::EqBandwidth:
|
||||
return kModIsPerVoice|kModIsAdditive;
|
||||
|
||||
// unknown
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -37,6 +37,12 @@ enum class ModId : int {
|
|||
Position,
|
||||
Pitch,
|
||||
Volume,
|
||||
FilGain,
|
||||
FilCutoff,
|
||||
FilResonance,
|
||||
EqGain,
|
||||
EqFrequency,
|
||||
EqBandwidth,
|
||||
|
||||
_TargetsEnd,
|
||||
// [/targets] --------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -87,6 +87,18 @@ std::string ModKey::toString() const
|
|||
return absl::StrCat("Pitch {", region_.number(), "}");
|
||||
case ModId::Volume:
|
||||
return absl::StrCat("Volume {", region_.number(), "}");
|
||||
case ModId::FilGain:
|
||||
return absl::StrCat("FilterGain {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||
case ModId::FilCutoff:
|
||||
return absl::StrCat("FilterCutoff {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||
case ModId::FilResonance:
|
||||
return absl::StrCat("FilterResonance {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||
case ModId::EqGain:
|
||||
return absl::StrCat("EqGain {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||
case ModId::EqFrequency:
|
||||
return absl::StrCat("EqFrequency {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||
case ModId::EqBandwidth:
|
||||
return absl::StrCat("EqBandwidth {", region_.number(), ", N=", 1 + params_.N, "}");
|
||||
|
||||
default:
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ ModMatrix::TargetId ModMatrix::registerTarget(const ModKey& key)
|
|||
return id;
|
||||
}
|
||||
|
||||
ModMatrix::SourceId ModMatrix::findSource(const ModKey& key)
|
||||
ModMatrix::SourceId ModMatrix::findSource(const ModKey& key) const
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
||||
|
|
@ -179,7 +179,7 @@ ModMatrix::SourceId ModMatrix::findSource(const ModKey& key)
|
|||
return SourceId(it->second);
|
||||
}
|
||||
|
||||
ModMatrix::TargetId ModMatrix::findTarget(const ModKey& key)
|
||||
ModMatrix::TargetId ModMatrix::findTarget(const ModKey& key) const
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
||||
|
|
|
|||
|
|
@ -77,14 +77,14 @@ public:
|
|||
*
|
||||
* @param key source key
|
||||
*/
|
||||
SourceId findSource(const ModKey& key);
|
||||
SourceId findSource(const ModKey& key) const;
|
||||
|
||||
/**
|
||||
* @brief Look up a target by key.
|
||||
*
|
||||
* @param key target key
|
||||
*/
|
||||
TargetId findTarget(const ModKey& key);
|
||||
TargetId findTarget(const ModKey& key) const;
|
||||
|
||||
/**
|
||||
* @brief Connect a source and a destination inside the matrix.
|
||||
|
|
|
|||
|
|
@ -99,3 +99,135 @@ width_oncc425=29
|
|||
R"("Controller 425 {curve=0, smooth=0, value=29, step=0}" -> "Width {0}")",
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_CASE("[Modulations] Filter CC connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString("/modulation.sfz", R"(
|
||||
<region> sample=*sine
|
||||
cutoff=100 fil1_gain_oncc3=5 fil1_gain_stepcc3=0.5
|
||||
cutoff2=300 cutoff2_cc2=100 cutoff2_curvecc2=2
|
||||
resonance3=-1 resonance3_oncc1=2 resonance3_smoothcc1=10
|
||||
)");
|
||||
|
||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||
REQUIRE(graph == createReferenceGraph({
|
||||
R"("Controller 1 {curve=0, smooth=10, value=2, step=0}" -> "FilterResonance {0, N=3}")",
|
||||
R"("Controller 2 {curve=2, smooth=0, value=100, step=0}" -> "FilterCutoff {0, N=2}")",
|
||||
R"("Controller 3 {curve=0, smooth=0, value=5, step=0.5}" -> "FilterGain {0, N=1}")",
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_CASE("[Modulations] EQ CC connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString("/modulation.sfz", R"(
|
||||
<region> sample=*sine
|
||||
eq1_gain_oncc2=5 eq1_gain_stepcc2=0.5
|
||||
eq2_freq_oncc3=300 eq2_freq_curvecc3=3
|
||||
eq3_bw_oncc1=2 eq3_bw_smoothcc1=10
|
||||
)");
|
||||
|
||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||
REQUIRE(graph == createReferenceGraph({
|
||||
R"("Controller 1 {curve=0, smooth=10, value=2, step=0}" -> "EqBandwidth {0, N=3}")",
|
||||
R"("Controller 2 {curve=0, smooth=0, value=5, step=0.5}" -> "EqGain {0, N=1}")",
|
||||
R"("Controller 3 {curve=3, smooth=0, value=300, step=0}" -> "EqFrequency {0, N=2}")",
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_CASE("[Modulations] LFO Filter connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString("/modulation.sfz", R"(
|
||||
<region> sample=*sine
|
||||
lfo1_freq=0.1 lfo1_cutoff1=1
|
||||
lfo2_freq=1 lfo2_cutoff=2
|
||||
lfo3_freq=2 lfo3_resonance=3
|
||||
lfo4_freq=0.5 lfo4_resonance1=4
|
||||
lfo5_freq=0.5 lfo5_resonance2=5
|
||||
lfo6_freq=3 lfo6_fil1_gain=-1
|
||||
)");
|
||||
|
||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||
REQUIRE(graph == createReferenceGraph({
|
||||
R"("LFO 1 {0}" -> "FilterCutoff {0, N=1}")",
|
||||
R"("LFO 2 {0}" -> "FilterCutoff {0, N=1}")",
|
||||
R"("LFO 3 {0}" -> "FilterResonance {0, N=1}")",
|
||||
R"("LFO 4 {0}" -> "FilterResonance {0, N=1}")",
|
||||
R"("LFO 5 {0}" -> "FilterResonance {0, N=2}")",
|
||||
R"("LFO 6 {0}" -> "FilterGain {0, N=1}")",
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_CASE("[Modulations] EG Filter connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString("/modulation.sfz", R"(
|
||||
<region> sample=*sine
|
||||
eg1_time1=0.1 eg1_cutoff1=1
|
||||
eg2_time1=1 eg2_cutoff=2
|
||||
eg3_time1=2 eg3_resonance=3
|
||||
eg4_time1=0.5 eg4_resonance1=4
|
||||
eg5_time1=0.5 eg5_resonance2=5
|
||||
eg6_time1=3 eg6_fil1_gain=-1
|
||||
)");
|
||||
|
||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||
REQUIRE(graph == createReferenceGraph({
|
||||
R"("EG 1 {0}" -> "FilterCutoff {0, N=1}")",
|
||||
R"("EG 2 {0}" -> "FilterCutoff {0, N=1}")",
|
||||
R"("EG 3 {0}" -> "FilterResonance {0, N=1}")",
|
||||
R"("EG 4 {0}" -> "FilterResonance {0, N=1}")",
|
||||
R"("EG 5 {0}" -> "FilterResonance {0, N=2}")",
|
||||
R"("EG 6 {0}" -> "FilterGain {0, N=1}")",
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_CASE("[Modulations] LFO EQ connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString("/modulation.sfz", R"(
|
||||
<region> sample=*sine
|
||||
lfo1_freq=0.1 lfo1_eq1bw=1
|
||||
lfo2_freq=1 lfo2_eq2freq=2
|
||||
lfo3_freq=2 lfo3_eq3gain=3
|
||||
lfo4_freq=0.5 lfo4_eq3bw=4
|
||||
lfo5_freq=0.5 lfo5_eq2gain=5
|
||||
lfo6_freq=3 lfo6_eq1freq=-1
|
||||
)");
|
||||
|
||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||
REQUIRE(graph == createReferenceGraph({
|
||||
R"("LFO 1 {0}" -> "EqBandwidth {0, N=1}")",
|
||||
R"("LFO 2 {0}" -> "EqFrequency {0, N=2}")",
|
||||
R"("LFO 3 {0}" -> "EqGain {0, N=3}")",
|
||||
R"("LFO 4 {0}" -> "EqBandwidth {0, N=3}")",
|
||||
R"("LFO 5 {0}" -> "EqGain {0, N=2}")",
|
||||
R"("LFO 6 {0}" -> "EqFrequency {0, N=1}")",
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_CASE("[Modulations] EG EQ connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzString("/modulation.sfz", R"(
|
||||
<region> sample=*sine
|
||||
eg1_freq=0.1 eg1_eq1bw=1
|
||||
eg2_freq=1 eg2_eq2freq=2
|
||||
eg3_freq=2 eg3_eq3gain=3
|
||||
eg4_freq=0.5 eg4_eq3bw=4
|
||||
eg5_freq=0.5 eg5_eq2gain=5
|
||||
eg6_freq=3 eg6_eq1freq=-1
|
||||
)");
|
||||
|
||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||
REQUIRE(graph == createReferenceGraph({
|
||||
R"("EG 1 {0}" -> "EqBandwidth {0, N=1}")",
|
||||
R"("EG 2 {0}" -> "EqFrequency {0, N=2}")",
|
||||
R"("EG 3 {0}" -> "EqGain {0, N=3}")",
|
||||
R"("EG 4 {0}" -> "EqBandwidth {0, N=3}")",
|
||||
R"("EG 5 {0}" -> "EqGain {0, N=2}")",
|
||||
R"("EG 6 {0}" -> "EqFrequency {0, N=1}")",
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1313,9 +1313,6 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.filters[0].gain == 0);
|
||||
REQUIRE(region.filters[0].veltrack == 0);
|
||||
REQUIRE(region.filters[0].resonance == 0.0f);
|
||||
REQUIRE(region.filters[0].cutoffCC.empty());
|
||||
REQUIRE(region.filters[0].gainCC.empty());
|
||||
REQUIRE(region.filters[0].resonanceCC.empty());
|
||||
|
||||
region.parseOpcode({ "cutoff2", "5000" });
|
||||
REQUIRE(region.filters.size() == 2);
|
||||
|
|
@ -1327,9 +1324,6 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.filters[1].gain == 0);
|
||||
REQUIRE(region.filters[1].veltrack == 0);
|
||||
REQUIRE(region.filters[1].resonance == 0.0f);
|
||||
REQUIRE(region.filters[1].cutoffCC.empty());
|
||||
REQUIRE(region.filters[1].gainCC.empty());
|
||||
REQUIRE(region.filters[1].resonanceCC.empty());
|
||||
|
||||
region.parseOpcode({ "cutoff4", "50" });
|
||||
REQUIRE(region.filters.size() == 4);
|
||||
|
|
@ -1342,18 +1336,12 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.filters[2].gain == 0);
|
||||
REQUIRE(region.filters[2].veltrack == 0);
|
||||
REQUIRE(region.filters[2].resonance == 0.0f);
|
||||
REQUIRE(region.filters[2].cutoffCC.empty());
|
||||
REQUIRE(region.filters[2].gainCC.empty());
|
||||
REQUIRE(region.filters[2].resonanceCC.empty());
|
||||
REQUIRE(region.filters[3].keycenter == 60);
|
||||
REQUIRE(region.filters[3].type == FilterType::kFilterLpf2p);
|
||||
REQUIRE(region.filters[3].keytrack == 0);
|
||||
REQUIRE(region.filters[3].gain == 0);
|
||||
REQUIRE(region.filters[3].veltrack == 0);
|
||||
REQUIRE(region.filters[3].resonance == 0.0f);
|
||||
REQUIRE(region.filters[3].cutoffCC.empty());
|
||||
REQUIRE(region.filters[3].gainCC.empty());
|
||||
REQUIRE(region.filters[3].resonanceCC.empty());
|
||||
}
|
||||
|
||||
SECTION("Filter parameter dispatch")
|
||||
|
|
@ -1373,16 +1361,6 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.filters[1].veltrack == -100);
|
||||
region.parseOpcode({ "fil3_keytrack", "100" });
|
||||
REQUIRE(region.filters[2].keytrack == 100);
|
||||
REQUIRE(region.filters[0].cutoffCC.empty());
|
||||
region.parseOpcode({ "cutoff1_cc15", "210" });
|
||||
REQUIRE(region.filters[0].cutoffCC.contains(15));
|
||||
REQUIRE(region.filters[0].cutoffCC[15] == 210);
|
||||
region.parseOpcode({ "resonance3_cc24", "10" });
|
||||
REQUIRE(region.filters[2].resonanceCC.contains(24));
|
||||
REQUIRE(region.filters[2].resonanceCC[24] == 10);
|
||||
region.parseOpcode({ "fil2_gain_oncc12", "-50" });
|
||||
REQUIRE(region.filters[1].gainCC.contains(12));
|
||||
REQUIRE(region.filters[1].gainCC[12] == -50.0f);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1430,16 +1408,6 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.filters[0].gain == 96.0f);
|
||||
region.parseOpcode({ "fil_gain", "-200" });
|
||||
REQUIRE(region.filters[0].gain == -96.0f);
|
||||
|
||||
region.parseOpcode({ "cutoff_cc43", "10000" });
|
||||
REQUIRE(region.filters[0].cutoffCC[43] == 9600);
|
||||
region.parseOpcode({ "cutoff_cc43", "-10000" });
|
||||
REQUIRE(region.filters[0].cutoffCC[43] == -9600);
|
||||
|
||||
region.parseOpcode({ "resonance_cc43", "100" });
|
||||
REQUIRE(region.filters[0].resonanceCC[43] == 96.0f);
|
||||
region.parseOpcode({ "resonance_cc43", "-5" });
|
||||
REQUIRE(region.filters[0].resonanceCC[43] == 0.0f);
|
||||
}
|
||||
|
||||
SECTION("Filter types")
|
||||
|
|
@ -1512,9 +1480,6 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.equalizers[0].frequency == 0.0f);
|
||||
REQUIRE(region.equalizers[0].vel2frequency == 0);
|
||||
REQUIRE(region.equalizers[0].vel2gain == 0);
|
||||
REQUIRE(region.equalizers[0].frequencyCC.empty());
|
||||
REQUIRE(region.equalizers[0].bandwidthCC.empty());
|
||||
REQUIRE(region.equalizers[0].gainCC.empty());
|
||||
|
||||
region.parseOpcode({ "eq2_gain", "-400" });
|
||||
REQUIRE(region.equalizers.size() == 2);
|
||||
|
|
@ -1525,9 +1490,6 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.equalizers[1].frequency == 0.0f);
|
||||
REQUIRE(region.equalizers[1].vel2frequency == 0);
|
||||
REQUIRE(region.equalizers[1].vel2gain == 0);
|
||||
REQUIRE(region.equalizers[1].frequencyCC.empty());
|
||||
REQUIRE(region.equalizers[1].bandwidthCC.empty());
|
||||
REQUIRE(region.equalizers[1].gainCC.empty());
|
||||
|
||||
region.parseOpcode({ "eq4_gain", "500" });
|
||||
REQUIRE(region.equalizers.size() == 4);
|
||||
|
|
@ -1539,16 +1501,10 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.equalizers[2].frequency == 0.0f);
|
||||
REQUIRE(region.equalizers[2].vel2frequency == 0);
|
||||
REQUIRE(region.equalizers[2].vel2gain == 0);
|
||||
REQUIRE(region.equalizers[2].frequencyCC.empty());
|
||||
REQUIRE(region.equalizers[2].bandwidthCC.empty());
|
||||
REQUIRE(region.equalizers[2].gainCC.empty());
|
||||
REQUIRE(region.equalizers[3].bandwidth == 1.0f);
|
||||
REQUIRE(region.equalizers[3].frequency == 0.0f);
|
||||
REQUIRE(region.equalizers[3].vel2frequency == 0);
|
||||
REQUIRE(region.equalizers[3].vel2gain == 0);
|
||||
REQUIRE(region.equalizers[3].frequencyCC.empty());
|
||||
REQUIRE(region.equalizers[3].bandwidthCC.empty());
|
||||
REQUIRE(region.equalizers[3].gainCC.empty());
|
||||
}
|
||||
|
||||
SECTION("EQ types")
|
||||
|
|
@ -1578,24 +1534,8 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.equalizers[2].vel2gain == 10.0f);
|
||||
region.parseOpcode({ "eq1_vel2freq", "100" });
|
||||
REQUIRE(region.equalizers[0].vel2frequency == 100.0f);
|
||||
REQUIRE(region.equalizers[0].bandwidthCC.empty());
|
||||
region.parseOpcode({ "eq1_bwcc24", "0.5" });
|
||||
REQUIRE(region.equalizers[0].bandwidthCC.contains(24));
|
||||
REQUIRE(region.equalizers[0].bandwidthCC[24] == 0.5f);
|
||||
region.parseOpcode({ "eq1_bw_oncc24", "1.5" });
|
||||
REQUIRE(region.equalizers[0].bandwidthCC[24] == 1.5f);
|
||||
region.parseOpcode({ "eq3_freqcc15", "10" });
|
||||
REQUIRE(region.equalizers[2].frequencyCC.contains(15));
|
||||
REQUIRE(region.equalizers[2].frequencyCC[15] == 10.0f);
|
||||
region.parseOpcode({ "eq3_freq_oncc15", "20" });
|
||||
REQUIRE(region.equalizers[2].frequencyCC[15] == 20.0f);
|
||||
region.parseOpcode({ "eq1_type", "hshelf" });
|
||||
REQUIRE(region.equalizers[0].type == EqType::kEqHighShelf);
|
||||
region.parseOpcode({ "eq2_gaincc123", "2" });
|
||||
REQUIRE(region.equalizers[1].gainCC.contains(123));
|
||||
REQUIRE(region.equalizers[1].gainCC[123] == 2.0f);
|
||||
region.parseOpcode({ "eq2_gain_oncc123", "-2" });
|
||||
REQUIRE(region.equalizers[1].gainCC[123] == -2.0f);
|
||||
}
|
||||
|
||||
SECTION("EQ parameter values")
|
||||
|
|
@ -1625,24 +1565,6 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
REQUIRE(region.equalizers[0].vel2frequency == 30000.0f);
|
||||
region.parseOpcode({ "eq1_vel2freq", "-35000" });
|
||||
REQUIRE(region.equalizers[0].vel2frequency == -30000.0f);
|
||||
region.parseOpcode({ "eq1_bwcc15", "2" });
|
||||
REQUIRE(region.equalizers[0].bandwidthCC[15] == 2.0f);
|
||||
region.parseOpcode({ "eq1_bwcc15", "-5" });
|
||||
REQUIRE(region.equalizers[0].bandwidthCC[15] == -4.0f);
|
||||
region.parseOpcode({ "eq1_bwcc15", "5" });
|
||||
REQUIRE(region.equalizers[0].bandwidthCC[15] == 4.0f);
|
||||
region.parseOpcode({ "eq1_gaincc15", "2" });
|
||||
REQUIRE(region.equalizers[0].gainCC[15] == 2.0f);
|
||||
region.parseOpcode({ "eq1_gaincc15", "-500" });
|
||||
REQUIRE(region.equalizers[0].gainCC[15] == -96.0f);
|
||||
region.parseOpcode({ "eq1_gaincc15", "500" });
|
||||
REQUIRE(region.equalizers[0].gainCC[15] == 96.0f);
|
||||
region.parseOpcode({ "eq1_freqcc15", "200" });
|
||||
REQUIRE(region.equalizers[0].frequencyCC[15] == 200.0f);
|
||||
region.parseOpcode({ "eq1_freqcc15", "-50000" });
|
||||
REQUIRE(region.equalizers[0].frequencyCC[15] == -30000.0f);
|
||||
region.parseOpcode({ "eq1_freqcc15", "50000" });
|
||||
REQUIRE(region.equalizers[0].frequencyCC[15] == 30000.0f);
|
||||
}
|
||||
|
||||
SECTION("Effects send")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue