Merge pull request #166 from paulfd/curvecc-stepcc
Add support for curvecc and stepcc
This commit is contained in:
commit
1feb073d0f
27 changed files with 860 additions and 435 deletions
|
|
@ -10,7 +10,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "SfzHelpers.h"
|
#include "ModifierHelpers.h"
|
||||||
#include "absl/types/span.h"
|
#include "absl/types/span.h"
|
||||||
|
|
||||||
class EnvelopeFixture : public benchmark::Fixture {
|
class EnvelopeFixture : public benchmark::Fixture {
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,11 @@ public:
|
||||||
*/
|
*/
|
||||||
const ValueType& getWithDefault(int index) const noexcept
|
const ValueType& getWithDefault(int index) const noexcept
|
||||||
{
|
{
|
||||||
auto it = absl::c_lower_bound(container, index, CCValuePairComparator<ValueType>{});
|
auto it = absl::c_lower_bound(container, index, CCDataComparator<ValueType>{});
|
||||||
if (it == container.end() || it->cc != index) {
|
if (it == container.end() || it->cc != index) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
} else {
|
} else {
|
||||||
return it->value;
|
return it->data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,12 +60,12 @@ public:
|
||||||
*/
|
*/
|
||||||
ValueType& operator[](const int& index) noexcept
|
ValueType& operator[](const int& index) noexcept
|
||||||
{
|
{
|
||||||
auto it = absl::c_lower_bound(container, index, CCValuePairComparator<ValueType>{});
|
auto it = absl::c_lower_bound(container, index, CCDataComparator<ValueType>{});
|
||||||
if (it == container.end() || it->cc != index) {
|
if (it == container.end() || it->cc != index) {
|
||||||
auto inserted = container.insert(it, { index, defaultValue });
|
auto inserted = container.insert(it, { index, defaultValue });
|
||||||
return inserted->value;
|
return inserted->data;
|
||||||
} else {
|
} else {
|
||||||
return it->value;
|
return it->data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,16 +85,16 @@ public:
|
||||||
*/
|
*/
|
||||||
bool contains(int index) const noexcept
|
bool contains(int index) const noexcept
|
||||||
{
|
{
|
||||||
return absl::c_binary_search(container, index, CCValuePairComparator<ValueType>{});
|
return absl::c_binary_search(container, index, CCDataComparator<ValueType>{});
|
||||||
}
|
}
|
||||||
typename std::vector<CCValuePair<ValueType>>::const_iterator begin() const { return container.cbegin(); }
|
typename std::vector<CCData<ValueType>>::const_iterator begin() const { return container.cbegin(); }
|
||||||
typename std::vector<CCValuePair<ValueType>>::const_iterator end() const { return container.cend(); }
|
typename std::vector<CCData<ValueType>>::const_iterator end() const { return container.cend(); }
|
||||||
private:
|
private:
|
||||||
// typename std::vector<std::pair<int, ValueType>>::iterator begin() { return container.begin(); }
|
// typename std::vector<std::pair<int, ValueType>>::iterator begin() { return container.begin(); }
|
||||||
// typename std::vector<std::pair<int, ValueType>>::iterator end() { return container.end(); }
|
// typename std::vector<std::pair<int, ValueType>>::iterator end() { return container.end(); }
|
||||||
|
|
||||||
const ValueType defaultValue;
|
const ValueType defaultValue;
|
||||||
std::vector<CCValuePair<ValueType>> container;
|
std::vector<CCData<ValueType>> container;
|
||||||
LEAK_DETECTOR(CCMap);
|
LEAK_DETECTOR(CCMap);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ namespace config {
|
||||||
constexpr size_t powerHistoryLength { 16 };
|
constexpr size_t powerHistoryLength { 16 };
|
||||||
constexpr float voiceStealingThreshold { 0.00001f };
|
constexpr float voiceStealingThreshold { 0.00001f };
|
||||||
constexpr uint16_t numCCs { 512 };
|
constexpr uint16_t numCCs { 512 };
|
||||||
|
constexpr int maxCurves { 256 };
|
||||||
constexpr int chunkSize { 1024 };
|
constexpr int chunkSize { 1024 };
|
||||||
constexpr int filtersInPool { maxVoices * 2 };
|
constexpr int filtersInPool { maxVoices * 2 };
|
||||||
constexpr int filtersPerVoice { 2 };
|
constexpr int filtersPerVoice { 2 };
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,12 @@ void CurveSet::addCurve(const Curve& curve, int explicitIndex)
|
||||||
{
|
{
|
||||||
std::unique_ptr<Curve>* slot;
|
std::unique_ptr<Curve>* slot;
|
||||||
|
|
||||||
|
if (explicitIndex < -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (explicitIndex >= config::maxCurves)
|
||||||
|
return;
|
||||||
|
|
||||||
if (explicitIndex == -1) {
|
if (explicitIndex == -1) {
|
||||||
if (_useExplicitIndexing)
|
if (_useExplicitIndexing)
|
||||||
return; // reject implicit indices if any were explicit before
|
return; // reject implicit indices if any were explicit before
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ public:
|
||||||
*/
|
*/
|
||||||
float evalNormalized(float value) const
|
float evalNormalized(float value) const
|
||||||
{
|
{
|
||||||
return evalCC7(denormalize7Bits<int>(value));
|
return evalCC7(value * 127.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ namespace Default
|
||||||
constexpr Range<uint8_t> midi7Range { 0, 127 };
|
constexpr Range<uint8_t> midi7Range { 0, 127 };
|
||||||
constexpr Range<float> normalizedRange { 0.0f, 1.0f };
|
constexpr Range<float> normalizedRange { 0.0f, 1.0f };
|
||||||
constexpr Range<float> symmetricNormalizedRange { -1.0, 1.0 };
|
constexpr Range<float> symmetricNormalizedRange { -1.0, 1.0 };
|
||||||
constexpr float zeroModifier { 0.0f };
|
|
||||||
|
|
||||||
// Wavetable oscillator
|
// Wavetable oscillator
|
||||||
constexpr float oscillatorPhase { 0.0 };
|
constexpr float oscillatorPhase { 0.0 };
|
||||||
|
|
@ -82,6 +81,9 @@ namespace Default
|
||||||
// Region logic: MIDI conditions
|
// Region logic: MIDI conditions
|
||||||
constexpr Range<uint8_t> channelRange { 1, 16 };
|
constexpr Range<uint8_t> channelRange { 1, 16 };
|
||||||
constexpr Range<uint8_t> midiChannelRange { 0, 15 };
|
constexpr Range<uint8_t> midiChannelRange { 0, 15 };
|
||||||
|
constexpr Range<uint8_t> stepCCRange { 0, 127 };
|
||||||
|
constexpr Range<uint8_t> smoothCCRange { 0, 127 };
|
||||||
|
constexpr Range<uint8_t> curveCCRange { 0, 255 };
|
||||||
constexpr Range<uint16_t> ccNumberRange { 0, config::numCCs };
|
constexpr Range<uint16_t> ccNumberRange { 0, config::numCCs };
|
||||||
constexpr auto ccValueRange = normalizedRange;
|
constexpr auto ccValueRange = normalizedRange;
|
||||||
constexpr Range<int> bendRange = { -8192, 8192 };
|
constexpr Range<int> bendRange = { -8192, 8192 };
|
||||||
|
|
@ -108,17 +110,21 @@ namespace Default
|
||||||
constexpr float volume { 0.0f };
|
constexpr float volume { 0.0f };
|
||||||
constexpr Range<float> volumeRange { -144.0, 6.0 };
|
constexpr Range<float> volumeRange { -144.0, 6.0 };
|
||||||
constexpr Range<float> volumeCCRange { -144.0, 48.0 };
|
constexpr Range<float> volumeCCRange { -144.0, 48.0 };
|
||||||
|
constexpr Range<float> volumeStepRange { 0, 48.0 };
|
||||||
constexpr float amplitude { 100.0 };
|
constexpr float amplitude { 100.0 };
|
||||||
constexpr Range<float> amplitudeRange { 0.0, 100.0 };
|
constexpr Range<float> amplitudeRange { 0.0, 100.0 };
|
||||||
constexpr float pan { 0.0 };
|
constexpr float pan { 0.0 };
|
||||||
constexpr Range<float> panRange { -100.0, 100.0 };
|
constexpr Range<float> panRange { -100.0, 100.0 };
|
||||||
constexpr Range<float> panCCRange { -200.0, 200.0 };
|
constexpr Range<float> panCCRange { -200.0, 200.0 };
|
||||||
|
constexpr Range<float> panStepRange { 0.0, 200.0 };
|
||||||
constexpr float position { 0.0 };
|
constexpr float position { 0.0 };
|
||||||
constexpr Range<float> positionRange { -100.0, 100.0 };
|
constexpr Range<float> positionRange { -100.0, 100.0 };
|
||||||
constexpr Range<float> positionCCRange { -200.0, 200.0 };
|
constexpr Range<float> positionCCRange { -200.0, 200.0 };
|
||||||
|
constexpr Range<float> positionStepRange { 0.0, 200.0 };
|
||||||
constexpr float width { 100.0 };
|
constexpr float width { 100.0 };
|
||||||
constexpr Range<float> widthRange { -100.0, 100.0 };
|
constexpr Range<float> widthRange { -100.0, 100.0 };
|
||||||
constexpr Range<float> widthCCRange { -200.0, 200.0 };
|
constexpr Range<float> widthCCRange { -200.0, 200.0 };
|
||||||
|
constexpr Range<float> widthStepRange { 0.0, 200.0 };
|
||||||
constexpr uint8_t ampKeycenter { 60 };
|
constexpr uint8_t ampKeycenter { 60 };
|
||||||
constexpr float ampKeytrack { 0.0 };
|
constexpr float ampKeytrack { 0.0 };
|
||||||
constexpr Range<float> ampKeytrackRange { -96, 12 };
|
constexpr Range<float> ampKeytrackRange { -96, 12 };
|
||||||
|
|
@ -194,6 +200,7 @@ namespace Default
|
||||||
constexpr int tune { 0 };
|
constexpr int tune { 0 };
|
||||||
constexpr Range<int> tuneRange { -9600, 9600 }; // ±100 in SFZv1, more in ARIA
|
constexpr Range<int> tuneRange { -9600, 9600 }; // ±100 in SFZv1, more in ARIA
|
||||||
constexpr Range<int> tuneCCRange { -9600, 9600 };
|
constexpr Range<int> tuneCCRange { -9600, 9600 };
|
||||||
|
constexpr Range<int> tuneStepRange { 0, 9600 };
|
||||||
constexpr Range<int> bendBoundRange { -9600, 9600 };
|
constexpr Range<int> bendBoundRange { -9600, 9600 };
|
||||||
constexpr Range<int> bendStepRange { 1, 1200 };
|
constexpr Range<int> bendStepRange { 1, 1200 };
|
||||||
constexpr int bendUp { 200 }; // No range here because the bounds can be inverted
|
constexpr int bendUp { 200 }; // No range here because the bounds can be inverted
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,10 @@ namespace sfz {
|
||||||
* @param value
|
* @param value
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
inline float ccSwitchedValue(const MidiState& state, const absl::optional<CCValuePair<float>>& ccSwitch, float value) noexcept
|
inline float ccSwitchedValue(const MidiState& state, const absl::optional<CCData<float>>& ccSwitch, float value) noexcept
|
||||||
{
|
{
|
||||||
if (ccSwitch)
|
if (ccSwitch)
|
||||||
return value + ccSwitch->value * state.getCCValue(ccSwitch->cc);
|
return value + ccSwitch->data * state.getCCValue(ccSwitch->cc);
|
||||||
else
|
else
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
@ -80,13 +80,13 @@ struct EGDescription {
|
||||||
float vel2sustain { Default::vel2sustain };
|
float vel2sustain { Default::vel2sustain };
|
||||||
int vel2depth { Default::depth };
|
int vel2depth { Default::depth };
|
||||||
|
|
||||||
absl::optional<CCValuePair<float>> ccAttack;
|
absl::optional<CCData<float>> ccAttack;
|
||||||
absl::optional<CCValuePair<float>> ccDecay;
|
absl::optional<CCData<float>> ccDecay;
|
||||||
absl::optional<CCValuePair<float>> ccDelay;
|
absl::optional<CCData<float>> ccDelay;
|
||||||
absl::optional<CCValuePair<float>> ccHold;
|
absl::optional<CCData<float>> ccHold;
|
||||||
absl::optional<CCValuePair<float>> ccRelease;
|
absl::optional<CCData<float>> ccRelease;
|
||||||
absl::optional<CCValuePair<float>> ccStart;
|
absl::optional<CCData<float>> ccStart;
|
||||||
absl::optional<CCValuePair<float>> ccSustain;
|
absl::optional<CCData<float>> ccSustain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the attack with possibly a CC modifier and a velocity modifier
|
* @brief Get the attack with possibly a CC modifier and a velocity modifier
|
||||||
|
|
|
||||||
|
|
@ -29,17 +29,17 @@ void sfz::EQHolder::setup(const EQDescription& description, unsigned numChannels
|
||||||
// Setup the modulated values
|
// Setup the modulated values
|
||||||
lastFrequency = baseFrequency;
|
lastFrequency = baseFrequency;
|
||||||
for (const auto& mod : description.frequencyCC)
|
for (const auto& mod : description.frequencyCC)
|
||||||
lastFrequency += midiState.getCCValue(mod.cc) * mod.value;
|
lastFrequency += midiState.getCCValue(mod.cc) * mod.data;
|
||||||
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
|
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
|
||||||
|
|
||||||
lastBandwidth = baseBandwidth;
|
lastBandwidth = baseBandwidth;
|
||||||
for (const auto& mod : description.bandwidthCC)
|
for (const auto& mod : description.bandwidthCC)
|
||||||
lastBandwidth += midiState.getCCValue(mod.cc) * mod.value;
|
lastBandwidth += midiState.getCCValue(mod.cc) * mod.data;
|
||||||
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);
|
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);
|
||||||
|
|
||||||
lastGain = baseGain;
|
lastGain = baseGain;
|
||||||
for (const auto& mod : description.gainCC)
|
for (const auto& mod : description.gainCC)
|
||||||
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
lastGain += midiState.getCCValue(mod.cc) * mod.data;
|
||||||
lastGain = Default::filterGainRange.clamp(lastGain);
|
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||||
|
|
||||||
// Initialize the EQ
|
// Initialize the EQ
|
||||||
|
|
@ -62,17 +62,17 @@ void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numF
|
||||||
// For now we take the last value
|
// For now we take the last value
|
||||||
lastFrequency = baseFrequency;
|
lastFrequency = baseFrequency;
|
||||||
for (const auto& mod : description->frequencyCC)
|
for (const auto& mod : description->frequencyCC)
|
||||||
lastFrequency += midiState.getCCValue(mod.cc) * mod.value;
|
lastFrequency += midiState.getCCValue(mod.cc) * mod.data;
|
||||||
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
|
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
|
||||||
|
|
||||||
lastBandwidth = baseBandwidth;
|
lastBandwidth = baseBandwidth;
|
||||||
for (const auto& mod : description->bandwidthCC)
|
for (const auto& mod : description->bandwidthCC)
|
||||||
lastBandwidth += midiState.getCCValue(mod.cc) * mod.value;
|
lastBandwidth += midiState.getCCValue(mod.cc) * mod.data;
|
||||||
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);
|
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);
|
||||||
|
|
||||||
lastGain = baseGain;
|
lastGain = baseGain;
|
||||||
for (const auto& mod : description->gainCC)
|
for (const auto& mod : description->gainCC)
|
||||||
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
lastGain += midiState.getCCValue(mod.cc) * mod.data;
|
||||||
lastGain = Default::filterGainRange.clamp(lastGain);
|
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||||
|
|
||||||
if (lastGain == 0.0f) {
|
if (lastGain == 0.0f) {
|
||||||
|
|
|
||||||
|
|
@ -41,17 +41,17 @@ void sfz::FilterHolder::setup(const FilterDescription& description, unsigned num
|
||||||
// Setup the modulated values
|
// Setup the modulated values
|
||||||
lastCutoff = baseCutoff;
|
lastCutoff = baseCutoff;
|
||||||
for (const auto& mod : description.cutoffCC)
|
for (const auto& mod : description.cutoffCC)
|
||||||
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value);
|
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.data);
|
||||||
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
|
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
|
||||||
|
|
||||||
lastResonance = baseResonance;
|
lastResonance = baseResonance;
|
||||||
for (const auto& mod : description.resonanceCC)
|
for (const auto& mod : description.resonanceCC)
|
||||||
lastResonance += midiState.getCCValue(mod.cc) * mod.value;
|
lastResonance += midiState.getCCValue(mod.cc) * mod.data;
|
||||||
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
|
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
|
||||||
|
|
||||||
lastGain = baseGain;
|
lastGain = baseGain;
|
||||||
for (const auto& mod : description.gainCC)
|
for (const auto& mod : description.gainCC)
|
||||||
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
lastGain += midiState.getCCValue(mod.cc) * mod.data;
|
||||||
lastGain = Default::filterGainRange.clamp(lastGain);
|
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||||
|
|
||||||
// Initialize the filter
|
// Initialize the filter
|
||||||
|
|
@ -71,17 +71,17 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned
|
||||||
// TODO: the template deduction could be automatic here?
|
// TODO: the template deduction could be automatic here?
|
||||||
lastCutoff = baseCutoff;
|
lastCutoff = baseCutoff;
|
||||||
for (const auto& mod : description->cutoffCC)
|
for (const auto& mod : description->cutoffCC)
|
||||||
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value);
|
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.data);
|
||||||
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
|
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
|
||||||
|
|
||||||
lastResonance = baseResonance;
|
lastResonance = baseResonance;
|
||||||
for (const auto& mod : description->resonanceCC)
|
for (const auto& mod : description->resonanceCC)
|
||||||
lastResonance += midiState.getCCValue(mod.cc) * mod.value;
|
lastResonance += midiState.getCCValue(mod.cc) * mod.data;
|
||||||
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
|
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
|
||||||
|
|
||||||
lastGain = baseGain;
|
lastGain = baseGain;
|
||||||
for (const auto& mod : description->gainCC)
|
for (const auto& mod : description->gainCC)
|
||||||
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
lastGain += midiState.getCCValue(mod.cc) * mod.data;
|
||||||
lastGain = Default::filterGainRange.clamp(lastGain);
|
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||||
|
|
||||||
filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames);
|
filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames);
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,10 @@
|
||||||
#define CXX11_MOVE(x) std::move(x)
|
#define CXX11_MOVE(x) std::move(x)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if __cplusplus >= 201703L
|
||||||
|
#define IF_CONSTEXPR if constexpr
|
||||||
|
#else
|
||||||
|
#define IF_CONSTEXPR if
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,26 +15,27 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <cfenv>
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
constexpr T max(T op1, T op2)
|
constexpr T max(T op1, T op2)
|
||||||
{
|
{
|
||||||
return op1 > op2 ? op1 : op2;
|
return op1 > op2 ? op1 : op2;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T, class... Args>
|
template <class T, class... Args>
|
||||||
constexpr T max(T op1, Args... rest)
|
constexpr T max(T op1, Args... rest)
|
||||||
{
|
{
|
||||||
return max(op1, max(rest...));
|
return max(op1, max(rest...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
constexpr T min(T op1, T op2)
|
constexpr T min(T op1, T op2)
|
||||||
{
|
{
|
||||||
return op1 > op2 ? op2 : op1;
|
return op1 > op2 ? op2 : op1;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T, class... Args>
|
template <class T, class... Args>
|
||||||
constexpr T min(T op1, Args... rest)
|
constexpr T min(T op1, Args... rest)
|
||||||
{
|
{
|
||||||
return min(op1, min(rest...));
|
return min(op1, min(rest...));
|
||||||
|
|
@ -46,7 +47,7 @@ constexpr T min(T op1, Args... rest)
|
||||||
* @param op
|
* @param op
|
||||||
* @return T
|
* @return T
|
||||||
*/
|
*/
|
||||||
template<class T>
|
template <class T>
|
||||||
constexpr T power2(T in)
|
constexpr T power2(T in)
|
||||||
{
|
{
|
||||||
return in * in;
|
return in * in;
|
||||||
|
|
@ -111,8 +112,8 @@ constexpr Type mag2db(Type in)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
namespace Random {
|
namespace Random {
|
||||||
static std::random_device randomDevice;
|
static std::random_device randomDevice;
|
||||||
static std::minstd_rand randomGenerator { randomDevice() };
|
static std::minstd_rand randomGenerator { randomDevice() };
|
||||||
} // namespace Random
|
} // namespace Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -135,42 +136,42 @@ inline float midiNoteFrequency(const int noteNumber)
|
||||||
* @param hi
|
* @param hi
|
||||||
* @return T
|
* @return T
|
||||||
*/
|
*/
|
||||||
template<class T>
|
template <class T>
|
||||||
constexpr T clamp( T v, T lo, T hi )
|
constexpr T clamp(T v, T lo, T hi)
|
||||||
{
|
{
|
||||||
return max(min(v, hi), lo);
|
return max(min(v, hi), lo);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int Increment = 1, class T>
|
template <int Increment = 1, class T>
|
||||||
inline CXX14_CONSTEXPR void incrementAll(T& only)
|
inline CXX14_CONSTEXPR void incrementAll(T& only)
|
||||||
{
|
{
|
||||||
only += Increment;
|
only += Increment;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int Increment = 1, class T, class... Args>
|
template <int Increment = 1, class T, class... Args>
|
||||||
inline CXX14_CONSTEXPR void incrementAll(T& first, Args&... rest)
|
inline CXX14_CONSTEXPR void incrementAll(T& first, Args&... rest)
|
||||||
{
|
{
|
||||||
first += Increment;
|
first += Increment;
|
||||||
incrementAll<Increment>(rest...);
|
incrementAll<Increment>(rest...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class ValueType>
|
template <class ValueType>
|
||||||
constexpr ValueType linearInterpolation(ValueType left, ValueType right, ValueType leftCoeff, ValueType rightCoeff)
|
constexpr ValueType linearInterpolation(ValueType left, ValueType right, ValueType leftCoeff, ValueType rightCoeff)
|
||||||
{
|
{
|
||||||
return left * leftCoeff + right * rightCoeff;
|
return left * leftCoeff + right * rightCoeff;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Type>
|
template <class Type>
|
||||||
constexpr Type pi() { return static_cast<Type>(3.141592653589793238462643383279502884); };
|
constexpr Type pi() { return static_cast<Type>(3.141592653589793238462643383279502884); };
|
||||||
template<class Type>
|
template <class Type>
|
||||||
constexpr Type twoPi() { return pi<Type>() * 2; };
|
constexpr Type twoPi() { return pi<Type>() * 2; };
|
||||||
template<class Type>
|
template <class Type>
|
||||||
constexpr Type piTwo() { return pi<Type>() / 2; };
|
constexpr Type piTwo() { return pi<Type>() / 2; };
|
||||||
template<class Type>
|
template <class Type>
|
||||||
constexpr Type piFour() { return pi<Type>() / 4; };
|
constexpr Type piFour() { return pi<Type>() / 4; };
|
||||||
template<class Type>
|
template <class Type>
|
||||||
constexpr Type sqrtTwo() { return static_cast<Type>(1.414213562373095048801688724209698078569671875376948073176); };
|
constexpr Type sqrtTwo() { return static_cast<Type>(1.414213562373095048801688724209698078569671875376948073176); };
|
||||||
template<class Type>
|
template <class Type>
|
||||||
constexpr Type sqrtTwoInv() { return static_cast<Type>(0.707106781186547524400844362104849039284835937688474036588); };
|
constexpr Type sqrtTwoInv() { return static_cast<Type>(0.707106781186547524400844362104849039284835937688474036588); };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -205,23 +206,23 @@ inline Fraction<I>::operator float() const noexcept
|
||||||
template <class F>
|
template <class F>
|
||||||
struct FP_traits;
|
struct FP_traits;
|
||||||
|
|
||||||
template <> struct FP_traits<double>
|
template <>
|
||||||
{
|
struct FP_traits<double> {
|
||||||
typedef double type;
|
typedef double type;
|
||||||
typedef uint64_t same_size_int;
|
typedef uint64_t same_size_int;
|
||||||
static_assert(sizeof(type) == sizeof(same_size_int),
|
static_assert(sizeof(type) == sizeof(same_size_int),
|
||||||
"Unexpected size of floating point type");
|
"Unexpected size of floating point type");
|
||||||
static constexpr int e_bits = 11;
|
static constexpr int e_bits = 11;
|
||||||
static constexpr int m_bits = 52;
|
static constexpr int m_bits = 52;
|
||||||
static constexpr int e_offset = -1023;
|
static constexpr int e_offset = -1023;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct FP_traits<float>
|
template <>
|
||||||
{
|
struct FP_traits<float> {
|
||||||
typedef float type;
|
typedef float type;
|
||||||
typedef uint32_t same_size_int;
|
typedef uint32_t same_size_int;
|
||||||
static_assert(sizeof(type) == sizeof(same_size_int),
|
static_assert(sizeof(type) == sizeof(same_size_int),
|
||||||
"Unexpected size of floating point type");
|
"Unexpected size of floating point type");
|
||||||
static constexpr int e_bits = 8;
|
static constexpr int e_bits = 8;
|
||||||
static constexpr int m_bits = 23;
|
static constexpr int m_bits = 23;
|
||||||
static constexpr int e_offset = -127;
|
static constexpr int e_offset = -127;
|
||||||
|
|
@ -237,7 +238,10 @@ template <class F>
|
||||||
inline bool fp_sign(F x)
|
inline bool fp_sign(F x)
|
||||||
{
|
{
|
||||||
typedef FP_traits<F> T;
|
typedef FP_traits<F> T;
|
||||||
union { F real; typename T::same_size_int integer; } u;
|
union {
|
||||||
|
F real;
|
||||||
|
typename T::same_size_int integer;
|
||||||
|
} u;
|
||||||
u.real = x;
|
u.real = x;
|
||||||
return ((u.integer >> (T::e_bits + T::m_bits)) & 1) != 0;
|
return ((u.integer >> (T::e_bits + T::m_bits)) & 1) != 0;
|
||||||
}
|
}
|
||||||
|
|
@ -254,7 +258,10 @@ template <class F>
|
||||||
inline int fp_exponent(F x)
|
inline int fp_exponent(F x)
|
||||||
{
|
{
|
||||||
typedef FP_traits<F> T;
|
typedef FP_traits<F> T;
|
||||||
union { F real; typename T::same_size_int integer; } u;
|
union {
|
||||||
|
F real;
|
||||||
|
typename T::same_size_int integer;
|
||||||
|
} u;
|
||||||
u.real = x;
|
u.real = x;
|
||||||
int ex = (u.integer >> T::m_bits) & ((1u << T::e_bits) - 1);
|
int ex = (u.integer >> T::m_bits) & ((1u << T::e_bits) - 1);
|
||||||
return ex + T::e_offset;
|
return ex + T::e_offset;
|
||||||
|
|
@ -269,10 +276,13 @@ template <class F>
|
||||||
inline Fraction<uint64_t> fp_mantissa(F x)
|
inline Fraction<uint64_t> fp_mantissa(F x)
|
||||||
{
|
{
|
||||||
typedef FP_traits<F> T;
|
typedef FP_traits<F> T;
|
||||||
union { F real; typename T::same_size_int integer; } u;
|
union {
|
||||||
|
F real;
|
||||||
|
typename T::same_size_int integer;
|
||||||
|
} u;
|
||||||
u.real = x;
|
u.real = x;
|
||||||
Fraction<uint64_t> f;
|
Fraction<uint64_t> f;
|
||||||
f.den = uint64_t{1} << T::m_bits;
|
f.den = uint64_t { 1 } << T::m_bits;
|
||||||
f.num = u.integer & (f.den - 1);
|
f.num = u.integer & (f.den - 1);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
@ -287,10 +297,11 @@ inline F fp_from_parts(bool sgn, int ex, uint64_t mant)
|
||||||
{
|
{
|
||||||
typedef FP_traits<F> T;
|
typedef FP_traits<F> T;
|
||||||
typedef typename T::same_size_int I;
|
typedef typename T::same_size_int I;
|
||||||
union { F real; I integer; } u;
|
union {
|
||||||
u.integer = mant |
|
F real;
|
||||||
(static_cast<I>(ex - T::e_offset) << T::m_bits) |
|
I integer;
|
||||||
(static_cast<I>(sgn) << (T::e_bits + T::m_bits));
|
} u;
|
||||||
|
u.integer = mant | (static_cast<I>(ex - T::e_offset) << T::m_bits) | (static_cast<I>(sgn) << (T::e_bits + T::m_bits));
|
||||||
return u.real;
|
return u.real;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -328,3 +339,20 @@ bool isValidAudio(absl::Span<Type> span)
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ScopedRoundingMode {
|
||||||
|
public:
|
||||||
|
ScopedRoundingMode() = delete;
|
||||||
|
ScopedRoundingMode(int newRoundingMode)
|
||||||
|
: savedFloatMode(std::fegetround())
|
||||||
|
{
|
||||||
|
std::fesetround(newRoundingMode);
|
||||||
|
}
|
||||||
|
~ScopedRoundingMode()
|
||||||
|
{
|
||||||
|
std::fesetround(savedFloatMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int savedFloatMode;
|
||||||
|
};
|
||||||
|
|
|
||||||
244
src/sfizz/ModifierHelpers.h
Normal file
244
src/sfizz/ModifierHelpers.h
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Range.h"
|
||||||
|
#include "Defaults.h"
|
||||||
|
#include "SfzHelpers.h"
|
||||||
|
#include "Resources.h"
|
||||||
|
#include "absl/types/span.h"
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute a crossfade in value with respect to a crossfade range (note, velocity, cc, ...)
|
||||||
|
*/
|
||||||
|
template <class T, class U>
|
||||||
|
float crossfadeIn(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
||||||
|
{
|
||||||
|
if (value < crossfadeRange.getStart())
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
const auto length = static_cast<float>(crossfadeRange.length());
|
||||||
|
if (length == 0.0f)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
else if (value < crossfadeRange.getEnd()) {
|
||||||
|
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
||||||
|
if (curve == SfzCrossfadeCurve::power)
|
||||||
|
return sqrt(crossfadePosition);
|
||||||
|
if (curve == SfzCrossfadeCurve::gain)
|
||||||
|
return crossfadePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute a crossfade out value with respect to a crossfade range (note, velocity, cc, ...)
|
||||||
|
*/
|
||||||
|
template <class T, class U>
|
||||||
|
float crossfadeOut(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
||||||
|
{
|
||||||
|
if (value > crossfadeRange.getEnd())
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
const auto length = static_cast<float>(crossfadeRange.length());
|
||||||
|
if (length == 0.0f)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
else if (value > crossfadeRange.getStart()) {
|
||||||
|
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
||||||
|
if (curve == SfzCrossfadeCurve::power)
|
||||||
|
return std::sqrt(1 - crossfadePosition);
|
||||||
|
if (curve == SfzCrossfadeCurve::gain)
|
||||||
|
return 1 - crossfadePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
||||||
|
{
|
||||||
|
ASSERT(events.size() > 0);
|
||||||
|
ASSERT(events[0].delay == 0);
|
||||||
|
if (envelope.size() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||||
|
|
||||||
|
auto lastValue = lambda(events[0].value);
|
||||||
|
auto lastDelay = events[0].delay;
|
||||||
|
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||||
|
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||||
|
const auto step = (lambda(events[i].value) - lastValue) / length;
|
||||||
|
lastValue = linearRamp<float>(envelope.subspan(lastDelay, length), lastValue, step);
|
||||||
|
lastDelay += length;
|
||||||
|
}
|
||||||
|
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
||||||
|
{
|
||||||
|
ASSERT(events.size() > 0);
|
||||||
|
ASSERT(events[0].delay == 0);
|
||||||
|
ASSERT(step != 0.0);
|
||||||
|
|
||||||
|
if (envelope.size() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto quantize = [step](float value) -> float {
|
||||||
|
return std::trunc(value / step) * step;
|
||||||
|
};
|
||||||
|
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||||
|
|
||||||
|
auto lastValue = quantize(lambda(events[0].value));
|
||||||
|
auto lastDelay = events[0].delay;
|
||||||
|
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||||
|
const auto nextValue = quantize(lambda(events[i].value));
|
||||||
|
const auto difference = std::abs(nextValue - lastValue);
|
||||||
|
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||||
|
|
||||||
|
if (difference < step) {
|
||||||
|
fill<float>(envelope.subspan(lastDelay, length), lastValue);
|
||||||
|
lastValue = nextValue;
|
||||||
|
lastDelay += length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto numSteps = static_cast<int>(difference / step);
|
||||||
|
const auto stepLength = static_cast<int>(length / numSteps);
|
||||||
|
for (int i = 0; i < numSteps; ++i) {
|
||||||
|
fill<float>(envelope.subspan(lastDelay, stepLength), lastValue);
|
||||||
|
lastValue += lastValue <= nextValue ? step : -step;
|
||||||
|
lastDelay += stepLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
||||||
|
{
|
||||||
|
ASSERT(events.size() > 0);
|
||||||
|
ASSERT(events[0].delay == 0);
|
||||||
|
|
||||||
|
if (envelope.size() == 0)
|
||||||
|
return;
|
||||||
|
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||||
|
|
||||||
|
auto lastValue = lambda(events[0].value);
|
||||||
|
auto lastDelay = events[0].delay;
|
||||||
|
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||||
|
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||||
|
const auto nextValue = lambda(events[i].value);
|
||||||
|
const auto step = std::exp((std::log(nextValue) - std::log(lastValue)) / length);
|
||||||
|
multiplicativeRamp<float>(envelope.subspan(lastDelay, length), lastValue, step);
|
||||||
|
lastValue = nextValue;
|
||||||
|
lastDelay += length;
|
||||||
|
}
|
||||||
|
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F, bool Round = false>
|
||||||
|
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
||||||
|
{
|
||||||
|
ASSERT(events.size() > 0);
|
||||||
|
ASSERT(events[0].delay == 0);
|
||||||
|
ASSERT(step != 0.0f);
|
||||||
|
|
||||||
|
ScopedRoundingMode roundingMode { Round ? FE_TONEAREST : FE_TOWARDZERO };
|
||||||
|
|
||||||
|
if (envelope.size() == 0)
|
||||||
|
return;
|
||||||
|
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||||
|
const auto logStep = std::log(step);
|
||||||
|
// If we assume that a = b.q^r for b in (1, q) then
|
||||||
|
// log a log b
|
||||||
|
// ----- = ----- + r
|
||||||
|
// log q log q
|
||||||
|
// and log(b)\log(q) is between 0 and 1.
|
||||||
|
auto quantize = [logStep](float value) -> float {
|
||||||
|
return std::exp(logStep * std::rint(std::log(value) / logStep));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto lastValue = quantize(lambda(events[0].value));
|
||||||
|
auto lastDelay = events[0].delay;
|
||||||
|
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||||
|
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||||
|
const auto nextValue = quantize(lambda(events[i].value));
|
||||||
|
const auto difference = nextValue > lastValue ? nextValue / lastValue : lastValue / nextValue;
|
||||||
|
|
||||||
|
if (difference < step) {
|
||||||
|
fill<float>(envelope.subspan(lastDelay, length), lastValue);
|
||||||
|
lastValue = nextValue;
|
||||||
|
lastDelay += length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto numSteps = std::round(std::log(difference) / logStep);
|
||||||
|
const auto stepLength = static_cast<int>(length / numSteps);
|
||||||
|
for (int i = 0; i < static_cast<int>(numSteps); ++i) {
|
||||||
|
fill<float>(envelope.subspan(lastDelay, stepLength), lastValue);
|
||||||
|
lastValue = nextValue > lastValue ? lastValue * step : lastValue / step;
|
||||||
|
lastDelay += stepLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void pitchBendEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
||||||
|
{
|
||||||
|
multiplicativeEnvelope<F, true>(events, envelope, std::forward<F>(lambda), step);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void pitchBendEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
||||||
|
{
|
||||||
|
multiplicativeEnvelope<F>(events, envelope, std::forward<F>(lambda));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void linearModifier(const sfz::Resources& resources, absl::Span<float> span, const sfz::CCData<sfz::Modifier>& ccData, F&& lambda)
|
||||||
|
{
|
||||||
|
const auto events = resources.midiState.getCCEvents(ccData.cc);
|
||||||
|
const auto curve = resources.curves.getCurve(ccData.data.curve);
|
||||||
|
if (ccData.data.step == 0.0f) {
|
||||||
|
linearEnvelope(events, span, [&ccData, &curve, &lambda](float x) {
|
||||||
|
return lambda(curve.evalNormalized(x) * ccData.data.value);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const float stepSize { lambda(ccData.data.step) };
|
||||||
|
linearEnvelope(
|
||||||
|
events, span, [&ccData, &curve, &lambda](float x) {
|
||||||
|
return lambda(curve.evalNormalized(x) * ccData.data.value);
|
||||||
|
},
|
||||||
|
stepSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void multiplicativeModifier(const sfz::Resources& resources, absl::Span<float> span, const sfz::CCData<sfz::Modifier>& ccData, F&& lambda)
|
||||||
|
{
|
||||||
|
const auto events = resources.midiState.getCCEvents(ccData.cc);
|
||||||
|
const auto curve = resources.curves.getCurve(ccData.data.curve);
|
||||||
|
if (ccData.data.step == 0.0f) {
|
||||||
|
multiplicativeEnvelope(events, span, [&ccData, &curve, &lambda](float x) {
|
||||||
|
return lambda(curve.evalNormalized(x) * ccData.data.value);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const float stepSize { lambda(ccData.data.step) };
|
||||||
|
multiplicativeEnvelope(
|
||||||
|
events, span, [&ccData, &curve, &lambda](float x) {
|
||||||
|
return lambda(curve.evalNormalized(x) * ccData.data.value);
|
||||||
|
},
|
||||||
|
stepSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void linearModifier(const sfz::Resources& resources, absl::Span<float> span, const sfz::CCData<sfz::Modifier>& ccData)
|
||||||
|
{
|
||||||
|
linearModifier(resources, span, ccData, [](float x) { return x; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -185,7 +185,7 @@ inline void setRangeStartFromOpcode(const Opcode& opcode, Range<ValueType>& targ
|
||||||
* @param validRange the range of admitted values used to clamp the opcode
|
* @param validRange the range of admitted values used to clamp the opcode
|
||||||
*/
|
*/
|
||||||
template <class ValueType>
|
template <class ValueType>
|
||||||
inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional<CCValuePair<ValueType>>& target, const Range<ValueType>& validRange)
|
inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional<CCData<ValueType>>& target, const Range<ValueType>& validRange)
|
||||||
{
|
{
|
||||||
auto value = readOpcode(opcode.value, validRange);
|
auto value = readOpcode(opcode.value, validRange);
|
||||||
if (value && Default::ccNumberRange.containsWithEnd(opcode.parameters.back()))
|
if (value && Default::ccNumberRange.containsWithEnd(opcode.parameters.back()))
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,12 @@
|
||||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
#include "Defaults.h"
|
|
||||||
#include "MathHelpers.h"
|
#include "MathHelpers.h"
|
||||||
#include "Macros.h"
|
#include "Macros.h"
|
||||||
#include "Debug.h"
|
#include "Debug.h"
|
||||||
#include "Opcode.h"
|
#include "Opcode.h"
|
||||||
#include "StringViewHelpers.h"
|
#include "StringViewHelpers.h"
|
||||||
#include "MidiState.h"
|
#include "ModifierHelpers.h"
|
||||||
#include "absl/strings/str_replace.h"
|
#include "absl/strings/str_replace.h"
|
||||||
#include "absl/strings/str_cat.h"
|
#include "absl/strings/str_cat.h"
|
||||||
#include "absl/algorithm/container.h"
|
#include "absl/algorithm/container.h"
|
||||||
|
|
@ -297,55 +296,146 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
case hash("volume"):
|
case hash("volume"):
|
||||||
setValueFromOpcode(opcode, volume, Default::volumeRange);
|
setValueFromOpcode(opcode, volume, Default::volumeRange);
|
||||||
break;
|
break;
|
||||||
|
case hash("volume_curvecc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||||
|
volumeCC[opcode.parameters.back()].curve = *value;
|
||||||
|
break;
|
||||||
|
case hash("volume_stepcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::volumeStepRange))
|
||||||
|
volumeCC[opcode.parameters.back()].step = *value;
|
||||||
|
break;
|
||||||
|
case hash("volume_smoothcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||||
|
volumeCC[opcode.parameters.back()].smooth = *value;
|
||||||
|
break;
|
||||||
case hash("gain_cc&"):
|
case hash("gain_cc&"):
|
||||||
case hash("gain_oncc&"): // fallthrough
|
case hash("gain_oncc&"): // fallthrough
|
||||||
case hash("volume_oncc&"):
|
case hash("volume_oncc&"):
|
||||||
if (opcode.parameters.back() > config::numCCs)
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
return false;
|
return false;
|
||||||
if (auto value = readOpcode(opcode.value, Default::volumeCCRange))
|
if (auto value = readOpcode(opcode.value, Default::volumeCCRange))
|
||||||
volumeCC[opcode.parameters.back()] = *value;
|
volumeCC[opcode.parameters.back()].value = *value;
|
||||||
break;
|
break;
|
||||||
case hash("amplitude"):
|
case hash("amplitude"):
|
||||||
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
||||||
amplitude = normalizePercents(*value);
|
amplitude = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
|
case hash("amplitude_curvecc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||||
|
amplitudeCC[opcode.parameters.back()].curve = *value;
|
||||||
|
break;
|
||||||
|
case hash("amplitude_stepcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
||||||
|
amplitudeCC[opcode.parameters.back()].step = normalizePercents(*value);
|
||||||
|
break;
|
||||||
|
case hash("amplitude_smoothcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||||
|
amplitudeCC[opcode.parameters.back()].smooth = *value;
|
||||||
|
break;
|
||||||
case hash("amplitude_cc&"): // fallthrough
|
case hash("amplitude_cc&"): // fallthrough
|
||||||
case hash("amplitude_oncc&"):
|
case hash("amplitude_oncc&"):
|
||||||
if (opcode.parameters.back() > config::numCCs)
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
return false;
|
return false;
|
||||||
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
||||||
amplitudeCC[opcode.parameters.back()] = normalizePercents(*value);
|
amplitudeCC[opcode.parameters.back()].value = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("pan"):
|
case hash("pan"):
|
||||||
if (auto value = readOpcode(opcode.value, Default::panRange))
|
if (auto value = readOpcode(opcode.value, Default::panRange))
|
||||||
pan = normalizePercents(*value);
|
pan = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
|
case hash("pan_curvecc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||||
|
panCC[opcode.parameters.back()].curve = *value;
|
||||||
|
break;
|
||||||
|
case hash("pan_stepcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::panStepRange))
|
||||||
|
panCC[opcode.parameters.back()].step = normalizePercents(*value);
|
||||||
|
break;
|
||||||
|
case hash("pan_smoothcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||||
|
panCC[opcode.parameters.back()].smooth = *value;
|
||||||
|
break;
|
||||||
case hash("pan_cc&"):
|
case hash("pan_cc&"):
|
||||||
case hash("pan_oncc&"):
|
case hash("pan_oncc&"):
|
||||||
if (opcode.parameters.back() > config::numCCs)
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
return false;
|
return false;
|
||||||
if (auto value = readOpcode(opcode.value, Default::panCCRange))
|
if (auto value = readOpcode(opcode.value, Default::panCCRange))
|
||||||
panCC[opcode.parameters.back()] = normalizePercents(*value);
|
panCC[opcode.parameters.back()].value = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("position"):
|
case hash("position"):
|
||||||
if (auto value = readOpcode(opcode.value, Default::positionRange))
|
if (auto value = readOpcode(opcode.value, Default::positionRange))
|
||||||
position = normalizePercents(*value);
|
position = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
|
case hash("position_curvecc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||||
|
positionCC[opcode.parameters.back()].curve = *value;
|
||||||
|
break;
|
||||||
|
case hash("position_stepcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::positionStepRange))
|
||||||
|
positionCC[opcode.parameters.back()].step = normalizePercents(*value);
|
||||||
|
break;
|
||||||
|
case hash("position_smoothcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||||
|
positionCC[opcode.parameters.back()].smooth = *value;
|
||||||
|
break;
|
||||||
|
case hash("position_cc&"): // fallthrough
|
||||||
case hash("position_oncc&"):
|
case hash("position_oncc&"):
|
||||||
if (opcode.parameters.back() > config::numCCs)
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
return false;
|
return false;
|
||||||
if (auto value = readOpcode(opcode.value, Default::positionCCRange))
|
if (auto value = readOpcode(opcode.value, Default::positionCCRange))
|
||||||
positionCC[opcode.parameters.back()] = normalizePercents(*value);
|
positionCC[opcode.parameters.back()].value = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("width"):
|
case hash("width"):
|
||||||
if (auto value = readOpcode(opcode.value, Default::widthRange))
|
if (auto value = readOpcode(opcode.value, Default::widthRange))
|
||||||
width = normalizePercents(*value);
|
width = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
|
case hash("width_curvecc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||||
|
widthCC[opcode.parameters.back()].curve = *value;
|
||||||
|
break;
|
||||||
|
case hash("width_stepcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::widthStepRange))
|
||||||
|
widthCC[opcode.parameters.back()].step = normalizePercents(*value);
|
||||||
|
break;
|
||||||
|
case hash("width_smoothcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||||
|
widthCC[opcode.parameters.back()].smooth = *value;
|
||||||
|
break;
|
||||||
case hash("width_oncc&"):
|
case hash("width_oncc&"):
|
||||||
if (opcode.parameters.back() > config::numCCs)
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
return false;
|
return false;
|
||||||
if (auto value = readOpcode(opcode.value, Default::widthCCRange))
|
if (auto value = readOpcode(opcode.value, Default::widthCCRange))
|
||||||
widthCC[opcode.parameters.back()] = normalizePercents(*value);
|
widthCC[opcode.parameters.back()].value = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("amp_keycenter"):
|
case hash("amp_keycenter"):
|
||||||
setValueFromOpcode(opcode, ampKeycenter, Default::keyRange);
|
setValueFromOpcode(opcode, ampKeycenter, Default::keyRange);
|
||||||
|
|
@ -726,6 +816,27 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
case hash("pitch"):
|
case hash("pitch"):
|
||||||
setValueFromOpcode(opcode, tune, Default::tuneRange);
|
setValueFromOpcode(opcode, tune, Default::tuneRange);
|
||||||
break;
|
break;
|
||||||
|
case hash("pitch_curvecc&"): // fallthrough
|
||||||
|
case hash("tune_curvecc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::curveCCRange))
|
||||||
|
tuneCC[opcode.parameters.back()].curve = *value;
|
||||||
|
break;
|
||||||
|
case hash("pitch_stepcc&"): // fallthrough
|
||||||
|
case hash("tune_stepcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::tuneStepRange))
|
||||||
|
tuneCC[opcode.parameters.back()].step = *value;
|
||||||
|
break;
|
||||||
|
case hash("pitch_smoothcc&"): // fallthrough
|
||||||
|
case hash("tune_smoothcc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::smoothCCRange))
|
||||||
|
tuneCC[opcode.parameters.back()].smooth = *value;
|
||||||
|
break;
|
||||||
case hash("tune_cc&"):
|
case hash("tune_cc&"):
|
||||||
case hash("tune_oncc&"):
|
case hash("tune_oncc&"):
|
||||||
case hash("pitch_cc&"):
|
case hash("pitch_cc&"):
|
||||||
|
|
@ -733,7 +844,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
if (opcode.parameters.back() > config::numCCs)
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
return false;
|
return false;
|
||||||
if (auto value = readOpcode(opcode.value, Default::tuneCCRange))
|
if (auto value = readOpcode(opcode.value, Default::tuneCCRange))
|
||||||
tuneCC[opcode.parameters.back()] = *value;
|
tuneCC[opcode.parameters.back()].value = *value;
|
||||||
break;
|
break;
|
||||||
case hash("bend_up"):
|
case hash("bend_up"):
|
||||||
setValueFromOpcode(opcode, bendUp, Default::bendBoundRange);
|
setValueFromOpcode(opcode, bendUp, Default::bendBoundRange);
|
||||||
|
|
@ -1078,15 +1189,15 @@ float sfz::Region::getCrossfadeGain() const noexcept
|
||||||
float gain { 1.0f };
|
float gain { 1.0f };
|
||||||
|
|
||||||
// Crossfades due to CC states
|
// Crossfades due to CC states
|
||||||
for (const auto& valuePair : crossfadeCCInRange) {
|
for (const auto& ccData : crossfadeCCInRange) {
|
||||||
const auto ccValue = midiState.getCCValue(valuePair.cc);
|
const auto ccValue = midiState.getCCValue(ccData.cc);
|
||||||
const auto crossfadeRange = valuePair.value;
|
const auto crossfadeRange = ccData.data;
|
||||||
gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve);
|
gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& valuePair : crossfadeCCOutRange) {
|
for (const auto& ccData : crossfadeCCOutRange) {
|
||||||
const auto ccValue = midiState.getCCValue(valuePair.cc);
|
const auto ccValue = midiState.getCCValue(ccData.cc);
|
||||||
const auto crossfadeRange = valuePair.value;
|
const auto crossfadeRange = ccData.data;
|
||||||
gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve);
|
gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,8 @@
|
||||||
#include "Opcode.h"
|
#include "Opcode.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
#include "MidiState.h"
|
#include "MidiState.h"
|
||||||
#include "absl/strings/str_cat.h"
|
#include "absl/types/optional.h"
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <absl/types/optional.h>
|
|
||||||
#include <random>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
@ -285,11 +283,11 @@ struct Region {
|
||||||
float pan { normalizePercents(Default::pan) }; // pan
|
float pan { normalizePercents(Default::pan) }; // pan
|
||||||
float width { normalizePercents(Default::width) }; // width
|
float width { normalizePercents(Default::width) }; // width
|
||||||
float position { normalizePercents(Default::position) }; // position
|
float position { normalizePercents(Default::position) }; // position
|
||||||
CCMap<float> volumeCC { Default::zeroModifier }; // volume_oncc
|
CCMap<Modifier> volumeCC { {} }; // volume_oncc
|
||||||
CCMap<float> amplitudeCC { Default::zeroModifier }; // amplitude_oncc
|
CCMap<Modifier> amplitudeCC { {} }; // amplitude_oncc
|
||||||
CCMap<float> panCC { Default::zeroModifier }; // pan_oncc
|
CCMap<Modifier> panCC { {} }; // pan_oncc
|
||||||
CCMap<float> widthCC { Default::zeroModifier }; // width_oncc
|
CCMap<Modifier> widthCC { {} }; // width_oncc
|
||||||
CCMap<float> positionCC { Default::zeroModifier }; // position_oncc
|
CCMap<Modifier> positionCC { {} }; // position_oncc
|
||||||
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
|
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
|
||||||
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
||||||
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
|
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
|
||||||
|
|
@ -317,7 +315,7 @@ struct Region {
|
||||||
int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack
|
int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack
|
||||||
int transpose { Default::transpose }; // transpose
|
int transpose { Default::transpose }; // transpose
|
||||||
int tune { Default::tune }; // tune
|
int tune { Default::tune }; // tune
|
||||||
CCMap<int> tuneCC { Default::tune };
|
CCMap<Modifier> tuneCC { {} };
|
||||||
int bendUp { Default::bendUp };
|
int bendUp { Default::bendUp };
|
||||||
int bendDown { Default::bendDown };
|
int bendDown { Default::bendDown };
|
||||||
int bendStep { Default::bendStep };
|
int bendStep { Default::bendStep };
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "EQPool.h"
|
#include "EQPool.h"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "Wavetables.h"
|
#include "Wavetables.h"
|
||||||
|
#include "Curve.h"
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
|
@ -21,6 +22,7 @@ struct Resources
|
||||||
BufferPool bufferPool;
|
BufferPool bufferPool;
|
||||||
MidiState midiState;
|
MidiState midiState;
|
||||||
Logger logger;
|
Logger logger;
|
||||||
|
CurveSet curves;
|
||||||
FilePool filePool { logger };
|
FilePool filePool { logger };
|
||||||
FilterPool filterPool { midiState };
|
FilterPool filterPool { midiState };
|
||||||
EQPool eqPool { midiState };
|
EQPool eqPool { midiState };
|
||||||
|
|
@ -38,5 +40,14 @@ struct Resources
|
||||||
bufferPool.setBufferSize(samplesPerBlock);
|
bufferPool.setBufferSize(samplesPerBlock);
|
||||||
midiState.setSamplesPerBlock(samplesPerBlock);
|
midiState.setSamplesPerBlock(samplesPerBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
curves = CurveSet::createPredefined();
|
||||||
|
filePool.clear();
|
||||||
|
wavePool.clearFileWaves();
|
||||||
|
logger.clear();
|
||||||
|
midiState.reset();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,44 +24,35 @@ using CCNamePair = std::pair<uint16_t, std::string>;
|
||||||
template <class T>
|
template <class T>
|
||||||
using MidiNoteArray = std::array<T, 128>;
|
using MidiNoteArray = std::array<T, 128>;
|
||||||
template<class ValueType>
|
template<class ValueType>
|
||||||
struct CCValuePair {
|
struct CCData {
|
||||||
int cc;
|
int cc;
|
||||||
ValueType value;
|
ValueType data;
|
||||||
|
static_assert(config::numCCs - 1 < std::numeric_limits<decltype(cc)>::max(), "The cc type in the CCData struct cannot support the required number of CCs");
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class ValueType, bool CompareValue = false>
|
struct Modifier {
|
||||||
struct CCValuePairComparator {
|
float value { 0.0f };
|
||||||
bool operator()(const CCValuePair<ValueType>& valuePair, const int& cc)
|
float step { 0.0f };
|
||||||
{
|
uint8_t curve { 0 };
|
||||||
return (valuePair.cc < cc);
|
uint8_t smooth { 0 };
|
||||||
}
|
static_assert(config::maxCurves - 1 <= std::numeric_limits<decltype(curve)>::max(), "The curve type in the Modifier struct cannot support the required number of curves");
|
||||||
|
|
||||||
bool operator()(const int& cc, const CCValuePair<ValueType>& valuePair)
|
|
||||||
{
|
|
||||||
return (cc < valuePair.cc);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator()(const CCValuePair<ValueType>& lhs, const CCValuePair<ValueType>& rhs)
|
|
||||||
{
|
|
||||||
return (lhs.cc < rhs.cc);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class ValueType>
|
template<class ValueType>
|
||||||
struct CCValuePairComparator<ValueType, true> {
|
struct CCDataComparator {
|
||||||
bool operator()(const CCValuePair<ValueType>& valuePair, const ValueType& value)
|
bool operator()(const CCData<ValueType>& ccData, const int& cc)
|
||||||
{
|
{
|
||||||
return (valuePair.value < value);
|
return (ccData.cc < cc);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(const ValueType& value, const CCValuePair<ValueType>& valuePair)
|
bool operator()(const int& cc, const CCData<ValueType>& ccData)
|
||||||
{
|
{
|
||||||
return (value < valuePair.value);
|
return (cc < ccData.cc);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(const CCValuePair<ValueType>& lhs, const CCValuePair<ValueType>& rhs)
|
bool operator()(const CCData<ValueType>& lhs, const CCData<ValueType>& rhs)
|
||||||
{
|
{
|
||||||
return (lhs.value < rhs.value);
|
return (lhs.cc < rhs.cc);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -273,200 +264,5 @@ bool findDefine(absl::string_view line, absl::string_view& variable, absl::strin
|
||||||
*/
|
*/
|
||||||
bool findInclude(absl::string_view line, std::string& path);
|
bool findInclude(absl::string_view line, std::string& path);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief multiply a value by a factor, in cents. To be used for pitch variations.
|
|
||||||
*
|
|
||||||
* @param base
|
|
||||||
* @param modifier
|
|
||||||
*/
|
|
||||||
inline CXX14_CONSTEXPR float multiplyByCentsModifier(int modifier, float base)
|
|
||||||
{
|
|
||||||
return base * centsFactor(modifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
inline CXX14_CONSTEXPR float gainModifier(T modifier, float value)
|
|
||||||
{
|
|
||||||
return value * modifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compute a crossfade in value with respect to a crossfade range (note, velocity, cc, ...)
|
|
||||||
*/
|
|
||||||
template <class T, class U>
|
|
||||||
float crossfadeIn(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
|
||||||
{
|
|
||||||
if (value < crossfadeRange.getStart())
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
const auto length = static_cast<float>(crossfadeRange.length());
|
|
||||||
if (length == 0.0f)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
else if (value < crossfadeRange.getEnd()) {
|
|
||||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
|
||||||
if (curve == SfzCrossfadeCurve::power)
|
|
||||||
return sqrt(crossfadePosition);
|
|
||||||
if (curve == SfzCrossfadeCurve::gain)
|
|
||||||
return crossfadePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compute a crossfade out value with respect to a crossfade range (note, velocity, cc, ...)
|
|
||||||
*/
|
|
||||||
template <class T, class U>
|
|
||||||
float crossfadeOut(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
|
||||||
{
|
|
||||||
if (value > crossfadeRange.getEnd())
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
const auto length = static_cast<float>(crossfadeRange.length());
|
|
||||||
if (length == 0.0f)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
else if (value > crossfadeRange.getStart()) {
|
|
||||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
|
||||||
if (curve == SfzCrossfadeCurve::power)
|
|
||||||
return std::sqrt(1 - crossfadePosition);
|
|
||||||
if (curve == SfzCrossfadeCurve::gain)
|
|
||||||
return 1 - crossfadePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class F>
|
|
||||||
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
|
||||||
{
|
|
||||||
ASSERT(events.size() > 0);
|
|
||||||
ASSERT(events[0].delay == 0);
|
|
||||||
if (envelope.size() == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
|
||||||
|
|
||||||
auto lastValue = lambda(events[0].value);
|
|
||||||
auto lastDelay = events[0].delay;
|
|
||||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
|
||||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
|
||||||
const auto step = (lambda(events[i].value) - lastValue) / length;
|
|
||||||
lastValue = linearRamp<float>(envelope.subspan(lastDelay, length), lastValue, step);
|
|
||||||
lastDelay += length;
|
|
||||||
}
|
|
||||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class F>
|
|
||||||
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
|
||||||
{
|
|
||||||
ASSERT(events.size() > 0);
|
|
||||||
ASSERT(events[0].delay == 0);
|
|
||||||
ASSERT(step != 0.0);
|
|
||||||
|
|
||||||
if (envelope.size() == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto quantize = [step](float value) -> float {
|
|
||||||
return std::round(value / step) * step;
|
|
||||||
};
|
|
||||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
|
||||||
|
|
||||||
auto lastValue = quantize(lambda(events[0].value));
|
|
||||||
auto lastDelay = events[0].delay;
|
|
||||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
|
||||||
const auto nextValue = quantize(lambda(events[i].value));
|
|
||||||
const auto difference = std::abs(nextValue - lastValue);
|
|
||||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
|
||||||
|
|
||||||
if (difference < step) {
|
|
||||||
fill<float>(envelope.subspan(lastDelay, length), lastValue);
|
|
||||||
lastValue = nextValue;
|
|
||||||
lastDelay += length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto numSteps = static_cast<int>(difference / step);
|
|
||||||
const auto stepLength = static_cast<int>(length / numSteps);
|
|
||||||
for (int i = 0; i < numSteps; ++i) {
|
|
||||||
fill<float>(envelope.subspan(lastDelay, stepLength), lastValue);
|
|
||||||
lastValue += lastValue <= nextValue ? step : -step;
|
|
||||||
lastDelay += stepLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class F>
|
|
||||||
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
|
||||||
{
|
|
||||||
ASSERT(events.size() > 0);
|
|
||||||
ASSERT(events[0].delay == 0);
|
|
||||||
|
|
||||||
if (envelope.size() == 0)
|
|
||||||
return;
|
|
||||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
|
||||||
|
|
||||||
auto lastValue = lambda(events[0].value);
|
|
||||||
auto lastDelay = events[0].delay;
|
|
||||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
|
||||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
|
||||||
const auto nextValue = lambda(events[i].value);
|
|
||||||
const auto step = std::exp((std::log(nextValue) - std::log(lastValue)) / length);
|
|
||||||
multiplicativeRamp<float>(envelope.subspan(lastDelay, length), lastValue, step);
|
|
||||||
lastValue = nextValue;
|
|
||||||
lastDelay += length;
|
|
||||||
}
|
|
||||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class F>
|
|
||||||
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
|
||||||
{
|
|
||||||
ASSERT(events.size() > 0);
|
|
||||||
ASSERT(events[0].delay == 0);
|
|
||||||
ASSERT(step != 0.0f);
|
|
||||||
|
|
||||||
if (envelope.size() == 0)
|
|
||||||
return;
|
|
||||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
|
||||||
|
|
||||||
const auto logStep = std::log(step);
|
|
||||||
// If we assume that a = b.q^r for b in (1, q) then
|
|
||||||
// log a log b
|
|
||||||
// ----- = ----- + r
|
|
||||||
// log q log q
|
|
||||||
// and log(b)\log(q) is between 0 and 1.
|
|
||||||
auto quantize = [logStep](float value) -> float {
|
|
||||||
return std::exp(logStep * std::round(std::log(value) / logStep));
|
|
||||||
};
|
|
||||||
|
|
||||||
auto lastValue = quantize(lambda(events[0].value));
|
|
||||||
auto lastDelay = events[0].delay;
|
|
||||||
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
|
||||||
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
|
||||||
const auto nextValue = quantize(lambda(events[i].value));
|
|
||||||
const auto difference = nextValue > lastValue ? nextValue / lastValue : lastValue / nextValue;
|
|
||||||
|
|
||||||
if (difference < step) {
|
|
||||||
fill<float>(envelope.subspan(lastDelay, length), lastValue);
|
|
||||||
lastValue = nextValue;
|
|
||||||
lastDelay += length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto numSteps = static_cast<int>(std::log(difference) / logStep);
|
|
||||||
const auto stepLength = static_cast<int>(length / numSteps);
|
|
||||||
for (int i = 0; i < numSteps; ++i) {
|
|
||||||
fill<float>(envelope.subspan(lastDelay, stepLength), lastValue);
|
|
||||||
lastValue = nextValue > lastValue ? lastValue * step : lastValue / step;
|
|
||||||
lastDelay += stepLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fill<float>(envelope.subspan(lastDelay), lastValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ void sfz::Synth::onParseFullBlock(const std::string& header, const std::vector<O
|
||||||
buildRegion(members);
|
buildRegion(members);
|
||||||
break;
|
break;
|
||||||
case hash("curve"):
|
case hash("curve"):
|
||||||
curves.addCurveFromHeader(members);
|
resources.curves.addCurveFromHeader(members);
|
||||||
break;
|
break;
|
||||||
case hash("effect"):
|
case hash("effect"):
|
||||||
handleEffectOpcodes(members);
|
handleEffectOpcodes(members);
|
||||||
|
|
@ -130,22 +130,18 @@ void sfz::Synth::clear()
|
||||||
list.clear();
|
list.clear();
|
||||||
for (auto& list : ccActivationLists)
|
for (auto& list : ccActivationLists)
|
||||||
list.clear();
|
list.clear();
|
||||||
|
|
||||||
regions.clear();
|
regions.clear();
|
||||||
effectBuses.clear();
|
effectBuses.clear();
|
||||||
effectBuses.emplace_back(new EffectBus);
|
effectBuses.emplace_back(new EffectBus);
|
||||||
effectBuses[0]->setGainToMain(1.0);
|
effectBuses[0]->setGainToMain(1.0);
|
||||||
effectBuses[0]->setSamplesPerBlock(samplesPerBlock);
|
effectBuses[0]->setSamplesPerBlock(samplesPerBlock);
|
||||||
effectBuses[0]->setSampleRate(sampleRate);
|
effectBuses[0]->setSampleRate(sampleRate);
|
||||||
curves = CurveSet::createPredefined();
|
resources.clear();
|
||||||
resources.filePool.clear();
|
|
||||||
resources.wavePool.clearFileWaves();
|
|
||||||
resources.logger.clear();
|
|
||||||
numGroups = 0;
|
numGroups = 0;
|
||||||
numMasters = 0;
|
numMasters = 0;
|
||||||
fileTicket = -1;
|
|
||||||
defaultSwitch = absl::nullopt;
|
defaultSwitch = absl::nullopt;
|
||||||
defaultPath = "";
|
defaultPath = "";
|
||||||
resources.midiState.reset();
|
|
||||||
ccNames.clear();
|
ccNames.clear();
|
||||||
globalOpcodes.clear();
|
globalOpcodes.clear();
|
||||||
masterOpcodes.clear();
|
masterOpcodes.clear();
|
||||||
|
|
@ -814,7 +810,7 @@ int sfz::Synth::getNumMasters() const noexcept
|
||||||
}
|
}
|
||||||
int sfz::Synth::getNumCurves() const noexcept
|
int sfz::Synth::getNumCurves() const noexcept
|
||||||
{
|
{
|
||||||
return static_cast<int>(curves.getNumCurves());
|
return static_cast<int>(resources.curves.getNumCurves());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string sfz::Synth::exportMidnam(absl::string_view model) const
|
std::string sfz::Synth::exportMidnam(absl::string_view model) const
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
#include "Voice.h"
|
#include "Voice.h"
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
#include "Effects.h"
|
#include "Effects.h"
|
||||||
#include "Curve.h"
|
|
||||||
#include "LeakDetector.h"
|
#include "LeakDetector.h"
|
||||||
#include "MidiState.h"
|
#include "MidiState.h"
|
||||||
#include "AudioSpan.h"
|
#include "AudioSpan.h"
|
||||||
|
|
@ -340,7 +339,7 @@ public:
|
||||||
*/
|
*/
|
||||||
void disableFreeWheeling() noexcept;
|
void disableFreeWheeling() noexcept;
|
||||||
|
|
||||||
const MidiState& getMidiState() const noexcept { return resources.midiState; }
|
const Resources& getResources() const noexcept { return resources; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if the SFZ should be reloaded.
|
* @brief Check if the SFZ should be reloaded.
|
||||||
|
|
@ -515,9 +514,6 @@ private:
|
||||||
typedef std::unique_ptr<EffectBus> EffectBusPtr;
|
typedef std::unique_ptr<EffectBus> EffectBusPtr;
|
||||||
std::vector<EffectBusPtr> effectBuses; // 0 is "main", 1-N are "fx1"-"fxN"
|
std::vector<EffectBusPtr> effectBuses; // 0 is "main", 1-N are "fx1"-"fxN"
|
||||||
|
|
||||||
// Curves
|
|
||||||
CurveSet curves;
|
|
||||||
|
|
||||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||||
float sampleRate { config::defaultSampleRate };
|
float sampleRate { config::defaultSampleRate };
|
||||||
float volume { Default::globalVolume };
|
float volume { Default::globalVolume };
|
||||||
|
|
@ -526,7 +522,6 @@ private:
|
||||||
|
|
||||||
// Distribution used to generate random value for the *rand opcodes
|
// Distribution used to generate random value for the *rand opcodes
|
||||||
std::uniform_real_distribution<float> randNoteDistribution { 0, 1 };
|
std::uniform_real_distribution<float> randNoteDistribution { 0, 1 };
|
||||||
unsigned fileTicket { 1 };
|
|
||||||
|
|
||||||
std::mutex callbackGuard;
|
std::mutex callbackGuard;
|
||||||
bool freeWheeling { false };
|
bool freeWheeling { false };
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,14 @@
|
||||||
// license. You should have receive a LICENSE.md file along with the code.
|
// license. You should have receive a LICENSE.md file along with the code.
|
||||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
#include "Macros.h"
|
|
||||||
#include "Voice.h"
|
#include "Voice.h"
|
||||||
#include "AudioSpan.h"
|
#include "Macros.h"
|
||||||
#include "Config.h"
|
|
||||||
#include "Defaults.h"
|
#include "Defaults.h"
|
||||||
|
#include "ModifierHelpers.h"
|
||||||
#include "MathHelpers.h"
|
#include "MathHelpers.h"
|
||||||
#include "SIMDHelpers.h"
|
#include "SIMDHelpers.h"
|
||||||
#include "SfzHelpers.h"
|
#include "SfzHelpers.h"
|
||||||
#include "absl/algorithm/container.h"
|
#include "absl/algorithm/container.h"
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
sfz::Voice::Voice(sfz::Resources& resources)
|
sfz::Voice::Voice(sfz::Resources& resources)
|
||||||
: resources(resources)
|
: resources(resources)
|
||||||
|
|
@ -264,28 +262,32 @@ void sfz::Voice::amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept
|
||||||
// Amplitude envelope
|
// Amplitude envelope
|
||||||
applyGain<float>(baseGain, modulationSpan);
|
applyGain<float>(baseGain, modulationSpan);
|
||||||
for (const auto& mod : region->amplitudeCC) {
|
for (const auto& mod : region->amplitudeCC) {
|
||||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
linearModifier(resources, *tempSpan, mod);
|
||||||
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
|
||||||
applyGain<float>(*tempSpan, modulationSpan);
|
applyGain<float>(*tempSpan, modulationSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crossfade envelopes
|
// Crossfade envelopes
|
||||||
for (const auto& mod : region->crossfadeCCInRange) {
|
for (const auto& mod : region->crossfadeCCInRange) {
|
||||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.value, x, xfCurve); });
|
linearEnvelope(events, *tempSpan, [&](float x) {
|
||||||
|
return crossfadeIn(mod.data, x, xfCurve);
|
||||||
|
});
|
||||||
applyGain<float>(*tempSpan, modulationSpan);
|
applyGain<float>(*tempSpan, modulationSpan);
|
||||||
}
|
}
|
||||||
for (const auto& mod : region->crossfadeCCOutRange) {
|
for (const auto& mod : region->crossfadeCCOutRange) {
|
||||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.value, x, xfCurve); });
|
linearEnvelope(events, *tempSpan, [&](float x) {
|
||||||
|
return crossfadeOut(mod.data, x, xfCurve);
|
||||||
|
});
|
||||||
applyGain<float>(*tempSpan, modulationSpan);
|
applyGain<float>(*tempSpan, modulationSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume envelope
|
// Volume envelope
|
||||||
applyGain<float>(db2mag(baseVolumedB), modulationSpan);
|
applyGain<float>(db2mag(baseVolumedB), modulationSpan);
|
||||||
for (const auto& mod : region->volumeCC) {
|
for (const auto& mod : region->volumeCC) {
|
||||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
multiplicativeModifier(resources, *tempSpan, mod, [](float x) {
|
||||||
multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.value); });
|
return db2mag(x);
|
||||||
|
});
|
||||||
applyGain<float>(*tempSpan, modulationSpan);
|
applyGain<float>(*tempSpan, modulationSpan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -337,8 +339,7 @@ void sfz::Voice::panStageMono(AudioSpan<float> buffer) noexcept
|
||||||
// Apply panning
|
// Apply panning
|
||||||
fill<float>(*modulationSpan, region->pan);
|
fill<float>(*modulationSpan, region->pan);
|
||||||
for (const auto& mod : region->panCC) {
|
for (const auto& mod : region->panCC) {
|
||||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
linearModifier(resources, *tempSpan, mod);
|
||||||
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
|
||||||
add<float>(*tempSpan, *modulationSpan);
|
add<float>(*tempSpan, *modulationSpan);
|
||||||
}
|
}
|
||||||
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
|
@ -357,30 +358,24 @@ void sfz::Voice::panStageStereo(AudioSpan<float> buffer) noexcept
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Apply panning
|
// Apply panning
|
||||||
// panningModulation(*modulationSpan);
|
|
||||||
fill<float>(*modulationSpan, region->pan);
|
fill<float>(*modulationSpan, region->pan);
|
||||||
for (const auto& mod : region->panCC) {
|
for (const auto& mod : region->panCC) {
|
||||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
linearModifier(resources, *tempSpan, mod);
|
||||||
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
|
||||||
add<float>(*tempSpan, *modulationSpan);
|
add<float>(*tempSpan, *modulationSpan);
|
||||||
}
|
}
|
||||||
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
|
||||||
// Apply the width/position process
|
// Apply the width/position process
|
||||||
// widthModulation(*modulationSpan);
|
|
||||||
fill<float>(*modulationSpan, region->width);
|
fill<float>(*modulationSpan, region->width);
|
||||||
for (const auto& mod : region->widthCC) {
|
for (const auto& mod : region->widthCC) {
|
||||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
linearModifier(resources, *tempSpan, mod);
|
||||||
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
|
||||||
add<float>(*tempSpan, *modulationSpan);
|
add<float>(*tempSpan, *modulationSpan);
|
||||||
}
|
}
|
||||||
width<float>(*modulationSpan, leftBuffer, rightBuffer);
|
width<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
|
||||||
// positionModulation(*modulationSpan);
|
|
||||||
fill<float>(*modulationSpan, region->position);
|
fill<float>(*modulationSpan, region->position);
|
||||||
for (const auto& mod : region->positionCC) {
|
for (const auto& mod : region->positionCC) {
|
||||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
linearModifier(resources, *tempSpan, mod);
|
||||||
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
|
||||||
add<float>(*tempSpan, *modulationSpan);
|
add<float>(*tempSpan, *modulationSpan);
|
||||||
}
|
}
|
||||||
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
|
@ -451,14 +446,13 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
|
||||||
};
|
};
|
||||||
|
|
||||||
if (region->bendStep > 1)
|
if (region->bendStep > 1)
|
||||||
multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor);
|
pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor);
|
||||||
else
|
else
|
||||||
multiplicativeEnvelope(events, *bends, bendLambda);
|
pitchBendEnvelope(events, *bends, bendLambda);
|
||||||
applyGain<float>(*bends, *jumps);
|
applyGain<float>(*bends, *jumps);
|
||||||
|
|
||||||
for (const auto& mod : region->tuneCC) {
|
for (const auto& mod : region->tuneCC) {
|
||||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
multiplicativeModifier(resources, *bends, mod, [](float x) { return centsFactor(x); });
|
||||||
multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.value); });
|
|
||||||
applyGain<float>(*bends, *jumps);
|
applyGain<float>(*bends, *jumps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -555,14 +549,15 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
|
||||||
return centsFactor(bendInCents);
|
return centsFactor(bendInCents);
|
||||||
};
|
};
|
||||||
if (region->bendStep > 1)
|
if (region->bendStep > 1)
|
||||||
multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor);
|
pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor);
|
||||||
else
|
else
|
||||||
multiplicativeEnvelope(events, *bends, bendLambda);
|
pitchBendEnvelope(events, *bends, bendLambda);
|
||||||
applyGain<float>(*bends, *frequencies);
|
applyGain<float>(*bends, *frequencies);
|
||||||
|
|
||||||
for (const auto& mod : region->tuneCC) {
|
for (const auto& mod : region->tuneCC) {
|
||||||
const auto events = resources.midiState.getCCEvents(mod.cc);
|
multiplicativeModifier(resources, *bends, mod, [](float x) {
|
||||||
multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.value); });
|
return centsFactor(x);
|
||||||
|
});
|
||||||
applyGain<float>(*bends, *frequencies);
|
applyGain<float>(*bends, *frequencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,10 @@
|
||||||
#include "HistoricalBuffer.h"
|
#include "HistoricalBuffer.h"
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
#include "MidiState.h"
|
|
||||||
#include "Wavetables.h"
|
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "AudioSpan.h"
|
#include "AudioSpan.h"
|
||||||
#include "LeakDetector.h"
|
#include "LeakDetector.h"
|
||||||
#include <absl/types/span.h>
|
#include "absl/types/span.h"
|
||||||
#include <atomic>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ TEST_CASE("[Curve] Bipolar -1 to 1")
|
||||||
REQUIRE( curve.evalCC7(85) == Approx(0.3386).margin(1e-3) );
|
REQUIRE( curve.evalCC7(85) == Approx(0.3386).margin(1e-3) );
|
||||||
REQUIRE( curve.evalNormalized(0.0f) == -1.0f );
|
REQUIRE( curve.evalNormalized(0.0f) == -1.0f );
|
||||||
REQUIRE( curve.evalNormalized(1.0f) == 1.0f );
|
REQUIRE( curve.evalNormalized(1.0f) == 1.0f );
|
||||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(-0.402).margin(1e-3) );
|
REQUIRE( curve.evalNormalized(0.3f) == Approx(-0.4).margin(1e-3) );
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Curve] Bipolar 1 to 0")
|
TEST_CASE("[Curve] Bipolar 1 to 0")
|
||||||
|
|
@ -59,7 +59,7 @@ TEST_CASE("[Curve] Bipolar 1 to -1")
|
||||||
REQUIRE( curve.evalCC7(85) == Approx(-0.3386).margin(1e-3) );
|
REQUIRE( curve.evalCC7(85) == Approx(-0.3386).margin(1e-3) );
|
||||||
REQUIRE( curve.evalNormalized(0.0f) == 1.0f );
|
REQUIRE( curve.evalNormalized(0.0f) == 1.0f );
|
||||||
REQUIRE( curve.evalNormalized(1.0f) == -1.0f );
|
REQUIRE( curve.evalNormalized(1.0f) == -1.0f );
|
||||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(0.402).margin(1e-3) );
|
REQUIRE( curve.evalNormalized(0.3f) == Approx(0.4).margin(1e-3) );
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Curve] x**2")
|
TEST_CASE("[Curve] x**2")
|
||||||
|
|
@ -199,6 +199,17 @@ TEST_CASE("[Curve] Add curves to CurveSet")
|
||||||
REQUIRE( curveSet.getCurve(4).evalCC7(0) == 1.0f );
|
REQUIRE( curveSet.getCurve(4).evalCC7(0) == 1.0f );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Curve] Add bad indices")
|
||||||
|
{
|
||||||
|
sfz::CurveSet curveSet;
|
||||||
|
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), -2);
|
||||||
|
REQUIRE( curveSet.getNumCurves() == 0 );
|
||||||
|
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), 256);
|
||||||
|
REQUIRE( curveSet.getNumCurves() == 0 );
|
||||||
|
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), 512);
|
||||||
|
REQUIRE( curveSet.getNumCurves() == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("[Curve] Default CurveSet")
|
TEST_CASE("[Curve] Default CurveSet")
|
||||||
{
|
{
|
||||||
auto curveSet = sfz::CurveSet::createPredefined();
|
auto curveSet = sfz::CurveSet::createPredefined();
|
||||||
|
|
@ -209,7 +220,7 @@ TEST_CASE("[Curve] Default CurveSet")
|
||||||
|
|
||||||
REQUIRE( curveSet.getCurve(1).evalNormalized(0.0f) == -1.0f );
|
REQUIRE( curveSet.getCurve(1).evalNormalized(0.0f) == -1.0f );
|
||||||
REQUIRE( curveSet.getCurve(1).evalNormalized(1.0f) == 1.0f );
|
REQUIRE( curveSet.getCurve(1).evalNormalized(1.0f) == 1.0f );
|
||||||
REQUIRE( curveSet.getCurve(1).evalNormalized(0.3f) == Approx(-0.402).margin(1e-3) );
|
REQUIRE( curveSet.getCurve(1).evalNormalized(0.3f) == Approx(-0.4).margin(1e-3) );
|
||||||
|
|
||||||
REQUIRE( curveSet.getCurve(2).evalNormalized(0.0f) == 1.0f );
|
REQUIRE( curveSet.getCurve(2).evalNormalized(0.0f) == 1.0f );
|
||||||
REQUIRE( curveSet.getCurve(2).evalNormalized(1.0f) == 0.0f );
|
REQUIRE( curveSet.getCurve(2).evalNormalized(1.0f) == 0.0f );
|
||||||
|
|
@ -217,7 +228,7 @@ TEST_CASE("[Curve] Default CurveSet")
|
||||||
|
|
||||||
REQUIRE( curveSet.getCurve(3).evalNormalized(0.0f) == 1.0f );
|
REQUIRE( curveSet.getCurve(3).evalNormalized(0.0f) == 1.0f );
|
||||||
REQUIRE( curveSet.getCurve(3).evalNormalized(1.0f) == -1.0f );
|
REQUIRE( curveSet.getCurve(3).evalNormalized(1.0f) == -1.0f );
|
||||||
REQUIRE( curveSet.getCurve(3).evalNormalized(0.3f) == Approx(0.402).margin(1e-3) );
|
REQUIRE( curveSet.getCurve(3).evalNormalized(0.3f) == Approx(0.4).margin(1e-3) );
|
||||||
|
|
||||||
REQUIRE( curveSet.getCurve(4).evalNormalized(0.0f) == 0.0f );
|
REQUIRE( curveSet.getCurve(4).evalNormalized(0.0f) == 0.0f );
|
||||||
REQUIRE( curveSet.getCurve(4).evalNormalized(1.0f) == 1.0f );
|
REQUIRE( curveSet.getCurve(4).evalNormalized(1.0f) == 1.0f );
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
// license. You should have receive a LICENSE.md file along with the code.
|
// license. You should have receive a LICENSE.md file along with the code.
|
||||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
#include "sfizz/SfzHelpers.h"
|
#include "sfizz/ModifierHelpers.h"
|
||||||
#include "sfizz/Buffer.h"
|
#include "sfizz/Buffer.h"
|
||||||
#include "catch2/catch.hpp"
|
#include "catch2/catch.hpp"
|
||||||
#include <absl/algorithm/container.h>
|
#include <absl/algorithm/container.h>
|
||||||
|
|
@ -42,13 +42,13 @@ TEST_CASE("[Envelopes] Empty")
|
||||||
std::array<float, 5> expected { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
|
std::array<float, 5> expected { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
|
||||||
std::array<float, 5> expectedMul { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
std::array<float, 5> expectedMul { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier);
|
multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier);
|
||||||
REQUIRE(output == expectedMul);
|
REQUIRE(approxEqual<float>(output, expectedMul));
|
||||||
multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier, 2.0f);
|
multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier, 2.0f);
|
||||||
REQUIRE(output == expectedMul);
|
REQUIRE(approxEqual<float>(output, expectedMul));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Envelopes] Linear basic")
|
TEST_CASE("[Envelopes] Linear basic")
|
||||||
|
|
@ -60,7 +60,7 @@ TEST_CASE("[Envelopes] Linear basic")
|
||||||
std::array<float, 9> output;
|
std::array<float, 9> output;
|
||||||
std::array<float, 9> expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
std::array<float, 9> expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 2 events, close")
|
TEST_CASE("[LinearEnvelope] 2 events, close")
|
||||||
|
|
@ -73,7 +73,7 @@ TEST_CASE("[LinearEnvelope] 2 events, close")
|
||||||
std::array<float, 9> output;
|
std::array<float, 9> output;
|
||||||
std::array<float, 9> expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f };
|
std::array<float, 9> expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 2 events, far")
|
TEST_CASE("[LinearEnvelope] 2 events, far")
|
||||||
|
|
@ -86,7 +86,7 @@ TEST_CASE("[LinearEnvelope] 2 events, far")
|
||||||
std::array<float, 9> output;
|
std::array<float, 9> output;
|
||||||
std::array<float, 9> expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.0f, 2.0f };
|
std::array<float, 9> expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.0f, 2.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 3 events, out of block")
|
TEST_CASE("[LinearEnvelope] 3 events, out of block")
|
||||||
|
|
@ -100,7 +100,7 @@ TEST_CASE("[LinearEnvelope] 3 events, out of block")
|
||||||
std::array<float, 9> output;
|
std::array<float, 9> output;
|
||||||
std::array<float, 9> expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f };
|
std::array<float, 9> expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 2 events, function")
|
TEST_CASE("[LinearEnvelope] 2 events, function")
|
||||||
|
|
@ -113,7 +113,7 @@ TEST_CASE("[LinearEnvelope] 2 events, function")
|
||||||
std::array<float, 9> output;
|
std::array<float, 9> output;
|
||||||
std::array<float, 9> expected { 0.0f, 1.0f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.0f, 4.0f };
|
std::array<float, 9> expected { 0.0f, 1.0f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.0f, 4.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), twiceModifier);
|
linearEnvelope(events, absl::MakeSpan(output), twiceModifier);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized")
|
TEST_CASE("[LinearEnvelope] Get quantized")
|
||||||
|
|
@ -126,7 +126,7 @@ TEST_CASE("[LinearEnvelope] Get quantized")
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f };
|
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets")
|
TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets")
|
||||||
|
|
@ -137,9 +137,9 @@ TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets")
|
||||||
{ 6, 1.9f }
|
{ 6, 1.9f }
|
||||||
};
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f };
|
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps")
|
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps")
|
||||||
|
|
@ -152,7 +152,7 @@ TEST_CASE("[LinearEnvelope] Get quantized with 2 steps")
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f };
|
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of block step")
|
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of block step")
|
||||||
|
|
@ -166,7 +166,7 @@ TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 4.0f };
|
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 4.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -180,7 +180,7 @@ TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps")
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 3.0f, 3.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f };
|
std::array<float, 8> expected { 3.0f, 3.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f };
|
||||||
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Basic event")
|
TEST_CASE("[MultiplicativeEnvelope] Basic event")
|
||||||
|
|
@ -224,52 +224,140 @@ TEST_CASE("[MultiplicativeEnvelope] 2 events, far")
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps")
|
TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps")
|
||||||
{
|
{
|
||||||
sfz::EventVector events {
|
sfz::EventVector events {
|
||||||
{ 0, 1.0f },
|
{ 0, 1.3f },
|
||||||
{ 2, 2.0f },
|
{ 2, 2.1f },
|
||||||
{ 6, 4.0f }
|
{ 6, 4.2f }
|
||||||
};
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 4.0f };
|
std::array<float, 8> expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 4.0f };
|
||||||
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Get quantized with an unquantized out of range step")
|
TEST_CASE("[MultiplicativeEnvelope] Get quantized with an unquantized out of range step")
|
||||||
{
|
{
|
||||||
sfz::EventVector events {
|
sfz::EventVector events {
|
||||||
{ 0, 1.0f },
|
{ 0, 1.3f },
|
||||||
{ 2, 2.0f },
|
{ 2, 2.1f },
|
||||||
{ 6, 4.0f },
|
{ 6, 4.1f },
|
||||||
{ 10, 8.2f }
|
{ 10, 8.2f }
|
||||||
};
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 8.0f };
|
std::array<float, 8> expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 8.0f };
|
||||||
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps")
|
TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps")
|
||||||
{
|
{
|
||||||
sfz::EventVector events {
|
sfz::EventVector events {
|
||||||
{ 0, 4.0f },
|
{ 0, 4.1f },
|
||||||
{ 2, 2.0f },
|
{ 2, 2.2f },
|
||||||
{ 6, 0.5f }
|
{ 6, 0.4f }
|
||||||
};
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 4.0f, 4.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f };
|
std::array<float, 8> expected { 4.0f, 4.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f };
|
||||||
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events")
|
TEST_CASE("[linearModifiers] Compare with envelopes")
|
||||||
{
|
{
|
||||||
sfz::EventVector events {
|
sfz::Resources resources;
|
||||||
{ 0, 1.0f },
|
resources.curves = sfz::CurveSet::createPredefined();
|
||||||
{ 2, 1.2f },
|
|
||||||
{ 6, 2.5f }
|
sfz::CCData<sfz::Modifier> ccData;
|
||||||
};
|
ccData.cc = 20;
|
||||||
std::array<float, 8> output;
|
ccData.data.value = 100.0f;
|
||||||
std::array<float, 8> expected { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f };
|
|
||||||
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
resources.midiState.ccEvent(5, 20, 0.1);
|
||||||
REQUIRE(output == expected);
|
resources.midiState.ccEvent(10, 20, 0.8);
|
||||||
|
|
||||||
|
std::array<float, 16> output;
|
||||||
|
std::array<float, 16> envelope;
|
||||||
|
|
||||||
|
linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||||
|
return ccData.data.value * x;
|
||||||
|
});
|
||||||
|
linearModifier(resources, absl::MakeSpan(output), ccData);
|
||||||
|
REQUIRE(approxEqual<float>(output, envelope));
|
||||||
|
|
||||||
|
ccData.data.curve = 1;
|
||||||
|
linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||||
|
return ccData.data.value * (2 * x - 1);
|
||||||
|
});
|
||||||
|
linearModifier(resources, absl::MakeSpan(output), ccData);
|
||||||
|
REQUIRE(approxEqual<float>(output, envelope));
|
||||||
|
|
||||||
|
ccData.data.curve = 3;
|
||||||
|
ccData.data.value = 10.0f;
|
||||||
|
linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||||
|
return ccData.data.value * (1 - 2 * x);
|
||||||
|
});
|
||||||
|
linearModifier(resources, absl::MakeSpan(output), ccData);
|
||||||
|
REQUIRE(approxEqual<float>(output, envelope));
|
||||||
|
|
||||||
|
ccData.data.curve = 2;
|
||||||
|
ccData.data.value = 20.0f;
|
||||||
|
ccData.data.step = 2.5f;
|
||||||
|
linearEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||||
|
return ccData.data.value * (1 - x);
|
||||||
|
}, ccData.data.step);
|
||||||
|
linearModifier(resources, absl::MakeSpan(output), ccData);
|
||||||
|
REQUIRE(approxEqual<float>(output, envelope));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[multiplicativeModifiers] Compare with envelopes")
|
||||||
|
{
|
||||||
|
sfz::Resources resources;
|
||||||
|
resources.curves = sfz::CurveSet::createPredefined();
|
||||||
|
|
||||||
|
sfz::CCData<sfz::Modifier> ccData;
|
||||||
|
ccData.cc = 20;
|
||||||
|
ccData.data.value = 100.0f;
|
||||||
|
|
||||||
|
resources.midiState.ccEvent(5, 20, 0.1);
|
||||||
|
resources.midiState.ccEvent(15, 20, 0.8);
|
||||||
|
|
||||||
|
std::array<float, 16> output;
|
||||||
|
std::array<float, 16> envelope;
|
||||||
|
|
||||||
|
multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||||
|
return db2mag(ccData.data.value * x);
|
||||||
|
});
|
||||||
|
multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) {
|
||||||
|
return db2mag(x);
|
||||||
|
});
|
||||||
|
REQUIRE(approxEqual<float>(output, envelope));
|
||||||
|
|
||||||
|
ccData.data.curve = 1;
|
||||||
|
multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||||
|
return db2mag(ccData.data.value * (2 * x - 1));
|
||||||
|
});
|
||||||
|
multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) {
|
||||||
|
return db2mag(x);
|
||||||
|
});
|
||||||
|
REQUIRE(approxEqual<float>(output, envelope));
|
||||||
|
|
||||||
|
ccData.data.curve = 3;
|
||||||
|
ccData.data.value = 10.0f;
|
||||||
|
multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||||
|
return db2mag(ccData.data.value * (1 - 2 * x));
|
||||||
|
});
|
||||||
|
multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) {
|
||||||
|
return db2mag(x);
|
||||||
|
});
|
||||||
|
REQUIRE(approxEqual<float>(output, envelope));
|
||||||
|
|
||||||
|
ccData.data.curve = 2;
|
||||||
|
ccData.data.value = 20.0f;
|
||||||
|
ccData.data.step = 2.5f;
|
||||||
|
multiplicativeEnvelope(resources.midiState.getCCEvents(20), absl::MakeSpan(envelope), [&ccData](float x) {
|
||||||
|
return db2mag(ccData.data.value * (1 - x));
|
||||||
|
}, db2mag(ccData.data.step) );
|
||||||
|
multiplicativeModifier(resources, absl::MakeSpan(output), ccData, [](float x) {
|
||||||
|
return db2mag(x);
|
||||||
|
});
|
||||||
|
REQUIRE(approxEqual<float>(output, envelope));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -351,7 +351,7 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines")
|
||||||
REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 );
|
REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 );
|
||||||
REQUIRE(!synth.getRegionView(2)->amplitudeCC.empty());
|
REQUIRE(!synth.getRegionView(2)->amplitudeCC.empty());
|
||||||
REQUIRE(synth.getRegionView(2)->amplitudeCC.contains(10));
|
REQUIRE(synth.getRegionView(2)->amplitudeCC.contains(10));
|
||||||
REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10) == 0.34f);
|
REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10).value == 0.34f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Specific bug: relative path with backslashes")
|
TEST_CASE("[Files] Specific bug: relative path with backslashes")
|
||||||
|
|
@ -394,9 +394,10 @@ TEST_CASE("[Files] Default path is ignored for generators")
|
||||||
TEST_CASE("[Files] Set CC applies properly")
|
TEST_CASE("[Files] Set CC applies properly")
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
|
const auto& midiState = synth.getResources().midiState;
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/set_cc.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/set_cc.sfz");
|
||||||
REQUIRE(synth.getMidiState().getCCValue(142) == 63_norm);
|
REQUIRE(midiState.getCCValue(142) == 63_norm);
|
||||||
REQUIRE(synth.getMidiState().getCCValue(61) == 122_norm);
|
REQUIRE(midiState.getCCValue(61) == 122_norm);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Note and octave offsets")
|
TEST_CASE("[Files] Note and octave offsets")
|
||||||
|
|
|
||||||
|
|
@ -470,7 +470,25 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
REQUIRE(region.panCC.empty());
|
REQUIRE(region.panCC.empty());
|
||||||
region.parseOpcode({ "pan_oncc45", "4.2" });
|
region.parseOpcode({ "pan_oncc45", "4.2" });
|
||||||
REQUIRE(region.panCC.contains(45));
|
REQUIRE(region.panCC.contains(45));
|
||||||
REQUIRE(region.panCC[45] == 0.042_a);
|
REQUIRE(region.panCC[45].value == 0.042_a);
|
||||||
|
region.parseOpcode({ "pan_curvecc17", "18" });
|
||||||
|
REQUIRE(region.panCC[17].curve == 18);
|
||||||
|
region.parseOpcode({ "pan_curvecc17", "15482" });
|
||||||
|
REQUIRE(region.panCC[17].curve == 255);
|
||||||
|
region.parseOpcode({ "pan_curvecc17", "-2" });
|
||||||
|
REQUIRE(region.panCC[17].curve == 0);
|
||||||
|
region.parseOpcode({ "pan_smoothcc14", "85" });
|
||||||
|
REQUIRE(region.panCC[14].smooth == 85);
|
||||||
|
region.parseOpcode({ "pan_smoothcc14", "15482" });
|
||||||
|
REQUIRE(region.panCC[14].smooth == 127);
|
||||||
|
region.parseOpcode({ "pan_smoothcc14", "-2" });
|
||||||
|
REQUIRE(region.panCC[14].smooth == 0);
|
||||||
|
region.parseOpcode({ "pan_stepcc120", "24" });
|
||||||
|
REQUIRE(region.panCC[120].step == 0.24_a);
|
||||||
|
region.parseOpcode({ "pan_stepcc120", "15482" });
|
||||||
|
REQUIRE(region.panCC[120].step == 2.0_a);
|
||||||
|
region.parseOpcode({ "pan_stepcc120", "-2" });
|
||||||
|
REQUIRE(region.panCC[120].step == 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("width")
|
SECTION("width")
|
||||||
|
|
@ -491,7 +509,25 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
REQUIRE(region.widthCC.empty());
|
REQUIRE(region.widthCC.empty());
|
||||||
region.parseOpcode({ "width_oncc45", "4.2" });
|
region.parseOpcode({ "width_oncc45", "4.2" });
|
||||||
REQUIRE(region.widthCC.contains(45));
|
REQUIRE(region.widthCC.contains(45));
|
||||||
REQUIRE(region.widthCC[45] == 0.042_a);
|
REQUIRE(region.widthCC[45].value == 0.042_a);
|
||||||
|
region.parseOpcode({ "width_curvecc17", "18" });
|
||||||
|
REQUIRE(region.widthCC[17].curve == 18);
|
||||||
|
region.parseOpcode({ "width_curvecc17", "15482" });
|
||||||
|
REQUIRE(region.widthCC[17].curve == 255);
|
||||||
|
region.parseOpcode({ "width_curvecc17", "-2" });
|
||||||
|
REQUIRE(region.widthCC[17].curve == 0);
|
||||||
|
region.parseOpcode({ "width_smoothcc14", "85" });
|
||||||
|
REQUIRE(region.widthCC[14].smooth == 85);
|
||||||
|
region.parseOpcode({ "width_smoothcc14", "15482" });
|
||||||
|
REQUIRE(region.widthCC[14].smooth == 127);
|
||||||
|
region.parseOpcode({ "width_smoothcc14", "-2" });
|
||||||
|
REQUIRE(region.widthCC[14].smooth == 0);
|
||||||
|
region.parseOpcode({ "width_stepcc120", "24" });
|
||||||
|
REQUIRE(region.widthCC[120].step == 0.24_a);
|
||||||
|
region.parseOpcode({ "width_stepcc120", "15482" });
|
||||||
|
REQUIRE(region.widthCC[120].step == 2.0_a);
|
||||||
|
region.parseOpcode({ "width_stepcc120", "-20" });
|
||||||
|
REQUIRE(region.widthCC[120].step == 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("position")
|
SECTION("position")
|
||||||
|
|
@ -512,7 +548,25 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
REQUIRE(region.positionCC.empty());
|
REQUIRE(region.positionCC.empty());
|
||||||
region.parseOpcode({ "position_oncc45", "4.2" });
|
region.parseOpcode({ "position_oncc45", "4.2" });
|
||||||
REQUIRE(region.positionCC.contains(45));
|
REQUIRE(region.positionCC.contains(45));
|
||||||
REQUIRE(region.positionCC[45] == 0.042_a);
|
REQUIRE(region.positionCC[45].value == 0.042_a);
|
||||||
|
region.parseOpcode({ "position_curvecc17", "18" });
|
||||||
|
REQUIRE(region.positionCC[17].curve == 18);
|
||||||
|
region.parseOpcode({ "position_curvecc17", "15482" });
|
||||||
|
REQUIRE(region.positionCC[17].curve == 255);
|
||||||
|
region.parseOpcode({ "position_curvecc17", "-2" });
|
||||||
|
REQUIRE(region.positionCC[17].curve == 0);
|
||||||
|
region.parseOpcode({ "position_smoothcc14", "85" });
|
||||||
|
REQUIRE(region.positionCC[14].smooth == 85);
|
||||||
|
region.parseOpcode({ "position_smoothcc14", "15482" });
|
||||||
|
REQUIRE(region.positionCC[14].smooth == 127);
|
||||||
|
region.parseOpcode({ "position_smoothcc14", "-2" });
|
||||||
|
REQUIRE(region.positionCC[14].smooth == 0);
|
||||||
|
region.parseOpcode({ "position_stepcc120", "24" });
|
||||||
|
REQUIRE(region.positionCC[120].step == 0.24_a);
|
||||||
|
region.parseOpcode({ "position_stepcc120", "15482" });
|
||||||
|
REQUIRE(region.positionCC[120].step == 2.0_a);
|
||||||
|
region.parseOpcode({ "position_stepcc120", "-2" });
|
||||||
|
REQUIRE(region.positionCC[120].step == 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("amp_keycenter")
|
SECTION("amp_keycenter")
|
||||||
|
|
@ -974,13 +1028,13 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
REQUIRE(region.amplitudeEG.ccRelease->cc == 5);
|
REQUIRE(region.amplitudeEG.ccRelease->cc == 5);
|
||||||
REQUIRE(region.amplitudeEG.ccStart->cc == 6);
|
REQUIRE(region.amplitudeEG.ccStart->cc == 6);
|
||||||
REQUIRE(region.amplitudeEG.ccSustain->cc == 7);
|
REQUIRE(region.amplitudeEG.ccSustain->cc == 7);
|
||||||
REQUIRE(region.amplitudeEG.ccAttack->value == 1.0f);
|
REQUIRE(region.amplitudeEG.ccAttack->data == 1.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccDecay->value == 2.0f);
|
REQUIRE(region.amplitudeEG.ccDecay->data == 2.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccDelay->value == 3.0f);
|
REQUIRE(region.amplitudeEG.ccDelay->data == 3.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccHold->value == 4.0f);
|
REQUIRE(region.amplitudeEG.ccHold->data == 4.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccRelease->value == 5.0f);
|
REQUIRE(region.amplitudeEG.ccRelease->data == 5.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccStart->value == 6.0f);
|
REQUIRE(region.amplitudeEG.ccStart->data == 6.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccSustain->value == 7.0f);
|
REQUIRE(region.amplitudeEG.ccSustain->data == 7.0f);
|
||||||
//
|
//
|
||||||
region.parseOpcode({ "ampeg_attack_oncc1", "101" });
|
region.parseOpcode({ "ampeg_attack_oncc1", "101" });
|
||||||
region.parseOpcode({ "ampeg_decay_oncc2", "101" });
|
region.parseOpcode({ "ampeg_decay_oncc2", "101" });
|
||||||
|
|
@ -989,13 +1043,13 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
region.parseOpcode({ "ampeg_release_oncc5", "101" });
|
region.parseOpcode({ "ampeg_release_oncc5", "101" });
|
||||||
region.parseOpcode({ "ampeg_start_oncc6", "101" });
|
region.parseOpcode({ "ampeg_start_oncc6", "101" });
|
||||||
region.parseOpcode({ "ampeg_sustain_oncc7", "101" });
|
region.parseOpcode({ "ampeg_sustain_oncc7", "101" });
|
||||||
REQUIRE(region.amplitudeEG.ccAttack->value == 100.0f);
|
REQUIRE(region.amplitudeEG.ccAttack->data == 100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccDecay->value == 100.0f);
|
REQUIRE(region.amplitudeEG.ccDecay->data == 100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccDelay->value == 100.0f);
|
REQUIRE(region.amplitudeEG.ccDelay->data == 100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccHold->value == 100.0f);
|
REQUIRE(region.amplitudeEG.ccHold->data == 100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccRelease->value == 100.0f);
|
REQUIRE(region.amplitudeEG.ccRelease->data == 100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccStart->value == 100.0f);
|
REQUIRE(region.amplitudeEG.ccStart->data == 100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccSustain->value == 100.0f);
|
REQUIRE(region.amplitudeEG.ccSustain->data == 100.0f);
|
||||||
//
|
//
|
||||||
region.parseOpcode({ "ampeg_attack_oncc1", "-101" });
|
region.parseOpcode({ "ampeg_attack_oncc1", "-101" });
|
||||||
region.parseOpcode({ "ampeg_decay_oncc2", "-101" });
|
region.parseOpcode({ "ampeg_decay_oncc2", "-101" });
|
||||||
|
|
@ -1004,13 +1058,13 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
region.parseOpcode({ "ampeg_release_oncc5", "-101" });
|
region.parseOpcode({ "ampeg_release_oncc5", "-101" });
|
||||||
region.parseOpcode({ "ampeg_start_oncc6", "-101" });
|
region.parseOpcode({ "ampeg_start_oncc6", "-101" });
|
||||||
region.parseOpcode({ "ampeg_sustain_oncc7", "-101" });
|
region.parseOpcode({ "ampeg_sustain_oncc7", "-101" });
|
||||||
REQUIRE(region.amplitudeEG.ccAttack->value == -100.0f);
|
REQUIRE(region.amplitudeEG.ccAttack->data == -100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccDecay->value == -100.0f);
|
REQUIRE(region.amplitudeEG.ccDecay->data == -100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccDelay->value == -100.0f);
|
REQUIRE(region.amplitudeEG.ccDelay->data == -100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccHold->value == -100.0f);
|
REQUIRE(region.amplitudeEG.ccHold->data == -100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccRelease->value == -100.0f);
|
REQUIRE(region.amplitudeEG.ccRelease->data == -100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccStart->value == -100.0f);
|
REQUIRE(region.amplitudeEG.ccStart->data == -100.0f);
|
||||||
REQUIRE(region.amplitudeEG.ccSustain->value == -100.0f);
|
REQUIRE(region.amplitudeEG.ccSustain->data == -100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("sustain_sw and sostenuto_sw")
|
SECTION("sustain_sw and sostenuto_sw")
|
||||||
|
|
@ -1448,10 +1502,28 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
REQUIRE(region.amplitudeCC.empty());
|
REQUIRE(region.amplitudeCC.empty());
|
||||||
region.parseOpcode({ "amplitude_cc1", "40" });
|
region.parseOpcode({ "amplitude_cc1", "40" });
|
||||||
REQUIRE(region.amplitudeCC.contains(1));
|
REQUIRE(region.amplitudeCC.contains(1));
|
||||||
REQUIRE(region.amplitudeCC[1] == 0.40_a);
|
REQUIRE(region.amplitudeCC[1].value == 0.40_a);
|
||||||
region.parseOpcode({ "amplitude_oncc2", "30" });
|
region.parseOpcode({ "amplitude_oncc2", "30" });
|
||||||
REQUIRE(region.amplitudeCC.contains(2));
|
REQUIRE(region.amplitudeCC.contains(2));
|
||||||
REQUIRE(region.amplitudeCC[2] == 0.30_a);
|
REQUIRE(region.amplitudeCC[2].value == 0.30_a);
|
||||||
|
region.parseOpcode({ "amplitude_curvecc17", "18" });
|
||||||
|
REQUIRE(region.amplitudeCC[17].curve == 18);
|
||||||
|
region.parseOpcode({ "amplitude_curvecc17", "15482" });
|
||||||
|
REQUIRE(region.amplitudeCC[17].curve == 255);
|
||||||
|
region.parseOpcode({ "amplitude_curvecc17", "-2" });
|
||||||
|
REQUIRE(region.amplitudeCC[17].curve == 0);
|
||||||
|
region.parseOpcode({ "amplitude_smoothcc14", "85" });
|
||||||
|
REQUIRE(region.amplitudeCC[14].smooth == 85);
|
||||||
|
region.parseOpcode({ "amplitude_smoothcc14", "15482" });
|
||||||
|
REQUIRE(region.amplitudeCC[14].smooth == 127);
|
||||||
|
region.parseOpcode({ "amplitude_smoothcc14", "-2" });
|
||||||
|
REQUIRE(region.amplitudeCC[14].smooth == 0);
|
||||||
|
region.parseOpcode({ "amplitude_stepcc120", "24" });
|
||||||
|
REQUIRE(region.amplitudeCC[120].step == 0.24_a);
|
||||||
|
region.parseOpcode({ "amplitude_stepcc120", "15482" });
|
||||||
|
REQUIRE(region.amplitudeCC[120].step == 1.0_a);
|
||||||
|
region.parseOpcode({ "amplitude_stepcc120", "-2" });
|
||||||
|
REQUIRE(region.amplitudeCC[120].step == 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("volume_oncc/gain_cc")
|
SECTION("volume_oncc/gain_cc")
|
||||||
|
|
@ -1459,13 +1531,31 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
REQUIRE(region.volumeCC.empty());
|
REQUIRE(region.volumeCC.empty());
|
||||||
region.parseOpcode({ "gain_cc1", "40" });
|
region.parseOpcode({ "gain_cc1", "40" });
|
||||||
REQUIRE(region.volumeCC.contains(1));
|
REQUIRE(region.volumeCC.contains(1));
|
||||||
REQUIRE(region.volumeCC[1] == 40_a);
|
REQUIRE(region.volumeCC[1].value == 40_a);
|
||||||
region.parseOpcode({ "volume_oncc2", "-76" });
|
region.parseOpcode({ "volume_oncc2", "-76" });
|
||||||
REQUIRE(region.volumeCC.contains(2));
|
REQUIRE(region.volumeCC.contains(2));
|
||||||
REQUIRE(region.volumeCC[2] == -76.0_a);
|
REQUIRE(region.volumeCC[2].value == -76.0_a);
|
||||||
region.parseOpcode({ "gain_oncc4", "-1" });
|
region.parseOpcode({ "gain_oncc4", "-1" });
|
||||||
REQUIRE(region.volumeCC.contains(4));
|
REQUIRE(region.volumeCC.contains(4));
|
||||||
REQUIRE(region.volumeCC[4] == -1.0_a);
|
REQUIRE(region.volumeCC[4].value == -1.0_a);
|
||||||
|
region.parseOpcode({ "volume_curvecc17", "18" });
|
||||||
|
REQUIRE(region.volumeCC[17].curve == 18);
|
||||||
|
region.parseOpcode({ "volume_curvecc17", "15482" });
|
||||||
|
REQUIRE(region.volumeCC[17].curve == 255);
|
||||||
|
region.parseOpcode({ "volume_curvecc17", "-2" });
|
||||||
|
REQUIRE(region.volumeCC[17].curve == 0);
|
||||||
|
region.parseOpcode({ "volume_smoothcc14", "85" });
|
||||||
|
REQUIRE(region.volumeCC[14].smooth == 85);
|
||||||
|
region.parseOpcode({ "volume_smoothcc14", "15482" });
|
||||||
|
REQUIRE(region.volumeCC[14].smooth == 127);
|
||||||
|
region.parseOpcode({ "volume_smoothcc14", "-2" });
|
||||||
|
REQUIRE(region.volumeCC[14].smooth == 0);
|
||||||
|
region.parseOpcode({ "volume_stepcc120", "24" });
|
||||||
|
REQUIRE(region.volumeCC[120].step == 24.0f);
|
||||||
|
region.parseOpcode({ "volume_stepcc120", "15482" });
|
||||||
|
REQUIRE(region.volumeCC[120].step == 48.0f);
|
||||||
|
region.parseOpcode({ "volume_stepcc120", "-2" });
|
||||||
|
REQUIRE(region.volumeCC[120].step == 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("tune_cc/pitch_cc")
|
SECTION("tune_cc/pitch_cc")
|
||||||
|
|
@ -1473,13 +1563,31 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
REQUIRE(region.tuneCC.empty());
|
REQUIRE(region.tuneCC.empty());
|
||||||
region.parseOpcode({ "pitch_cc1", "40" });
|
region.parseOpcode({ "pitch_cc1", "40" });
|
||||||
REQUIRE(region.tuneCC.contains(1));
|
REQUIRE(region.tuneCC.contains(1));
|
||||||
REQUIRE(region.tuneCC[1] == 40);
|
REQUIRE(region.tuneCC[1].value == 40.0);
|
||||||
region.parseOpcode({ "tune_oncc2", "-76" });
|
region.parseOpcode({ "tune_oncc2", "-76" });
|
||||||
REQUIRE(region.tuneCC.contains(2));
|
REQUIRE(region.tuneCC.contains(2));
|
||||||
REQUIRE(region.tuneCC[2] == -76.0);
|
REQUIRE(region.tuneCC[2].value == -76.0);
|
||||||
region.parseOpcode({ "pitch_oncc4", "-1" });
|
region.parseOpcode({ "pitch_oncc4", "-1" });
|
||||||
REQUIRE(region.tuneCC.contains(4));
|
REQUIRE(region.tuneCC.contains(4));
|
||||||
REQUIRE(region.tuneCC[4] == -1.0);
|
REQUIRE(region.tuneCC[4].value == -1.0);
|
||||||
|
region.parseOpcode({ "tune_curvecc17", "18" });
|
||||||
|
REQUIRE(region.tuneCC[17].curve == 18);
|
||||||
|
region.parseOpcode({ "pitch_curvecc17", "15482" });
|
||||||
|
REQUIRE(region.tuneCC[17].curve == 255);
|
||||||
|
region.parseOpcode({ "tune_curvecc17", "-2" });
|
||||||
|
REQUIRE(region.tuneCC[17].curve == 0);
|
||||||
|
region.parseOpcode({ "pitch_smoothcc14", "85" });
|
||||||
|
REQUIRE(region.tuneCC[14].smooth == 85);
|
||||||
|
region.parseOpcode({ "tune_smoothcc14", "15482" });
|
||||||
|
REQUIRE(region.tuneCC[14].smooth == 127);
|
||||||
|
region.parseOpcode({ "pitch_smoothcc14", "-2" });
|
||||||
|
REQUIRE(region.tuneCC[14].smooth == 0);
|
||||||
|
region.parseOpcode({ "tune_stepcc120", "24" });
|
||||||
|
REQUIRE(region.tuneCC[120].step == 24.0f);
|
||||||
|
region.parseOpcode({ "pitch_stepcc120", "15482" });
|
||||||
|
REQUIRE(region.tuneCC[120].step == 9600.0f);
|
||||||
|
region.parseOpcode({ "tune_stepcc120", "-2" });
|
||||||
|
REQUIRE(region.tuneCC[120].step == 0.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,10 +140,11 @@ TEST_CASE("[Synth] All notes offs/all sounds off")
|
||||||
TEST_CASE("[Synth] Reset all controllers")
|
TEST_CASE("[Synth] Reset all controllers")
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
|
const auto& midiState = synth.getResources().midiState;
|
||||||
synth.cc(0, 12, 64);
|
synth.cc(0, 12, 64);
|
||||||
REQUIRE(synth.getMidiState().getCCValue(12) == 64_norm);
|
REQUIRE(midiState.getCCValue(12) == 64_norm);
|
||||||
synth.cc(0, 121, 64);
|
synth.cc(0, 121, 64);
|
||||||
REQUIRE(synth.getMidiState().getCCValue(12) == 0_norm);
|
REQUIRE(midiState.getCCValue(12) == 0_norm);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kills the voice")
|
TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kills the voice")
|
||||||
|
|
@ -419,3 +420,17 @@ TEST_CASE("[Synth] Polyphony in master")
|
||||||
synth.noteOn(0, 61, 64);
|
synth.noteOn(0, 61, 64);
|
||||||
REQUIRE(synth.getNumActiveVoices() == 3);
|
REQUIRE(synth.getNumActiveVoices() == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Basic curves")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
const auto& curves = synth.getResources().curves;
|
||||||
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/curves.sfz");
|
||||||
|
REQUIRE( synth.getNumCurves() == 19 );
|
||||||
|
REQUIRE( curves.getCurve(18).evalCC7(127) == 1.0f );
|
||||||
|
REQUIRE( curves.getCurve(18).evalCC7(95) == 0.5f );
|
||||||
|
REQUIRE( curves.getCurve(17).evalCC7(100) == 1.0f );
|
||||||
|
REQUIRE( curves.getCurve(17).evalCC7(95) == 0.5f );
|
||||||
|
// Default linear
|
||||||
|
REQUIRE( curves.getCurve(16).evalCC7(63) == Approx(63_norm) );
|
||||||
|
}
|
||||||
|
|
|
||||||
10
tests/TestFiles/curves.sfz
Normal file
10
tests/TestFiles/curves.sfz
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<region> sample=*sine
|
||||||
|
<curve>curve_index=18
|
||||||
|
v000=0
|
||||||
|
v095=0.5
|
||||||
|
v127=1
|
||||||
|
|
||||||
|
<curve>curve_index=17
|
||||||
|
v000=0
|
||||||
|
v095=0.5
|
||||||
|
v100=1
|
||||||
Loading…
Add table
Reference in a new issue