Merge pull request #181 from paulfd/smoothers

Smoothers and `smoothcc`
This commit is contained in:
JP Cimalando 2020-06-20 18:48:07 +02:00 committed by GitHub
commit 02630ed5eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1397 additions and 606 deletions

View file

@ -42,7 +42,16 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, Linear)(benchmark::State& state) {
{ 0, 0.0f },
{ static_cast<int>(state.range(0) - 1), dist(gen) }
};
linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; });
sfz::linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; });
}
}
BENCHMARK_DEFINE_F(EnvelopeFixture, LinearNoEvent) (benchmark::State& state) {
for (auto _ : state) {
sfz::EventVector events {
{ 0, dist(gen) }
};
sfz::linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; });
}
}
@ -53,7 +62,7 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, LinearQuantized)(benchmark::State& state) {
{ 0, 0.0f },
{ static_cast<int>(state.range(0) - 1), dist(gen) }
};
linearEnvelope(
sfz::linearEnvelope(
events, absl::MakeSpan(output), [](float x) { return x; }, 0.5);
}
}
@ -65,7 +74,16 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, Multiplicative)(benchmark::State& state) {
{ 0, 1.0f },
{ static_cast<int>(state.range(0) - 1), dist(gen) }
};
multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; });
sfz::multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; });
}
}
BENCHMARK_DEFINE_F(EnvelopeFixture, MultiplicativeNoEvent) (benchmark::State& state) {
for (auto _ : state) {
sfz::EventVector events {
{ 0, dist(gen) }
};
sfz::multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; });
}
}
@ -76,14 +94,16 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, MultiplicativeQuantized)(benchmark::State& s
{ 0, 1.0f },
{ static_cast<int>(state.range(0) - 1), dist(gen) }
};
multiplicativeEnvelope(
sfz::multiplicativeEnvelope(
events, absl::MakeSpan(output), [](float x) { return x; }, 2.0f);
}
}
BENCHMARK_REGISTER_F(EnvelopeFixture, Linear)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(EnvelopeFixture, LinearNoEvent)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(EnvelopeFixture, LinearQuantized)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(EnvelopeFixture, Multiplicative)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(EnvelopeFixture, MultiplicativeNoEvent)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(EnvelopeFixture, MultiplicativeQuantized)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_MAIN();

View file

@ -7,6 +7,7 @@
#include "SIMDHelpers.h"
#include "OnePoleFilter.h"
#include "SfzFilter.h"
#include "SfzHelpers.h"
#include "ScopedFTZ.h"
#include "SfzHelpers.h"
#include <benchmark/benchmark.h>

View file

@ -0,0 +1,57 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// 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
#include "SIMDHelpers.h"
#include <benchmark/benchmark.h>
#include <random>
#include <numeric>
#include <vector>
#include <cmath>
#include <iostream>
#include "ModifierHelpers.h"
#include "Smoothers.h"
#include "absl/types/span.h"
class SmootherFixture : public benchmark::Fixture {
public:
void SetUp(const ::benchmark::State& state)
{
input = std::vector<float>(state.range(0));
output = std::vector<float>(state.range(0));
std::generate(input.begin(), input.end(), [&]() { return dist(gen); });
sfz::cumsum<float, false>(input, absl::MakeSpan(input));
}
void TearDown(const ::benchmark::State& /* state */)
{
}
std::random_device rd {};
std::mt19937 gen { rd() };
std::uniform_real_distribution<float> dist { 0.5, 4 };
std::vector<float> input;
std::vector<float> output;
};
BENCHMARK_DEFINE_F(SmootherFixture, Linear) (benchmark::State& state)
{
sfz::Smoother smoother;
smoother.setSmoothing(10, sfz::config::defaultSampleRate);
for (auto _ : state) {
smoother.process(input, absl::MakeSpan(output));
}
}
// BENCHMARK_DEFINE_F(SmootherFixture, Multiplicative)(benchmark::State& state) {
// sfz::MultiplicativeSmoother smoother;
// smoother.setSmoothing(10, sfz::config::defaultSampleRate);
// for (auto _ : state)
// {
// smoother.process(input, absl::MakeSpan(output));
// }
// }
BENCHMARK_REGISTER_F(SmootherFixture, Linear)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
// BENCHMARK_REGISTER_F(SmootherFixture, Multiplicative)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_MAIN();

View file

@ -0,0 +1,59 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// 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
#include "SIMDHelpers.h"
#include "OnePoleFilter.h"
#include "SfzFilter.h"
#include "ScopedFTZ.h"
#include <benchmark/benchmark.h>
#include <random>
#include <numeric>
#include <vector>
#include <cmath>
#include <iostream>
constexpr int blockSize { 256 };
constexpr float sampleRate { 48000.0f };
class FilterFixture : public benchmark::Fixture {
public:
void SetUp(const ::benchmark::State&)
{
input = std::vector<float>(blockSize);
output = std::vector<float>(blockSize);
std::generate(input.begin(), input.end(), [&]() { return dist(gen); });
}
void TearDown(const ::benchmark::State& /* state */)
{
}
std::random_device rd {};
std::mt19937 gen { rd() };
std::normal_distribution<float> dist { 1, 0.2 };
std::vector<float> input;
std::vector<float> output;
};
BENCHMARK_DEFINE_F(FilterFixture, OnePole_VA) (benchmark::State& state)
{
ScopedFTZ ftz;
sfz::OnePoleFilter<float> filter;
for (auto _ : state) {
filter.processLowpass(input, absl::MakeSpan(output));
}
}
BENCHMARK_DEFINE_F(FilterFixture, OnePoleMul_VA) (benchmark::State& state)
{
ScopedFTZ ftz;
sfz::OnePoleFilterMul<float> filter;
for (auto _ : state) {
filter.processLowpass(input, absl::MakeSpan(output));
}
}
BENCHMARK_REGISTER_F(FilterFixture, OnePole_VA);
BENCHMARK_REGISTER_F(FilterFixture, OnePoleMul_VA);
BENCHMARK_MAIN();

View file

@ -62,6 +62,8 @@ sfizz_add_benchmark(bm_random BM_random.cpp)
sfizz_add_benchmark(bm_logger BM_logger.cpp)
target_link_libraries(bm_logger PRIVATE sfizz::sfizz)
sfizz_add_benchmark(bm_smoothers BM_smoothers.cpp)
target_link_libraries(bm_smoothers PRIVATE sfizz::sfizz)
if (TARGET sfizz-samplerate)
sfizz_add_benchmark(bm_resample BM_resample.cpp ${BENCHMARK_SIMD_SOURCES})

View file

@ -20,6 +20,7 @@ set (SFIZZ_SOURCES
sfizz/Logger.cpp
sfizz/SfzFilter.cpp
sfizz/Curve.cpp
sfizz/Smoothers.cpp
sfizz/Wavetables.cpp
sfizz/Tuning.cpp
sfizz/RTSemaphore.cpp

View file

@ -22,7 +22,10 @@ namespace sfz {
template <class ValueType>
class CCMap {
public:
CCMap() = delete;
CCMap()
: defaultValue(ValueType {})
{
}
/**
* @brief Construct a new CCMap object with the specified default value.
*
@ -44,7 +47,7 @@ public:
*/
const ValueType& getWithDefault(int index) const noexcept
{
auto it = absl::c_lower_bound(container, index, CCDataComparator<ValueType>{});
auto it = absl::c_lower_bound(container, index, CCDataComparator<ValueType> {});
if (it == container.end() || it->cc != index) {
return defaultValue;
} else {
@ -60,7 +63,7 @@ public:
*/
ValueType& operator[](const int& index) noexcept
{
auto it = absl::c_lower_bound(container, index, CCDataComparator<ValueType>{});
auto it = absl::c_lower_bound(container, index, CCDataComparator<ValueType> {});
if (it == container.end() || it->cc != index) {
auto inserted = container.insert(it, { index, defaultValue });
return inserted->data;
@ -85,10 +88,19 @@ public:
*/
bool contains(int index) const noexcept
{
return absl::c_binary_search(container, index, CCDataComparator<ValueType>{});
return absl::c_binary_search(container, index, CCDataComparator<ValueType> {});
}
/**
* @brief Container size
*
* @return size_t
*/
size_t size() const noexcept { return container.size(); }
typename std::vector<CCData<ValueType>>::const_iterator begin() const { return container.cbegin(); }
typename std::vector<CCData<ValueType>>::const_iterator end() const { return container.cend(); }
private:
// typename std::vector<std::pair<int, ValueType>>::iterator begin() { return container.begin(); }
// typename std::vector<std::pair<int, ValueType>>::iterator end() { return container.end(); }

View file

@ -26,6 +26,7 @@ enum class Oversampling: int {
namespace config {
constexpr float defaultSampleRate { 48000 };
constexpr float maxSampleRate { 192000 };
constexpr int defaultSamplesPerBlock { 1024 };
constexpr int maxBlockSize { 8192 };
constexpr int bufferPoolSize { 6 };
@ -39,6 +40,9 @@ namespace config {
constexpr int numBackgroundThreads { 4 };
constexpr int numVoices { 64 };
constexpr unsigned maxVoices { 256 };
constexpr unsigned smoothingSteps { 512 };
constexpr uint8_t gainSmoothing { 5 };
constexpr unsigned powerTableSizeExponent { 11 };
constexpr int maxFilePromises { maxVoices };
constexpr int sustainCC { 64 };
constexpr int allSoundOffCC { 120 };

View file

@ -82,8 +82,8 @@ namespace Default
// Region logic: MIDI conditions
constexpr Range<uint8_t> channelRange { 1, 16 };
constexpr Range<uint8_t> midiChannelRange { 0, 15 };
constexpr Range<float> stepCCRange { 0, 127 };
constexpr Range<uint8_t> smoothCCRange { 0, 127 };
constexpr Range<uint8_t> smoothCCRange { 0, 100 };
constexpr float smoothTauPerStep { 3e-3 };
constexpr Range<uint8_t> curveCCRange { 0, 255 };
constexpr Range<uint16_t> ccNumberRange { 0, config::numCCs };
constexpr auto ccValueRange = normalizedRange;
@ -202,6 +202,7 @@ namespace Default
constexpr int bendUp { 200 }; // No range here because the bounds can be inverted
constexpr int bendDown { -200 };
constexpr int bendStep { 1 };
constexpr uint8_t bendSmooth { 0 };
// Envelope generators
constexpr float attack { 0 };

View file

@ -2,6 +2,7 @@
#include "SfzFilter.h"
#include "FilterDescription.h"
#include "MidiState.h"
#include "Defaults.h"
#include <vector>
#include <memory>
#include <mutex>

View file

@ -70,7 +70,8 @@ sfz::Logger::~Logger()
loadLogFile << time.waitDuration.count() << ','
<< time.loadDuration.count() << ','
<< time.fileSize << ','
<< time.filename << '\n';
<< "disabled" << '\n';
// << time.filename << '\n';
}
if (!callbackTimes.empty()) {

View file

@ -254,6 +254,11 @@ constexpr Type sqrtTwo() { return static_cast<Type>(1.41421356237309504880168872
template <class Type>
constexpr Type sqrtTwoInv() { return static_cast<Type>(0.707106781186547524400844362104849039284835937688474036588); };
constexpr unsigned int mask(int x)
{
return (1U << x) - 1;
}
/**
* @brief lround for positive values
* This optimizes a bit better by ignoring the negative code path
@ -493,7 +498,6 @@ constexpr bool checkSpanSizes(const absl::Span<T>& span1, Others... others)
#define CHECK_SPAN_SIZES(...) SFIZZ_CHECK(checkSpanSizes(__VA_ARGS__))
class ScopedRoundingMode {
public:
ScopedRoundingMode() = delete;

View file

@ -1,13 +1,18 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// 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
#pragma once
#include "Range.h"
#include "Defaults.h"
#include "SfzHelpers.h"
#include "Modifiers.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, ...)
*/
@ -56,6 +61,14 @@ float crossfadeOut(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCur
return 1.0f;
}
/**
* @brief Compute a linear envelope based on events, with a lambda applied to the event values
*
* @tparam F
* @param events
* @param envelope
* @param lambda
*/
template <class F>
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
{
@ -77,6 +90,15 @@ void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& l
fill(envelope.subspan(lastDelay), lastValue);
}
/**
* @brief Compute a quantized linear envelope based on events, with a lambda applied to the event values
*
* @tparam F
* @param events
* @param envelope
* @param lambda
* @param step
*/
template <class F>
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
{
@ -117,6 +139,15 @@ void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& l
fill(envelope.subspan(lastDelay), lastValue);
}
/**
* @brief Compute a multiplicative envelope based on events, with a lambda applied to the event values
*
* @tparam F
* @param events
* @param envelope
* @param lambda
*/
template <class F>
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
{
@ -140,6 +171,16 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelop
fill(envelope.subspan(lastDelay), lastValue);
}
/**
* @brief Compute a quantized multiplicative envelope based on events, with a lambda applied to the event values
*
* @tparam F
* @tparam Round is true if the quantization rounds rather than truncs the elements
* @param events
* @param envelope
* @param lambda
* @param step
*/
template <class F, bool Round = false>
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
{
@ -187,18 +228,46 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelop
fill(envelope.subspan(lastDelay), lastValue);
}
/**
* @brief Alias for a multiplicative envelope that rounds the elements
*
* @tparam F
* @param events
* @param envelope
* @param lambda
* @param step
*/
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);
}
/**
* @brief Alias for a multiplicative envelope
*
* @tparam F
* @param events
* @param envelope
* @param lambda
*/
template <class F>
void pitchBendEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
{
multiplicativeEnvelope<F>(events, envelope, std::forward<F>(lambda));
}
/**
* @brief Builds a linear envelope, possibly quantized, based on the events fetched
* from a midi state and the modifier data. This is a helper function for recurrent
* code in the voice logic.
*
* @tparam F
* @param resources
* @param span
* @param ccData
* @param lambda
*/
template <class F>
void linearModifier(const sfz::Resources& resources, absl::Span<float> span, const sfz::CCData<sfz::Modifier>& ccData, F&& lambda)
{
@ -218,6 +287,17 @@ void linearModifier(const sfz::Resources& resources, absl::Span<float> span, con
}
}
/**
* @brief Builds a multiplicative envelope, possibly quantized, based on the events fetched
* from a midi state and the modifier data. This is a helper function for recurrent
* code in the voice logic.
*
* @tparam F
* @param resources
* @param span
* @param ccData
* @param lambda
*/
template <class F>
void multiplicativeModifier(const sfz::Resources& resources, absl::Span<float> span, const sfz::CCData<sfz::Modifier>& ccData, F&& lambda)
{
@ -237,6 +317,15 @@ void multiplicativeModifier(const sfz::Resources& resources, absl::Span<float> s
}
}
/**
* @brief Alias for a simple linear modifier with no lambda
*
* @tparam F
* @param resources
* @param span
* @param ccData
* @param lambda
*/
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; });

92
src/sfizz/Modifiers.h Normal file
View file

@ -0,0 +1,92 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// 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
#pragma once
#include "Config.h"
#include <numeric>
#include <array>
#include <vector>
#include <limits>
#include <cstdint>
namespace sfz {
/**
* @brief Base modifier class
*
*/
struct Modifier {
float value { 0.0f };
float step { 0.0f };
uint8_t curve { 0 };
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");
};
enum class Mod : size_t {
amplitude = 0,
pan,
width,
position,
pitch,
volume,
sentinel
};
/**
* @brief Vectors of elements indexed on modifiers with casting and iterators
*
* @tparam T
*/
template <class T>
class ModifierVector : public std::vector<T> {
public:
T& operator[](sfz::Mod idx) { return this->std::vector<T>::operator[](static_cast<size_t>(idx)); }
const T& operator[](sfz::Mod idx) const { return this->std::vector<T>::operator[](static_cast<size_t>(idx)); }
};
/**
* @brief Array of elements indexed on modifiers with casting and iterators
*
* @tparam T
*/
template <class T>
class ModifierArray {
public:
using ContainerType = typename std::array<T, (size_t)Mod::sentinel>;
using iterator = typename ContainerType::iterator;
using const_iterator = typename ContainerType::const_iterator;
ModifierArray() = default;
ModifierArray(T val)
{
std::fill(underlying.begin(), underlying.end(), val);
}
ModifierArray(std::array<T, (size_t)Mod::sentinel>&& array) : underlying(array) {}
T& operator[](sfz::Mod idx) { return underlying.operator[](static_cast<size_t>(idx)); }
const T& operator[](sfz::Mod idx) const { return underlying.operator[](static_cast<size_t>(idx)); }
iterator begin() { return underlying.begin(); }
iterator end() { return underlying.end(); }
const_iterator begin() const { return underlying.begin(); }
const_iterator end() const { return underlying.end(); }
private:
ContainerType underlying {};
};
/**
* @brief Helper for iterating over all possible modifiers.
* Should fail at compile time if you update the modifiers but not this.
*
*/
static const ModifierArray<Mod> allModifiers {{
Mod::amplitude,
Mod::pan,
Mod::width,
Mod::position,
Mod::pitch,
Mod::volume
}};
}

View file

@ -113,3 +113,149 @@ sfz::OpcodeCategory sfz::Opcode::identifyCategory(absl::string_view name)
return category;
}
absl::optional<uint8_t> sfz::readNoteValue(const absl::string_view& value)
{
switch(hash(value))
{
case hash("c-1"): case hash("C-1"): return (uint8_t) 0;
case hash("c#-1"): case hash("C#-1"): return (uint8_t) 1;
case hash("d-1"): case hash("D-1"): return (uint8_t) 2;
case hash("d#-1"): case hash("D#-1"): return (uint8_t) 3;
case hash("e-1"): case hash("E-1"): return (uint8_t) 4;
case hash("f-1"): case hash("F-1"): return (uint8_t) 5;
case hash("f#-1"): case hash("F#-1"): return (uint8_t) 6;
case hash("g-1"): case hash("G-1"): return (uint8_t) 7;
case hash("g#-1"): case hash("G#-1"): return (uint8_t) 8;
case hash("a-1"): case hash("A-1"): return (uint8_t) 9;
case hash("a#-1"): case hash("A#-1"): return (uint8_t) 10;
case hash("b-1"): case hash("B-1"): return (uint8_t) 11;
case hash("c0"): case hash("C0"): return (uint8_t) 12;
case hash("c#0"): case hash("C#0"): return (uint8_t) 13;
case hash("d0"): case hash("D0"): return (uint8_t) 14;
case hash("d#0"): case hash("D#0"): return (uint8_t) 15;
case hash("e0"): case hash("E0"): return (uint8_t) 16;
case hash("f0"): case hash("F0"): return (uint8_t) 17;
case hash("f#0"): case hash("F#0"): return (uint8_t) 18;
case hash("g0"): case hash("G0"): return (uint8_t) 19;
case hash("g#0"): case hash("G#0"): return (uint8_t) 20;
case hash("a0"): case hash("A0"): return (uint8_t) 21;
case hash("a#0"): case hash("A#0"): return (uint8_t) 22;
case hash("b0"): case hash("B0"): return (uint8_t) 23;
case hash("c1"): case hash("C1"): return (uint8_t) 24;
case hash("c#1"): case hash("C#1"): return (uint8_t) 25;
case hash("d1"): case hash("D1"): return (uint8_t) 26;
case hash("d#1"): case hash("D#1"): return (uint8_t) 27;
case hash("e1"): case hash("E1"): return (uint8_t) 28;
case hash("f1"): case hash("F1"): return (uint8_t) 29;
case hash("f#1"): case hash("F#1"): return (uint8_t) 30;
case hash("g1"): case hash("G1"): return (uint8_t) 31;
case hash("g#1"): case hash("G#1"): return (uint8_t) 32;
case hash("a1"): case hash("A1"): return (uint8_t) 33;
case hash("a#1"): case hash("A#1"): return (uint8_t) 34;
case hash("b1"): case hash("B1"): return (uint8_t) 35;
case hash("c2"): case hash("C2"): return (uint8_t) 36;
case hash("c#2"): case hash("C#2"): return (uint8_t) 37;
case hash("d2"): case hash("D2"): return (uint8_t) 38;
case hash("d#2"): case hash("D#2"): return (uint8_t) 39;
case hash("e2"): case hash("E2"): return (uint8_t) 40;
case hash("f2"): case hash("F2"): return (uint8_t) 41;
case hash("f#2"): case hash("F#2"): return (uint8_t) 42;
case hash("g2"): case hash("G2"): return (uint8_t) 43;
case hash("g#2"): case hash("G#2"): return (uint8_t) 44;
case hash("a2"): case hash("A2"): return (uint8_t) 45;
case hash("a#2"): case hash("A#2"): return (uint8_t) 46;
case hash("b2"): case hash("B2"): return (uint8_t) 47;
case hash("c3"): case hash("C3"): return (uint8_t) 48;
case hash("c#3"): case hash("C#3"): return (uint8_t) 49;
case hash("d3"): case hash("D3"): return (uint8_t) 50;
case hash("d#3"): case hash("D#3"): return (uint8_t) 51;
case hash("e3"): case hash("E3"): return (uint8_t) 52;
case hash("f3"): case hash("F3"): return (uint8_t) 53;
case hash("f#3"): case hash("F#3"): return (uint8_t) 54;
case hash("g3"): case hash("G3"): return (uint8_t) 55;
case hash("g#3"): case hash("G#3"): return (uint8_t) 56;
case hash("a3"): case hash("A3"): return (uint8_t) 57;
case hash("a#3"): case hash("A#3"): return (uint8_t) 58;
case hash("b3"): case hash("B3"): return (uint8_t) 59;
case hash("c4"): case hash("C4"): return (uint8_t) 60;
case hash("c#4"): case hash("C#4"): return (uint8_t) 61;
case hash("d4"): case hash("D4"): return (uint8_t) 62;
case hash("d#4"): case hash("D#4"): return (uint8_t) 63;
case hash("e4"): case hash("E4"): return (uint8_t) 64;
case hash("f4"): case hash("F4"): return (uint8_t) 65;
case hash("f#4"): case hash("F#4"): return (uint8_t) 66;
case hash("g4"): case hash("G4"): return (uint8_t) 67;
case hash("g#4"): case hash("G#4"): return (uint8_t) 68;
case hash("a4"): case hash("A4"): return (uint8_t) 69;
case hash("a#4"): case hash("A#4"): return (uint8_t) 70;
case hash("b4"): case hash("B4"): return (uint8_t) 71;
case hash("c5"): case hash("C5"): return (uint8_t) 72;
case hash("c#5"): case hash("C#5"): return (uint8_t) 73;
case hash("d5"): case hash("D5"): return (uint8_t) 74;
case hash("d#5"): case hash("D#5"): return (uint8_t) 75;
case hash("e5"): case hash("E5"): return (uint8_t) 76;
case hash("f5"): case hash("F5"): return (uint8_t) 77;
case hash("f#5"): case hash("F#5"): return (uint8_t) 78;
case hash("g5"): case hash("G5"): return (uint8_t) 79;
case hash("g#5"): case hash("G#5"): return (uint8_t) 80;
case hash("a5"): case hash("A5"): return (uint8_t) 81;
case hash("a#5"): case hash("A#5"): return (uint8_t) 82;
case hash("b5"): case hash("B5"): return (uint8_t) 83;
case hash("c6"): case hash("C6"): return (uint8_t) 84;
case hash("c#6"): case hash("C#6"): return (uint8_t) 85;
case hash("d6"): case hash("D6"): return (uint8_t) 86;
case hash("d#6"): case hash("D#6"): return (uint8_t) 87;
case hash("e6"): case hash("E6"): return (uint8_t) 88;
case hash("f6"): case hash("F6"): return (uint8_t) 89;
case hash("f#6"): case hash("F#6"): return (uint8_t) 90;
case hash("g6"): case hash("G6"): return (uint8_t) 91;
case hash("g#6"): case hash("G#6"): return (uint8_t) 92;
case hash("a6"): case hash("A6"): return (uint8_t) 93;
case hash("a#6"): case hash("A#6"): return (uint8_t) 94;
case hash("b6"): case hash("B6"): return (uint8_t) 95;
case hash("c7"): case hash("C7"): return (uint8_t) 96;
case hash("c#7"): case hash("C#7"): return (uint8_t) 97;
case hash("d7"): case hash("D7"): return (uint8_t) 98;
case hash("d#7"): case hash("D#7"): return (uint8_t) 99;
case hash("e7"): case hash("E7"): return (uint8_t) 100;
case hash("f7"): case hash("F7"): return (uint8_t) 101;
case hash("f#7"): case hash("F#7"): return (uint8_t) 102;
case hash("g7"): case hash("G7"): return (uint8_t) 103;
case hash("g#7"): case hash("G#7"): return (uint8_t) 104;
case hash("a7"): case hash("A7"): return (uint8_t) 105;
case hash("a#7"): case hash("A#7"): return (uint8_t) 106;
case hash("b7"): case hash("B7"): return (uint8_t) 107;
case hash("c8"): case hash("C8"): return (uint8_t) 108;
case hash("c#8"): case hash("C#8"): return (uint8_t) 109;
case hash("d8"): case hash("D8"): return (uint8_t) 110;
case hash("d#8"): case hash("D#8"): return (uint8_t) 111;
case hash("e8"): case hash("E8"): return (uint8_t) 112;
case hash("f8"): case hash("F8"): return (uint8_t) 113;
case hash("f#8"): case hash("F#8"): return (uint8_t) 114;
case hash("g8"): case hash("G8"): return (uint8_t) 115;
case hash("g#8"): case hash("G#8"): return (uint8_t) 116;
case hash("a8"): case hash("A8"): return (uint8_t) 117;
case hash("a#8"): case hash("A#8"): return (uint8_t) 118;
case hash("b8"): case hash("B8"): return (uint8_t) 119;
case hash("c9"): case hash("C9"): return (uint8_t) 120;
case hash("c#9"): case hash("C#9"): return (uint8_t) 121;
case hash("d9"): case hash("D9"): return (uint8_t) 122;
case hash("d#9"): case hash("D#9"): return (uint8_t) 123;
case hash("e9"): case hash("E9"): return (uint8_t) 124;
case hash("f9"): case hash("F9"): return (uint8_t) 125;
case hash("f#9"): case hash("F#9"): return (uint8_t) 126;
case hash("g9"): case hash("G9"): return (uint8_t) 127;
default: return {};
}
}

View file

@ -103,6 +103,14 @@ private:
LEAK_DETECTOR(Opcode);
};
/**
* @brief Convert a note in string to its equivalent midi note number
*
* @param value
* @return absl::optional<uint8_t>
*/
absl::optional<uint8_t> readNoteValue(const absl::string_view& value);
/**
* @brief Read a value from the sfz file and cast it to the destination parameter along
* with a proper clamping into range if needed. This particular template version acts on

View file

@ -351,35 +351,35 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
setValueFromOpcode(opcode, volume, Default::volumeRange);
break;
case_any_ccN("volume"): // also gain
processGenericCc(opcode, Default::volumeCCRange, &volumeCC);
processGenericCc(opcode, Default::volumeCCRange, &modifiers[Mod::volume]);
break;
case hash("amplitude"):
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
amplitude = normalizePercents(*value);
break;
case_any_ccN("amplitude"):
processGenericCc(opcode, Default::amplitudeRange, &amplitudeCC);
processGenericCc(opcode, Default::amplitudeRange, &modifiers[Mod::amplitude]);
break;
case hash("pan"):
if (auto value = readOpcode(opcode.value, Default::panRange))
pan = normalizePercents(*value);
break;
case_any_ccN("pan"):
processGenericCc(opcode, Default::panCCRange, &panCC);
processGenericCc(opcode, Default::panCCRange, &modifiers[Mod::pan]);
break;
case hash("position"):
if (auto value = readOpcode(opcode.value, Default::positionRange))
position = normalizePercents(*value);
break;
case_any_ccN("position"):
processGenericCc(opcode, Default::positionCCRange, &positionCC);
processGenericCc(opcode, Default::positionCCRange, &modifiers[Mod::position]);
break;
case hash("width"):
if (auto value = readOpcode(opcode.value, Default::widthRange))
width = normalizePercents(*value);
break;
case_any_ccN("width"):
processGenericCc(opcode, Default::widthCCRange, &widthCC);
processGenericCc(opcode, Default::widthCCRange, &modifiers[Mod::width]);
break;
case hash("amp_keycenter"):
setValueFromOpcode(opcode, ampKeycenter, Default::keyRange);
@ -742,7 +742,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
setValueFromOpcode(opcode, tune, Default::tuneRange);
break;
case_any_ccN("pitch"): // also tune
processGenericCc(opcode, Default::tuneCCRange, &tuneCC);
processGenericCc(opcode, Default::tuneCCRange, &modifiers[Mod::pitch]);
break;
case hash("bend_up"): // also bendup
setValueFromOpcode(opcode, bendUp, Default::bendBoundRange);
@ -753,6 +753,9 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
case hash("bend_step"):
setValueFromOpcode(opcode, bendStep, Default::bendStepRange);
break;
case hash("bend_smooth"):
setValueFromOpcode(opcode, bendSmooth, Default::smoothCCRange);
break;
// Amplitude Envelope
case hash("ampeg_attack"):
@ -1219,3 +1222,8 @@ float sfz::Region::getGainToEffectBus(unsigned number) const noexcept
return gainToEffect[number];
}
float sfz::Region::getBendInCents(float bend) const noexcept
{
return bend > 0.0f ? bend * static_cast<float>(bendUp) : -bend * static_cast<float>(bendDown);
}

View file

@ -17,6 +17,7 @@
#include "MidiState.h"
#include "FileId.h"
#include "NumericId.h"
#include "Modifiers.h"
#include "absl/types/optional.h"
#include <bitset>
#include <string>
@ -195,6 +196,14 @@ struct Region {
* @return float
*/
float velocityCurve(float velocity) const noexcept;
/**
* @brief Get the detuning in cents for a given bend value between -1 and 1
*
* @param bend
* @return float
*/
float getBendInCents(float bend) const noexcept;
/**
* @brief Get the region offset in samples
*
@ -309,11 +318,6 @@ struct Region {
float pan { normalizePercents(Default::pan) }; // pan
float width { normalizePercents(Default::width) }; // width
float position { normalizePercents(Default::position) }; // position
CCMap<Modifier> volumeCC { {} }; // volume_oncc
CCMap<Modifier> amplitudeCC { {} }; // amplitude_oncc
CCMap<Modifier> panCC { {} }; // pan_oncc
CCMap<Modifier> widthCC { {} }; // width_oncc
CCMap<Modifier> positionCC { {} }; // position_oncc
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
@ -342,10 +346,10 @@ struct Region {
int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack
int transpose { Default::transpose }; // transpose
int tune { Default::tune }; // tune
CCMap<Modifier> tuneCC { {} };
int bendUp { Default::bendUp };
int bendDown { Default::bendDown };
int bendStep { Default::bendStep };
uint8_t bendSmooth { Default::bendSmooth };
// Envelopes
EGDescription amplitudeEG;
@ -357,6 +361,9 @@ struct Region {
// Effects
std::vector<float> gainToEffect;
// Modifiers
ModifierArray<CCMap<Modifier>> modifiers;
private:
const MidiState& midiState;
bool keySwitched { true };

View file

@ -7,153 +7,7 @@
#include "SfzHelpers.h"
#include "StringViewHelpers.h"
namespace sfz{
absl::optional<uint8_t> readNoteValue(const absl::string_view& value)
{
switch(hash(value))
{
case hash("c-1"): case hash("C-1"): return (uint8_t) 0;
case hash("c#-1"): case hash("C#-1"): return (uint8_t) 1;
case hash("d-1"): case hash("D-1"): return (uint8_t) 2;
case hash("d#-1"): case hash("D#-1"): return (uint8_t) 3;
case hash("e-1"): case hash("E-1"): return (uint8_t) 4;
case hash("f-1"): case hash("F-1"): return (uint8_t) 5;
case hash("f#-1"): case hash("F#-1"): return (uint8_t) 6;
case hash("g-1"): case hash("G-1"): return (uint8_t) 7;
case hash("g#-1"): case hash("G#-1"): return (uint8_t) 8;
case hash("a-1"): case hash("A-1"): return (uint8_t) 9;
case hash("a#-1"): case hash("A#-1"): return (uint8_t) 10;
case hash("b-1"): case hash("B-1"): return (uint8_t) 11;
case hash("c0"): case hash("C0"): return (uint8_t) 12;
case hash("c#0"): case hash("C#0"): return (uint8_t) 13;
case hash("d0"): case hash("D0"): return (uint8_t) 14;
case hash("d#0"): case hash("D#0"): return (uint8_t) 15;
case hash("e0"): case hash("E0"): return (uint8_t) 16;
case hash("f0"): case hash("F0"): return (uint8_t) 17;
case hash("f#0"): case hash("F#0"): return (uint8_t) 18;
case hash("g0"): case hash("G0"): return (uint8_t) 19;
case hash("g#0"): case hash("G#0"): return (uint8_t) 20;
case hash("a0"): case hash("A0"): return (uint8_t) 21;
case hash("a#0"): case hash("A#0"): return (uint8_t) 22;
case hash("b0"): case hash("B0"): return (uint8_t) 23;
case hash("c1"): case hash("C1"): return (uint8_t) 24;
case hash("c#1"): case hash("C#1"): return (uint8_t) 25;
case hash("d1"): case hash("D1"): return (uint8_t) 26;
case hash("d#1"): case hash("D#1"): return (uint8_t) 27;
case hash("e1"): case hash("E1"): return (uint8_t) 28;
case hash("f1"): case hash("F1"): return (uint8_t) 29;
case hash("f#1"): case hash("F#1"): return (uint8_t) 30;
case hash("g1"): case hash("G1"): return (uint8_t) 31;
case hash("g#1"): case hash("G#1"): return (uint8_t) 32;
case hash("a1"): case hash("A1"): return (uint8_t) 33;
case hash("a#1"): case hash("A#1"): return (uint8_t) 34;
case hash("b1"): case hash("B1"): return (uint8_t) 35;
case hash("c2"): case hash("C2"): return (uint8_t) 36;
case hash("c#2"): case hash("C#2"): return (uint8_t) 37;
case hash("d2"): case hash("D2"): return (uint8_t) 38;
case hash("d#2"): case hash("D#2"): return (uint8_t) 39;
case hash("e2"): case hash("E2"): return (uint8_t) 40;
case hash("f2"): case hash("F2"): return (uint8_t) 41;
case hash("f#2"): case hash("F#2"): return (uint8_t) 42;
case hash("g2"): case hash("G2"): return (uint8_t) 43;
case hash("g#2"): case hash("G#2"): return (uint8_t) 44;
case hash("a2"): case hash("A2"): return (uint8_t) 45;
case hash("a#2"): case hash("A#2"): return (uint8_t) 46;
case hash("b2"): case hash("B2"): return (uint8_t) 47;
case hash("c3"): case hash("C3"): return (uint8_t) 48;
case hash("c#3"): case hash("C#3"): return (uint8_t) 49;
case hash("d3"): case hash("D3"): return (uint8_t) 50;
case hash("d#3"): case hash("D#3"): return (uint8_t) 51;
case hash("e3"): case hash("E3"): return (uint8_t) 52;
case hash("f3"): case hash("F3"): return (uint8_t) 53;
case hash("f#3"): case hash("F#3"): return (uint8_t) 54;
case hash("g3"): case hash("G3"): return (uint8_t) 55;
case hash("g#3"): case hash("G#3"): return (uint8_t) 56;
case hash("a3"): case hash("A3"): return (uint8_t) 57;
case hash("a#3"): case hash("A#3"): return (uint8_t) 58;
case hash("b3"): case hash("B3"): return (uint8_t) 59;
case hash("c4"): case hash("C4"): return (uint8_t) 60;
case hash("c#4"): case hash("C#4"): return (uint8_t) 61;
case hash("d4"): case hash("D4"): return (uint8_t) 62;
case hash("d#4"): case hash("D#4"): return (uint8_t) 63;
case hash("e4"): case hash("E4"): return (uint8_t) 64;
case hash("f4"): case hash("F4"): return (uint8_t) 65;
case hash("f#4"): case hash("F#4"): return (uint8_t) 66;
case hash("g4"): case hash("G4"): return (uint8_t) 67;
case hash("g#4"): case hash("G#4"): return (uint8_t) 68;
case hash("a4"): case hash("A4"): return (uint8_t) 69;
case hash("a#4"): case hash("A#4"): return (uint8_t) 70;
case hash("b4"): case hash("B4"): return (uint8_t) 71;
case hash("c5"): case hash("C5"): return (uint8_t) 72;
case hash("c#5"): case hash("C#5"): return (uint8_t) 73;
case hash("d5"): case hash("D5"): return (uint8_t) 74;
case hash("d#5"): case hash("D#5"): return (uint8_t) 75;
case hash("e5"): case hash("E5"): return (uint8_t) 76;
case hash("f5"): case hash("F5"): return (uint8_t) 77;
case hash("f#5"): case hash("F#5"): return (uint8_t) 78;
case hash("g5"): case hash("G5"): return (uint8_t) 79;
case hash("g#5"): case hash("G#5"): return (uint8_t) 80;
case hash("a5"): case hash("A5"): return (uint8_t) 81;
case hash("a#5"): case hash("A#5"): return (uint8_t) 82;
case hash("b5"): case hash("B5"): return (uint8_t) 83;
case hash("c6"): case hash("C6"): return (uint8_t) 84;
case hash("c#6"): case hash("C#6"): return (uint8_t) 85;
case hash("d6"): case hash("D6"): return (uint8_t) 86;
case hash("d#6"): case hash("D#6"): return (uint8_t) 87;
case hash("e6"): case hash("E6"): return (uint8_t) 88;
case hash("f6"): case hash("F6"): return (uint8_t) 89;
case hash("f#6"): case hash("F#6"): return (uint8_t) 90;
case hash("g6"): case hash("G6"): return (uint8_t) 91;
case hash("g#6"): case hash("G#6"): return (uint8_t) 92;
case hash("a6"): case hash("A6"): return (uint8_t) 93;
case hash("a#6"): case hash("A#6"): return (uint8_t) 94;
case hash("b6"): case hash("B6"): return (uint8_t) 95;
case hash("c7"): case hash("C7"): return (uint8_t) 96;
case hash("c#7"): case hash("C#7"): return (uint8_t) 97;
case hash("d7"): case hash("D7"): return (uint8_t) 98;
case hash("d#7"): case hash("D#7"): return (uint8_t) 99;
case hash("e7"): case hash("E7"): return (uint8_t) 100;
case hash("f7"): case hash("F7"): return (uint8_t) 101;
case hash("f#7"): case hash("F#7"): return (uint8_t) 102;
case hash("g7"): case hash("G7"): return (uint8_t) 103;
case hash("g#7"): case hash("G#7"): return (uint8_t) 104;
case hash("a7"): case hash("A7"): return (uint8_t) 105;
case hash("a#7"): case hash("A#7"): return (uint8_t) 106;
case hash("b7"): case hash("B7"): return (uint8_t) 107;
case hash("c8"): case hash("C8"): return (uint8_t) 108;
case hash("c#8"): case hash("C#8"): return (uint8_t) 109;
case hash("d8"): case hash("D8"): return (uint8_t) 110;
case hash("d#8"): case hash("D#8"): return (uint8_t) 111;
case hash("e8"): case hash("E8"): return (uint8_t) 112;
case hash("f8"): case hash("F8"): return (uint8_t) 113;
case hash("f#8"): case hash("F#8"): return (uint8_t) 114;
case hash("g8"): case hash("G8"): return (uint8_t) 115;
case hash("g#8"): case hash("G#8"): return (uint8_t) 116;
case hash("a8"): case hash("A8"): return (uint8_t) 117;
case hash("a#8"): case hash("A#8"): return (uint8_t) 118;
case hash("b8"): case hash("B8"): return (uint8_t) 119;
case hash("c9"): case hash("C9"): return (uint8_t) 120;
case hash("c#9"): case hash("C#9"): return (uint8_t) 121;
case hash("d9"): case hash("D9"): return (uint8_t) 122;
case hash("d#9"): case hash("D#9"): return (uint8_t) 123;
case hash("e9"): case hash("E9"): return (uint8_t) 124;
case hash("f9"): case hash("F9"): return (uint8_t) 125;
case hash("f#9"): case hash("F#9"): return (uint8_t) 126;
case hash("g9"): case hash("G9"): return (uint8_t) 127;
default: return {};
}
}
namespace sfz {
bool findHeader(absl::string_view& source, absl::string_view& header, absl::string_view& members)
{

View file

@ -17,30 +17,21 @@
#include "absl/meta/type_traits.h"
#include "Defaults.h"
namespace sfz
{
namespace sfz {
using CCNamePair = std::pair<uint16_t, std::string>;
using NoteNamePair = std::pair<uint8_t, std::string>;
template <class T>
using MidiNoteArray = std::array<T, 128>;
template<class ValueType>
template <class ValueType>
struct CCData {
int cc;
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");
};
struct Modifier {
float value { 0.0f };
float step { 0.0f };
uint8_t curve { 0 };
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");
};
template<class ValueType>
template <class ValueType>
struct CCDataComparator {
bool operator()(const CCData<ValueType>& ccData, const int& cc)
{
@ -106,13 +97,13 @@ struct MidiEventValueComparator {
* @param centsPerOctave
* @return constexpr float
*/
template<class T>
template <class T>
constexpr float centsFactor(T cents, T centsPerOctave = 1200)
{
return std::pow(2.0f, static_cast<float>(cents) / centsPerOctave);
}
template<class T, absl::enable_if_t<std::is_integral<T>::value, int> = 0>
template <class T, absl::enable_if_t<std::is_integral<T>::value, int> = 0>
constexpr T denormalize7Bits(float value)
{
return static_cast<T>(value * 127.0f);
@ -128,10 +119,10 @@ constexpr uint8_t denormalizeVelocity(float value)
return denormalize7Bits<uint8_t>(value);
}
template<class T>
template <class T>
constexpr float normalize7Bits(T value)
{
return static_cast<float>(min(max(value, T{ 0 }), T{ 127 })) / 127.0f;
return static_cast<float>(min(max(value, T { 0 }), T { 127 })) / 127.0f;
}
/**
@ -141,7 +132,7 @@ constexpr float normalize7Bits(T value)
* @param ccValue
* @return constexpr float
*/
template<class T>
template <class T>
constexpr float normalizeCC(T ccValue)
{
return normalize7Bits(ccValue);
@ -154,13 +145,12 @@ constexpr float normalizeCC(T ccValue)
* @param ccValue
* @return constexpr float
*/
template<class T>
template <class T>
constexpr float normalizeVelocity(T velocity)
{
return normalize7Bits(velocity);
}
/**
* @brief Normalize a percentage between 0 and 1
*
@ -168,7 +158,7 @@ constexpr float normalizeVelocity(T velocity)
* @param percentValue
* @return constexpr float
*/
template<class T>
template <class T>
constexpr float normalizePercents(T percentValue)
{
return percentValue * 0.01f;
@ -206,14 +196,6 @@ namespace literals {
}
}
/**
* @brief Convert a note in string to its equivalent midi note number
*
* @param value
* @return absl::optional<uint8_t>
*/
absl::optional<uint8_t> readNoteValue(const absl::string_view& value);
template <class Type>
inline CXX14_CONSTEXPR Type vaGain(Type cutoff, Type sampleRate)
{
@ -283,4 +265,3 @@ bool findDefine(absl::string_view line, absl::string_view& variable, absl::strin
bool findInclude(absl::string_view line, std::string& path);
} // namespace sfz

36
src/sfizz/Smoothers.cpp Normal file
View file

@ -0,0 +1,36 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// 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
#include "Smoothers.h"
namespace sfz {
Smoother::Smoother()
{
}
void Smoother::setSmoothing(uint8_t smoothValue, float sampleRate)
{
smoothing = (smoothValue > 0);
if (smoothing) {
filter.setGain(std::tan(1.0f / (2 * Default::smoothTauPerStep * smoothValue * sampleRate)));
}
}
void Smoother::reset(float value)
{
filter.reset(value);
}
void Smoother::process(absl::Span<const float> input, absl::Span<float> output)
{
if (smoothing)
filter.processLowpass(input, output);
else
copy<float>(input, output);
}
}

53
src/sfizz/Smoothers.h Normal file
View file

@ -0,0 +1,53 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// 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
#pragma once
#include "Config.h"
#include "Defaults.h"
#include "MathHelpers.h"
#include "SfzHelpers.h"
#include "OnePoleFilter.h"
#include "SIMDHelpers.h"
#include <array>
namespace sfz {
/**
* @brief Wrapper class for a one pole filter smoother
*
*/
class Smoother {
public:
Smoother();
/**
* @brief Set the filter cutoff based on the sfz smoothing value
* and the sample rate.
*
* @param smoothValue
* @param sampleRate
*/
void setSmoothing(uint8_t smoothValue, float sampleRate);
/**
* @brief Reset the filter state to a given value
*
* @param value
*/
void reset(float value = 0.0f);
/**
* @brief Process a span of data. Input and output can refer to the same
* memory.
*
* @param input
* @param output
*/
void process(absl::Span<const float> input, absl::Span<float> output);
private:
bool smoothing { false };
OnePoleFilter<float> filter {};
};
}

View file

@ -9,6 +9,7 @@
#include "Debug.h"
#include "Macros.h"
#include "MidiState.h"
#include "ModifierHelpers.h"
#include "ScopedFTZ.h"
#include "StringViewHelpers.h"
#include "pugixml.hpp"
@ -374,6 +375,7 @@ void sfz::Synth::finalizeSfzLoad()
size_t maxFilters { 0 };
size_t maxEQs { 0 };
ModifierArray<size_t> maxModifiers { 0 };
while (currentRegionIndex < currentRegionCount) {
auto region = regions[currentRegionIndex].get();
@ -480,6 +482,8 @@ void sfz::Synth::finalizeSfzLoad()
region->registerTempo(2.0f);
maxFilters = max(maxFilters, region->filters.size());
maxEQs = max(maxEQs, region->equalizers.size());
for (const auto& mod : allModifiers)
maxModifiers[mod] = max(maxModifiers[mod], region->modifiers[mod].size());
++currentRegionIndex;
}
@ -490,6 +494,7 @@ void sfz::Synth::finalizeSfzLoad()
for (auto& voice : voices) {
voice->setMaxFiltersPerVoice(maxFilters);
voice->setMaxEQsPerVoice(maxEQs);
voice->prepareSmoothers(maxModifiers);
}
}

View file

@ -24,6 +24,8 @@ sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources)
for (WavetableOscillator& osc : waveOscillators)
osc.init(sampleRate);
gainSmoother.setSmoothing(config::gainSmoothing, sampleRate);
for (auto & filter : channelEnvelopeFilters)
filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate));
}
@ -97,6 +99,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
baseGain = region->getBaseGain();
if (triggerType != TriggerType::CC)
baseGain *= region->getNoteGain(number, value);
gainSmoother.reset();
// Check that we can handle the number of filters; filters should be cleared here
ASSERT((filters.capacity() - filters.size()) >= region->filters.size());
@ -120,7 +123,33 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
initialDelay = delay + static_cast<int>(region->getDelay() * sampleRate);
baseFrequency = resources.tuning.getFrequencyOfKey(number);
bendStepFactor = centsFactor(region->bendStep);
bendSmoother.setSmoothing(region->bendSmooth, sampleRate);
bendSmoother.reset(centsFactor(region->getBendInCents(resources.midiState.getPitchBend())));
egEnvelope.reset(region->amplitudeEG, *region, resources.midiState, delay, value, sampleRate);
for (auto& modId : allModifiers) {
ASSERT(modifierSmoothers[modId].size() >= region->modifiers[modId].size());
forEachWithSmoother(modId, [modId, this](const CCData<Modifier>& mod, Smoother& smoother) {
switch (modId) {
case Mod::volume:
smoother.reset(db2mag(resources.midiState.getCCValue(mod.cc) * mod.data.value));
break;
case Mod::pitch:
smoother.reset(centsFactor(resources.midiState.getCCValue(mod.cc) * mod.data.value));
break;
case Mod::amplitude:
case Mod::pan:
case Mod::width:
case Mod::position:
smoother.reset(normalizePercents(resources.midiState.getCCValue(mod.cc) * mod.data.value));
break;
default:
smoother.reset(resources.midiState.getCCValue(mod.cc) * mod.data.value);
break;
}
smoother.setSmoothing(mod.data.smooth, sampleRate);
});
}
}
int sfz::Voice::getCurrentSampleQuality() const noexcept
@ -206,6 +235,7 @@ void sfz::Voice::registerTempo(int delay, float secondsPerQuarter) noexcept
void sfz::Voice::setSampleRate(float sampleRate) noexcept
{
this->sampleRate = sampleRate;
gainSmoother.setSmoothing(config::gainSmoothing, sampleRate);
for (auto & filter : channelEnvelopeFilters)
filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate));
@ -283,10 +313,11 @@ void sfz::Voice::amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept
// Amplitude envelope
applyGain1<float>(baseGain, modulationSpan);
for (const auto& mod : region->amplitudeCC) {
forEachWithSmoother(Mod::amplitude, [&](const CCData<Modifier>& mod, Smoother& smoother) {
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
smoother.process(*tempSpan, *tempSpan);
applyGain<float>(*tempSpan, modulationSpan);
}
});
// Crossfade envelopes
for (const auto& mod : region->crossfadeCCInRange) {
@ -306,12 +337,16 @@ void sfz::Voice::amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept
// Volume envelope
applyGain1<float>(db2mag(baseVolumedB), modulationSpan);
for (const auto& mod : region->volumeCC) {
forEachWithSmoother(Mod::volume, [&](const CCData<Modifier>& mod, Smoother& smoother) {
multiplicativeModifier(resources, *tempSpan, mod, [](float x) {
return db2mag(x);
});
smoother.process(*tempSpan, *tempSpan);
applyGain<float>(*tempSpan, modulationSpan);
}
});
// Smooth the gain transitions
gainSmoother.process(modulationSpan, modulationSpan);
}
void sfz::Voice::ampStageMono(AudioSpan<float> buffer) noexcept
@ -360,10 +395,11 @@ void sfz::Voice::panStageMono(AudioSpan<float> buffer) noexcept
// Apply panning
fill(*modulationSpan, region->pan);
for (const auto& mod : region->panCC) {
forEachWithSmoother(Mod::pan, [&](const CCData<Modifier>& mod, Smoother& smoother) {
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
smoother.process(*tempSpan, *tempSpan);
add<float>(*tempSpan, *modulationSpan);
}
});
pan(*modulationSpan, leftBuffer, rightBuffer);
}
@ -381,25 +417,28 @@ void sfz::Voice::panStageStereo(AudioSpan<float> buffer) noexcept
// Apply panning
fill(*modulationSpan, region->pan);
for (const auto& mod : region->panCC) {
forEachWithSmoother(Mod::pan, [&](const CCData<Modifier>& mod, Smoother& smoother) {
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
smoother.process(*tempSpan, *tempSpan);
add<float>(*tempSpan, *modulationSpan);
}
});
pan(*modulationSpan, leftBuffer, rightBuffer);
// Apply the width/position process
fill(*modulationSpan, region->width);
for (const auto& mod : region->widthCC) {
forEachWithSmoother(Mod::width, [&](const CCData<Modifier>& mod, Smoother& smoother) {
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
smoother.process(*tempSpan, *tempSpan);
add<float>(*tempSpan, *modulationSpan);
}
});
width(*modulationSpan, leftBuffer, rightBuffer);
fill(*modulationSpan, region->position);
for (const auto& mod : region->positionCC) {
forEachWithSmoother(Mod::position, [&](const CCData<Modifier>& mod, Smoother& smoother) {
linearModifier(resources, *tempSpan, mod, normalizePercents<float>);
smoother.process(*tempSpan, *tempSpan);
add<float>(*tempSpan, *modulationSpan);
}
});
pan(*modulationSpan, leftBuffer, rightBuffer);
}
@ -452,30 +491,13 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
auto source = currentPromise->getData();
auto jumps = resources.bufferPool.getBuffer(numSamples);
auto bends = resources.bufferPool.getBuffer(numSamples);
auto coeffs = resources.bufferPool.getBuffer(numSamples);
auto indices = resources.bufferPool.getIndexBuffer(numSamples);
if (!jumps || !bends || !indices || !coeffs)
if (!jumps || !indices || !coeffs)
return;
fill(*jumps, pitchRatio * speedRatio);
const auto events = resources.midiState.getPitchEvents();
const auto bendLambda = [this](float bend) {
const auto bendInCents = bend > 0.0f ? bend * static_cast<float>(region->bendUp) : -bend * static_cast<float>(region->bendDown);
return centsFactor(bendInCents);
};
if (region->bendStep > 1)
pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor);
else
pitchBendEnvelope(events, *bends, bendLambda);
applyGain<float>(*bends, *jumps);
for (const auto& mod : region->tuneCC) {
multiplicativeModifier(resources, *bends, mod, [](float x) { return centsFactor(x); });
applyGain<float>(*bends, *jumps);
}
pitchEnvelope(*jumps);
jumps->front() += floatPositionOffset;
cumsum<float>(*jumps, *jumps);
@ -584,30 +606,12 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
const auto numFrames = buffer.getNumFrames();
auto frequencies = resources.bufferPool.getBuffer(numFrames);
auto bends = resources.bufferPool.getBuffer(numFrames);
if (!frequencies || !bends)
if (!frequencies)
return;
float keycenterFrequency = midiNoteFrequency(region->pitchKeycenter);
fill(*frequencies, pitchRatio * keycenterFrequency);
const auto events = resources.midiState.getPitchEvents();
const auto bendLambda = [this](float bend) {
const auto bendInCents = bend > 0.0f ? bend * static_cast<float>(region->bendUp) : -bend * static_cast<float>(region->bendDown);
return centsFactor(bendInCents);
};
if (region->bendStep > 1)
pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor);
else
pitchBendEnvelope(events, *bends, bendLambda);
applyGain<float>(*bends, *frequencies);
for (const auto& mod : region->tuneCC) {
multiplicativeModifier(resources, *bends, mod, [](float x) {
return centsFactor(x);
});
applyGain<float>(*bends, *frequencies);
}
pitchEnvelope(*frequencies);
if (waveUnisonSize == 1) {
WavetableOscillator& osc = waveOscillators[0];
@ -784,7 +788,6 @@ void sfz::Voice::setupOscillatorUnison()
#endif
}
void sfz::Voice::updateChannelPowers(AudioSpan<float> buffer)
{
assert(smoothedChannelEnvelopes.size() == channelEnvelopeFilters.size());
@ -809,3 +812,58 @@ void sfz::Voice::switchState(State s)
stateListener->onVoiceStateChanged(id, s);
}
}
void sfz::Voice::prepareSmoothers(const ModifierArray<size_t>& numModifiers)
{
for (auto& mod : allModifiers)
modifierSmoothers[mod].resize(numModifiers[mod]);
}
void sfz::Voice::pitchEnvelope(absl::Span<float> pitchSpan) noexcept
{
const auto numFrames = pitchSpan.size();
auto bends = resources.bufferPool.getBuffer(numFrames);
if (!bends)
return;
const auto events = resources.midiState.getPitchEvents();
const auto bendLambda = [this](float bend) {
return centsFactor(region->getBendInCents(bend));
};
if (region->bendStep > 1)
pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor);
else
pitchBendEnvelope(events, *bends, bendLambda);
bendSmoother.process(*bends, *bends);
applyGain<float>(*bends, pitchSpan);
forEachWithSmoother(Mod::pitch, [&](const CCData<Modifier>& mod, Smoother& smoother) {
multiplicativeModifier(resources, *bends, mod, [](float x) {
return centsFactor(x);
});
smoother.process(*bends, *bends);
applyGain<float>(*bends, pitchSpan);
});
}
void sfz::Voice::resetSmoothers() noexcept
{
for (auto& mod : allModifiers) {
const auto resetValue = [mod] {
switch (mod) {
case Mod::volume: // fallthrough
case Mod::pitch:
return 1.0f;
default:
return 0.0f;
}
}();
for (auto& smoother : modifierSmoothers[mod]) {
smoother.reset(resetValue);
}
}
bendSmoother.reset(1.0f);
gainSmoother.reset(0.0f);
}

View file

@ -11,6 +11,7 @@
#include "Region.h"
#include "AudioBuffer.h"
#include "Resources.h"
#include "Smoothers.h"
#include "AudioSpan.h"
#include "LeakDetector.h"
#include "OnePoleFilter.h"
@ -301,6 +302,8 @@ public:
Duration getLastFilterDuration() const noexcept { return filterDuration; }
Duration getLastPanningDuration() const noexcept { return panningDuration; }
void prepareSmoothers(const ModifierArray<size_t>& numModifiers);
private:
/**
* @brief Fill a span with data from a file source. This is the first step
@ -330,19 +333,73 @@ private:
const AudioSpan<const float>& source, AudioSpan<float>& dest,
absl::Span<const int> indices, absl::Span<const float> coeffs);
/**
* @brief Compute the amplitude envelope, applied as a gain to a mono
* or stereo buffer
*
* @param modulationSpan
*/
void amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept;
/**
* @brief Amplitude stage for a mono source
*
* @param buffer
*/
void ampStageMono(AudioSpan<float> buffer) noexcept;
/**
* @brief Amplitude stage for a stereo source
*
* @param buffer
*/
void ampStageStereo(AudioSpan<float> buffer) noexcept;
/**
* @brief Amplitude stage for a mono source
*
* @param buffer
*/
void panStageMono(AudioSpan<float> buffer) noexcept;
void panStageStereo(AudioSpan<float> buffer) noexcept;
/**
* @brief Amplitude stage for a mono source
*
* @param buffer
*/
void filterStageMono(AudioSpan<float> buffer) noexcept;
void filterStageStereo(AudioSpan<float> buffer) noexcept;
/**
* @brief Compute the pitch envelope. This envelope is meant to multiply
* the frequency parameter for each sample (which translates to floating
* point intervals for sample-based voices, or phases for generators)
*
* @param pitchSpan
*/
void pitchEnvelope(absl::Span<float> pitchSpan) noexcept;
/**
* @brief Remove the voice from the sister ring
*
*/
void removeVoiceFromRing() noexcept;
/**
* @brief Helper function to iterate jointly on modifiers and smoothers
* for a given modulation target of type sfz::Mod
*
* @tparam F
* @param modId
* @param lambda
*/
template <class F>
void forEachWithSmoother(sfz::Mod modId, F&& lambda)
{
auto mod = region->modifiers[modId].begin();
auto smoother = modifierSmoothers[modId].begin();
while (mod < region->modifiers[modId].end()) {
lambda(*mod, *smoother);
incrementAll(mod, smoother);
}
}
/**
* @brief Initialize frequency and gain coefficients for the oscillators.
*/
@ -410,8 +467,15 @@ private:
std::normal_distribution<float> noiseDist { 0, config::noiseVariance };
ModifierArray<std::vector<Smoother>> modifierSmoothers;
Smoother gainSmoother;
Smoother bendSmoother;
void resetSmoothers() noexcept;
std::array<OnePoleFilter<float>, 2> channelEnvelopeFilters;
std::array<float, 2> smoothedChannelEnvelopes;
HistoricalBuffer<float> powerHistory { config::powerHistoryLength };
LEAK_DETECTOR(Voice);
};

View file

@ -16,8 +16,8 @@ set(SFIZZ_TEST_SOURCES
SIMDHelpersT.cpp
FilesT.cpp
MidiStateT.cpp
OnePoleFilterT.cpp
InterpolatorsT.cpp
SmoothersT.cpp
RegionActivationT.cpp
RegionValueComputationsT.cpp
# If we're tweaking the curves this kind of tests does not make sense
@ -53,6 +53,11 @@ if(JACK_FOUND AND TARGET Qt5::Widgets)
target_link_libraries(sfizz_demo_filters PRIVATE sfizz::sfizz Qt5::Widgets ${JACK_LIBRARIES})
set_target_properties(sfizz_demo_filters PROPERTIES AUTOUIC ON)
add_executable(sfizz_demo_smooth DemoSmooth.cpp)
target_include_directories(sfizz_demo_smooth PRIVATE ${JACK_INCLUDE_DIRS})
target_link_libraries(sfizz_demo_smooth PRIVATE sfizz::sfizz Qt5::Widgets ${JACK_LIBRARIES})
set_target_properties(sfizz_demo_smooth PROPERTIES AUTOUIC ON)
add_executable(sfizz_demo_stereo DemoStereo.cpp)
target_include_directories(sfizz_demo_stereo PRIVATE ${JACK_INCLUDE_DIRS})
target_link_libraries(sfizz_demo_stereo PRIVATE sfizz::sfizz Qt5::Widgets ${JACK_LIBRARIES})

155
tests/DemoSmooth.cpp Normal file
View file

@ -0,0 +1,155 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// 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
#include "sfizz/Smoothers.h"
#include "sfizz/MathHelpers.h"
#include "ui_DemoSmooth.h"
#include <QApplication>
#include <QMainWindow>
#include <QMessageBox>
#include <QButtonGroup>
#include <QDebug>
#include <jack/jack.h>
#include <memory>
#include <cmath>
///
struct jack_delete {
void operator()(jack_client_t* x) const noexcept { jack_client_close(x); }
};
typedef std::unique_ptr<jack_client_t, jack_delete> jack_client_u;
///
class DemoApp : public QApplication {
public:
DemoApp(int& argc, char** argv);
bool initSound();
void initWindow();
private:
static int processAudio(jack_nframes_t nframes, void* cbdata);
private:
void valueChangedSmooth(int value);
private:
QMainWindow* fWindow = nullptr;
Ui::DemoSmoothWindow fUi;
static constexpr int smoothMin = 0;
static constexpr int smoothMax = 100;
int fSmooth = 0;
sfz::Smoother fFilter;
jack_client_u fClient;
jack_port_t* fPorts[2] = {};
};
DemoApp::DemoApp(int& argc, char** argv)
: QApplication(argc, argv)
{
setApplicationName(tr("Sfizz Smooth"));
}
bool DemoApp::initSound()
{
jack_client_t* client = jack_client_open(
applicationName().toUtf8().data(), JackNoStartServer, nullptr);
if (!client) {
QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open JACK audio."));
return false;
}
fClient.reset(client);
double sampleRate = jack_get_sample_rate(client);
fFilter.setSmoothing(fSmooth, sampleRate);
fPorts[0] = jack_port_register(client, "in", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
fPorts[1] = jack_port_register(client, "out", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
if (!(fPorts[0] && fPorts[1])) {
QMessageBox::critical(nullptr, tr("Error"), tr("Cannot register JACK ports."));
return false;
}
jack_set_process_callback(client, &processAudio, this);
if (jack_activate(client) != 0) {
QMessageBox::critical(nullptr, tr("Error"), tr("Cannot activate JACK client."));
return false;
}
return true;
}
void DemoApp::initWindow()
{
QMainWindow* window = new QMainWindow;
fWindow = window;
fUi.setupUi(window);
window->setWindowTitle(applicationDisplayName());
fUi.dialSmooth->setRange(smoothMin, smoothMax);
fUi.spinSmooth->setRange(smoothMin, smoothMax);
fUi.dialSmooth->setValue(fSmooth);
fUi.spinSmooth->setValue(fSmooth);
connect(
fUi.dialSmooth, &QDial::valueChanged,
this, [this](int value) { valueChangedSmooth(value); });
connect(
fUi.spinSmooth, QOverload<int>::of(&QSpinBox::valueChanged),
this, [this](int value) { valueChangedSmooth(value); });
window->adjustSize();
window->setFixedSize(window->size());
window->show();
}
int DemoApp::processAudio(jack_nframes_t nframes, void* cbdata)
{
DemoApp* self = reinterpret_cast<DemoApp*>(cbdata);
const float* in = reinterpret_cast<float*>(jack_port_get_buffer(self->fPorts[0], nframes));
float* out = reinterpret_cast<float*>(jack_port_get_buffer(self->fPorts[1], nframes));
self->fFilter.process(absl::MakeSpan(in, nframes), absl::MakeSpan(out, nframes));
return 0;
}
void DemoApp::valueChangedSmooth(int value)
{
fUi.dialSmooth->blockSignals(true);
fUi.dialSmooth->setValue(value);
fUi.dialSmooth->blockSignals(false);
fUi.spinSmooth->blockSignals(true);
fUi.spinSmooth->setValue(value);
fUi.spinSmooth->blockSignals(false);
fSmooth = value;
double sampleRate = jack_get_sample_rate(fClient.get());
fFilter.setSmoothing(fSmooth, sampleRate);
}
int main(int argc, char* argv[])
{
DemoApp app(argc, argv);
if (!app.initSound())
return 1;
app.initWindow();
return app.exec();
}

40
tests/DemoSmooth.ui Normal file
View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DemoSmoothWindow</class>
<widget class="QMainWindow" name="DemoSmoothWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>118</width>
<height>169</height>
</rect>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0">
<widget class="QSpinBox" name="spinSmooth">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QDial" name="dialSmooth"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Smooth</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -13,10 +13,11 @@
#endif
using namespace Catch::literals;
using namespace sfz::literals;
using namespace sfz;
TEST_CASE("[Files] Single region (regions_one.sfz)")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_one.sfz");
REQUIRE(synth.getNumRegions() == 1);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "dummy.wav");
@ -25,7 +26,7 @@ TEST_CASE("[Files] Single region (regions_one.sfz)")
TEST_CASE("[Files] Multiple regions (regions_many.sfz)")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_many.sfz");
REQUIRE(synth.getNumRegions() == 3);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "dummy.wav");
@ -35,15 +36,15 @@ TEST_CASE("[Files] Multiple regions (regions_many.sfz)")
TEST_CASE("[Files] Basic opcodes (regions_opcodes.sfz)")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_opcodes.sfz");
REQUIRE(synth.getNumRegions() == 1);
REQUIRE(synth.getRegionView(0)->keyRange == sfz::Range<uint8_t>(2, 14));
REQUIRE(synth.getRegionView(0)->keyRange == Range<uint8_t>(2, 14));
}
TEST_CASE("[Files] Underscore opcodes (underscore_opcodes.sfz)")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/underscore_opcodes.sfz");
REQUIRE(synth.getNumRegions() == 1);
REQUIRE(synth.getRegionView(0)->loopMode == SfzLoopMode::loop_sustain);
@ -51,7 +52,7 @@ TEST_CASE("[Files] Underscore opcodes (underscore_opcodes.sfz)")
TEST_CASE("[Files] (regions_bad.sfz)")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_bad.sfz");
REQUIRE(synth.getNumRegions() == 2);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "dummy.wav");
@ -60,7 +61,7 @@ TEST_CASE("[Files] (regions_bad.sfz)")
TEST_CASE("[Files] Local include")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_local.sfz");
REQUIRE(synth.getNumRegions() == 1);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "dummy.wav");
@ -68,7 +69,7 @@ TEST_CASE("[Files] Local include")
TEST_CASE("[Files] Multiple includes")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/multiple_includes.sfz");
REQUIRE(synth.getNumRegions() == 2);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "dummy.wav");
@ -77,7 +78,7 @@ TEST_CASE("[Files] Multiple includes")
TEST_CASE("[Files] Multiple includes with comments")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/multiple_includes_with_comments.sfz");
REQUIRE(synth.getNumRegions() == 2);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "dummy.wav");
@ -86,7 +87,7 @@ TEST_CASE("[Files] Multiple includes with comments")
TEST_CASE("[Files] Subdir include")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_subdir.sfz");
REQUIRE(synth.getNumRegions() == 1);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "dummy_subdir.wav");
@ -94,7 +95,7 @@ TEST_CASE("[Files] Subdir include")
TEST_CASE("[Files] Subdir include Win")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_subdir_win.sfz");
REQUIRE(synth.getNumRegions() == 1);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "dummy_subdir.wav");
@ -102,8 +103,8 @@ TEST_CASE("[Files] Subdir include Win")
TEST_CASE("[Files] Recursive include (with include guard)")
{
sfz::Synth synth;
sfz::Parser& parser = synth.getParser();
Synth synth;
Parser& parser = synth.getParser();
parser.setRecursiveIncludeGuardEnabled(true);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_recursive.sfz");
REQUIRE(synth.getNumRegions() == 2);
@ -113,8 +114,8 @@ TEST_CASE("[Files] Recursive include (with include guard)")
TEST_CASE("[Files] Include loops (with include guard)")
{
sfz::Synth synth;
sfz::Parser& parser = synth.getParser();
Synth synth;
Parser& parser = synth.getParser();
parser.setRecursiveIncludeGuardEnabled(true);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Includes/root_loop.sfz");
REQUIRE(synth.getNumRegions() == 2);
@ -124,34 +125,34 @@ TEST_CASE("[Files] Include loops (with include guard)")
TEST_CASE("[Files] Define test")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/defines.sfz");
REQUIRE(synth.getNumRegions() == 4);
REQUIRE(synth.getRegionView(0)->keyRange == sfz::Range<uint8_t>(36, 36));
REQUIRE(synth.getRegionView(1)->keyRange == sfz::Range<uint8_t>(38, 38));
REQUIRE(synth.getRegionView(2)->keyRange == sfz::Range<uint8_t>(42, 42));
REQUIRE(synth.getRegionView(0)->keyRange == Range<uint8_t>(36, 36));
REQUIRE(synth.getRegionView(1)->keyRange == Range<uint8_t>(38, 38));
REQUIRE(synth.getRegionView(2)->keyRange == Range<uint8_t>(42, 42));
REQUIRE(synth.getRegionView(3)->volume == -12.0f);
}
TEST_CASE("[Files] Group from AVL")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz");
REQUIRE(synth.getNumRegions() == 5);
for (int i = 0; i < synth.getNumRegions(); ++i) {
REQUIRE(synth.getRegionView(i)->volume == 6.0f);
REQUIRE(synth.getRegionView(i)->keyRange == sfz::Range<uint8_t>(36, 36));
REQUIRE(synth.getRegionView(i)->keyRange == Range<uint8_t>(36, 36));
}
REQUIRE(synth.getRegionView(0)->velocityRange == sfz::Range<float>(1_norm, 26_norm));
REQUIRE(synth.getRegionView(1)->velocityRange == sfz::Range<float>(27_norm, 52_norm));
REQUIRE(synth.getRegionView(2)->velocityRange == sfz::Range<float>(53_norm, 77_norm));
REQUIRE(synth.getRegionView(3)->velocityRange == sfz::Range<float>(78_norm, 102_norm));
REQUIRE(synth.getRegionView(4)->velocityRange == sfz::Range<float>(103_norm, 127_norm));
REQUIRE(synth.getRegionView(0)->velocityRange == Range<float>(1_norm, 26_norm));
REQUIRE(synth.getRegionView(1)->velocityRange == Range<float>(27_norm, 52_norm));
REQUIRE(synth.getRegionView(2)->velocityRange == Range<float>(53_norm, 77_norm));
REQUIRE(synth.getRegionView(3)->velocityRange == Range<float>(78_norm, 102_norm));
REQUIRE(synth.getRegionView(4)->velocityRange == Range<float>(103_norm, 127_norm));
}
TEST_CASE("[Files] Full hierarchy")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
REQUIRE(synth.getNumRegions() == 8);
for (int i = 0; i < synth.getNumRegions(); ++i) {
@ -159,40 +160,40 @@ TEST_CASE("[Files] Full hierarchy")
}
REQUIRE(synth.getRegionView(0)->pan == 0.3_a);
REQUIRE(synth.getRegionView(0)->delay == 67);
REQUIRE(synth.getRegionView(0)->keyRange == sfz::Range<uint8_t>(60, 60));
REQUIRE(synth.getRegionView(0)->keyRange == Range<uint8_t>(60, 60));
REQUIRE(synth.getRegionView(1)->pan == 0.3_a);
REQUIRE(synth.getRegionView(1)->delay == 67);
REQUIRE(synth.getRegionView(1)->keyRange == sfz::Range<uint8_t>(61, 61));
REQUIRE(synth.getRegionView(1)->keyRange == Range<uint8_t>(61, 61));
REQUIRE(synth.getRegionView(2)->pan == 0.3_a);
REQUIRE(synth.getRegionView(2)->delay == 56);
REQUIRE(synth.getRegionView(2)->keyRange == sfz::Range<uint8_t>(50, 50));
REQUIRE(synth.getRegionView(2)->keyRange == Range<uint8_t>(50, 50));
REQUIRE(synth.getRegionView(3)->pan == 0.3_a);
REQUIRE(synth.getRegionView(3)->delay == 56);
REQUIRE(synth.getRegionView(3)->keyRange == sfz::Range<uint8_t>(51, 51));
REQUIRE(synth.getRegionView(3)->keyRange == Range<uint8_t>(51, 51));
REQUIRE(synth.getRegionView(4)->pan == -0.1_a);
REQUIRE(synth.getRegionView(4)->delay == 47);
REQUIRE(synth.getRegionView(4)->keyRange == sfz::Range<uint8_t>(40, 40));
REQUIRE(synth.getRegionView(4)->keyRange == Range<uint8_t>(40, 40));
REQUIRE(synth.getRegionView(5)->pan == -0.1_a);
REQUIRE(synth.getRegionView(5)->delay == 47);
REQUIRE(synth.getRegionView(5)->keyRange == sfz::Range<uint8_t>(41, 41));
REQUIRE(synth.getRegionView(5)->keyRange == Range<uint8_t>(41, 41));
REQUIRE(synth.getRegionView(6)->pan == -0.1_a);
REQUIRE(synth.getRegionView(6)->delay == 36);
REQUIRE(synth.getRegionView(6)->keyRange == sfz::Range<uint8_t>(30, 30));
REQUIRE(synth.getRegionView(6)->keyRange == Range<uint8_t>(30, 30));
REQUIRE(synth.getRegionView(7)->pan == -0.1_a);
REQUIRE(synth.getRegionView(7)->delay == 36);
REQUIRE(synth.getRegionView(7)->keyRange == sfz::Range<uint8_t>(31, 31));
REQUIRE(synth.getRegionView(7)->keyRange == Range<uint8_t>(31, 31));
}
TEST_CASE("[Files] Reloading files")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
REQUIRE(synth.getNumRegions() == 8);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
@ -202,7 +203,7 @@ TEST_CASE("[Files] Reloading files")
TEST_CASE("[Files] Full hierarchy with antislashes")
{
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
REQUIRE(synth.getNumRegions() == 8);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "Regions/dummy.wav");
@ -216,7 +217,7 @@ TEST_CASE("[Files] Full hierarchy with antislashes")
}
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy_antislash.sfz");
REQUIRE(synth.getNumRegions() == 8);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "Regions/dummy.wav");
@ -232,19 +233,19 @@ TEST_CASE("[Files] Full hierarchy with antislashes")
TEST_CASE("[Files] Pizz basic")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/SpecificBugs/MeatBassPizz/Programs/pizz.sfz");
REQUIRE(synth.getNumRegions() == 4);
for (int i = 0; i < synth.getNumRegions(); ++i) {
REQUIRE(synth.getRegionView(i)->keyRange == sfz::Range<uint8_t>(12, 22));
REQUIRE(synth.getRegionView(i)->velocityRange == sfz::Range<float>(97_norm, 127_norm));
REQUIRE(synth.getRegionView(i)->keyRange == Range<uint8_t>(12, 22));
REQUIRE(synth.getRegionView(i)->velocityRange == Range<float>(97_norm, 127_norm));
REQUIRE(synth.getRegionView(i)->pitchKeycenter == 21);
REQUIRE(synth.getRegionView(i)->ccConditions.getWithDefault(107) == sfz::Range<float>(0_norm, 13_norm));
REQUIRE(synth.getRegionView(i)->ccConditions.getWithDefault(107) == Range<float>(0_norm, 13_norm));
}
REQUIRE(synth.getRegionView(0)->randRange == sfz::Range<float>(0, 0.25));
REQUIRE(synth.getRegionView(1)->randRange == sfz::Range<float>(0.25, 0.5));
REQUIRE(synth.getRegionView(2)->randRange == sfz::Range<float>(0.5, 0.75));
REQUIRE(synth.getRegionView(3)->randRange == sfz::Range<float>(0.75, 1.0));
REQUIRE(synth.getRegionView(0)->randRange == Range<float>(0, 0.25));
REQUIRE(synth.getRegionView(1)->randRange == Range<float>(0.25, 0.5));
REQUIRE(synth.getRegionView(2)->randRange == Range<float>(0.5, 0.75));
REQUIRE(synth.getRegionView(3)->randRange == Range<float>(0.75, 1.0));
REQUIRE(synth.getRegionView(0)->sampleId.filename() == R"(../Samples/pizz/a0_vl4_rr1.wav)");
REQUIRE(synth.getRegionView(1)->sampleId.filename() == R"(../Samples/pizz/a0_vl4_rr2.wav)");
REQUIRE(synth.getRegionView(2)->sampleId.filename() == R"(../Samples/pizz/a0_vl4_rr3.wav)");
@ -253,7 +254,7 @@ TEST_CASE("[Files] Pizz basic")
TEST_CASE("[Files] Channels (channels.sfz)")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels.sfz");
REQUIRE(synth.getNumRegions() == 2);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "mono_sample.wav");
@ -264,7 +265,7 @@ TEST_CASE("[Files] Channels (channels.sfz)")
TEST_CASE("[Files] Channels (channels_multi.sfz)")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels_multi.sfz");
REQUIRE(synth.getNumRegions() == 6);
@ -301,7 +302,7 @@ TEST_CASE("[Files] Channels (channels_multi.sfz)")
TEST_CASE("[Files] sw_default")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/sw_default.sfz");
REQUIRE( synth.getNumRegions() == 4 );
REQUIRE( !synth.getRegionView(0)->isSwitchedOn() );
@ -312,7 +313,7 @@ TEST_CASE("[Files] sw_default")
TEST_CASE("[Files] sw_default and playing with switches")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/sw_default.sfz");
REQUIRE( synth.getNumRegions() == 4 );
REQUIRE( !synth.getRegionView(0)->isSwitchedOn() );
@ -341,7 +342,7 @@ TEST_CASE("[Files] sw_default and playing with switches")
TEST_CASE("[Files] wrong (overlapping) replacement for defines")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/SpecificBugs/wrong-replacements.sfz");
REQUIRE( synth.getNumRegions() == 3 );
@ -355,14 +356,14 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines")
REQUIRE( synth.getRegionView(1)->keyRange.getStart() == 57 );
REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 );
REQUIRE(!synth.getRegionView(2)->amplitudeCC.empty());
REQUIRE(synth.getRegionView(2)->amplitudeCC.contains(10));
REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10).value == 34.0f);
REQUIRE(!synth.getRegionView(2)->modifiers[Mod::amplitude].empty());
REQUIRE(synth.getRegionView(2)->modifiers[Mod::amplitude].contains(10));
REQUIRE(synth.getRegionView(2)->modifiers[Mod::amplitude].getWithDefault(10).value == 34.0f);
}
TEST_CASE("[Files] Specific bug: relative path with backslashes")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/SpecificBugs/win_backslashes.sfz");
REQUIRE(synth.getNumRegions() == 1);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == R"(Xylo/Subfolder/closedhat.wav)");
@ -370,7 +371,7 @@ TEST_CASE("[Files] Specific bug: relative path with backslashes")
TEST_CASE("[Files] Default path")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path.sfz");
REQUIRE(synth.getNumRegions() == 4);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == R"(DefaultPath/SubPath1/sample1.wav)");
@ -381,7 +382,7 @@ TEST_CASE("[Files] Default path")
TEST_CASE("[Files] Default path reset when calling loadSfzFile again")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path.sfz");
REQUIRE(synth.getNumRegions() == 4);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path_reset.sfz");
@ -391,7 +392,7 @@ TEST_CASE("[Files] Default path reset when calling loadSfzFile again")
TEST_CASE("[Files] Default path is ignored for generators")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/default_path_generator.sfz");
REQUIRE(synth.getNumRegions() == 1);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == R"(*sine)");
@ -399,7 +400,7 @@ TEST_CASE("[Files] Default path is ignored for generators")
TEST_CASE("[Files] Set CC applies properly")
{
sfz::Synth synth;
Synth synth;
const auto& midiState = synth.getResources().midiState;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/set_cc.sfz");
REQUIRE(midiState.getCCValue(142) == 63_norm);
@ -426,26 +427,26 @@ TEST_CASE("[Files] Set RealCC applies properly")
TEST_CASE("[Files] Note and octave offsets")
{
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/note_offset.sfz");
REQUIRE( synth.getNumRegions() == 7 );
REQUIRE( synth.getRegionView(0)->keyRange == sfz::Range<uint8_t>(64, 64) );
REQUIRE(synth.getRegionView(0)->keyRange == Range<uint8_t>(64, 64));
REQUIRE( synth.getRegionView(0)->pitchKeycenter == 64 );
REQUIRE( synth.getRegionView(0)->keyswitchRange == sfz::Default::keyRange );
REQUIRE( synth.getRegionView(0)->crossfadeKeyInRange == sfz::Default::crossfadeKeyInRange );
REQUIRE( synth.getRegionView(0)->crossfadeKeyOutRange == sfz::Default::crossfadeKeyOutRange );
REQUIRE(synth.getRegionView(0)->keyswitchRange == Default::keyRange);
REQUIRE(synth.getRegionView(0)->crossfadeKeyInRange == Default::crossfadeKeyInRange);
REQUIRE(synth.getRegionView(0)->crossfadeKeyOutRange == Default::crossfadeKeyOutRange);
REQUIRE( synth.getRegionView(1)->keyRange == sfz::Range<uint8_t>(51, 56) );
REQUIRE(synth.getRegionView(1)->keyRange == Range<uint8_t>(51, 56));
REQUIRE( synth.getRegionView(1)->pitchKeycenter == 51 );
REQUIRE( synth.getRegionView(2)->keyRange == sfz::Range<uint8_t>(41, 45) );
REQUIRE(synth.getRegionView(2)->keyRange == Range<uint8_t>(41, 45));
REQUIRE( synth.getRegionView(2)->pitchKeycenter == 41 );
REQUIRE( synth.getRegionView(2)->crossfadeKeyInRange == sfz::Range<uint8_t>(37, 41) );
REQUIRE( synth.getRegionView(2)->crossfadeKeyOutRange == sfz::Range<uint8_t>(45, 49) );
REQUIRE(synth.getRegionView(2)->crossfadeKeyInRange == Range<uint8_t>(37, 41));
REQUIRE(synth.getRegionView(2)->crossfadeKeyOutRange == Range<uint8_t>(45, 49));
REQUIRE( synth.getRegionView(3)->keyRange == sfz::Range<uint8_t>(62, 62) );
REQUIRE( synth.getRegionView(3)->keyswitchRange == sfz::Range<uint8_t>(23, 27) );
REQUIRE(synth.getRegionView(3)->keyRange == Range<uint8_t>(62, 62));
REQUIRE(synth.getRegionView(3)->keyswitchRange == Range<uint8_t>(23, 27));
REQUIRE( synth.getRegionView(3)->keyswitch );
REQUIRE( *synth.getRegionView(3)->keyswitch == 24 );
REQUIRE( synth.getRegionView(3)->keyswitchUp );
@ -455,21 +456,21 @@ TEST_CASE("[Files] Note and octave offsets")
REQUIRE( synth.getRegionView(3)->previousNote );
REQUIRE( *synth.getRegionView(3)->previousNote == 61 );
REQUIRE( synth.getRegionView(4)->keyRange == sfz::Range<uint8_t>(76, 76) );
REQUIRE(synth.getRegionView(4)->keyRange == Range<uint8_t>(76, 76));
REQUIRE( synth.getRegionView(4)->pitchKeycenter == 76 );
REQUIRE( synth.getRegionView(5)->keyRange == sfz::Range<uint8_t>(50, 50) );
REQUIRE(synth.getRegionView(5)->keyRange == Range<uint8_t>(50, 50));
REQUIRE( synth.getRegionView(5)->pitchKeycenter == 50 );
REQUIRE( synth.getRegionView(6)->keyRange == sfz::Range<uint8_t>(50, 50) );
REQUIRE(synth.getRegionView(6)->keyRange == Range<uint8_t>(50, 50));
REQUIRE( synth.getRegionView(6)->pitchKeycenter == 50 );
}
TEST_CASE("[Files] Off by with different delays")
{
sfz::Synth synth;
Synth synth;
synth.setSamplesPerBlock(256);
sfz::AudioBuffer<float> buffer(2, 256);
AudioBuffer<float> buffer(2, 256);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/off_by.sfz");
REQUIRE( synth.getNumRegions() == 4 );
synth.noteOn(0, 63, 63);
@ -484,7 +485,7 @@ TEST_CASE("[Files] Off by with different delays")
TEST_CASE("[Files] Off by with the same delays")
{
sfz::Synth synth;
Synth synth;
synth.setSamplesPerBlock(256);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/off_by.sfz");
REQUIRE( synth.getNumRegions() == 4 );
@ -499,7 +500,7 @@ TEST_CASE("[Files] Off by with the same delays")
TEST_CASE("[Files] Off by with the same notes at the same time")
{
sfz::Synth synth;
Synth synth;
synth.setSamplesPerBlock(256);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/off_by.sfz");
REQUIRE( synth.getNumRegions() == 4 );
@ -507,7 +508,7 @@ TEST_CASE("[Files] Off by with the same notes at the same time")
REQUIRE( synth.getNumActiveVoices() == 2 );
synth.noteOn(0, 65, 63);
REQUIRE( synth.getNumActiveVoices() == 4 );
sfz::AudioBuffer<float> buffer { 2, 256 };
AudioBuffer<float> buffer { 2, 256 };
synth.renderBlock(buffer);
synth.noteOn(0, 65, 63);
synth.renderBlock(buffer);
@ -516,7 +517,7 @@ TEST_CASE("[Files] Off by with the same notes at the same time")
TEST_CASE("[Files] Off modes")
{
sfz::Synth synth;
Synth synth;
synth.setSamplesPerBlock(256);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/off_mode.sfz");
REQUIRE( synth.getNumRegions() == 3 );
@ -532,7 +533,7 @@ TEST_CASE("[Files] Off modes")
synth.getVoiceView(0) ;
synth.noteOn(100, 63, 63);
REQUIRE( synth.getNumActiveVoices() == 3 );
sfz::AudioBuffer<float> buffer { 2, 256 };
AudioBuffer<float> buffer { 2, 256 };
synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 2 );
REQUIRE( fastVoice->isFree() );
@ -541,7 +542,7 @@ TEST_CASE("[Files] Off modes")
TEST_CASE("[Files] Looped regions taken from files and possibly overriden")
{
sfz::Synth synth;
Synth synth;
synth.setSamplesPerBlock(256);
synth.setSampleRate(44100);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/looped_regions.sfz");
@ -550,9 +551,9 @@ TEST_CASE("[Files] Looped regions taken from files and possibly overriden")
REQUIRE( synth.getRegionView(1)->loopMode == SfzLoopMode::no_loop );
REQUIRE( synth.getRegionView(2)->loopMode == SfzLoopMode::loop_continuous );
REQUIRE( synth.getRegionView(0)->loopRange == sfz::Range<uint32_t>{ 77554, 186581 } );
REQUIRE( synth.getRegionView(1)->loopRange == sfz::Range<uint32_t>{ 77554, 186581 } );
REQUIRE( synth.getRegionView(2)->loopRange == sfz::Range<uint32_t>{ 4, 124 } );
REQUIRE(synth.getRegionView(0)->loopRange == Range<uint32_t> { 77554, 186581 });
REQUIRE(synth.getRegionView(1)->loopRange == Range<uint32_t> { 77554, 186581 });
REQUIRE(synth.getRegionView(2)->loopRange == Range<uint32_t> { 4, 124 });
}
TEST_CASE("[Files] Case sentitiveness")
@ -568,7 +569,7 @@ TEST_CASE("[Files] Case sentitiveness")
#endif
if (caseSensitiveFs) {
sfz::Synth synth;
Synth synth;
synth.loadSfzFile(sfzFilePath);
REQUIRE(synth.getNumRegions() == 4);
REQUIRE(synth.getRegionView(0)->sampleId.filename() == "dummy1.wav");
@ -580,8 +581,8 @@ TEST_CASE("[Files] Case sentitiveness")
TEST_CASE("[Files] Empty file")
{
sfz::Synth synth;
sfz::Parser& parser = synth.getParser();
Synth synth;
Parser& parser = synth.getParser();
REQUIRE(!synth.loadSfzFile(""));
REQUIRE(parser.getIncludedFiles().empty());
REQUIRE(!synth.loadSfzFile({}));

View file

@ -10,11 +10,12 @@
#include "catch2/catch.hpp"
using namespace Catch::literals;
using namespace sfz::literals;
using namespace sfz;
TEST_CASE("[Region] Parsing opcodes")
{
sfz::MidiState midiState;
sfz::Region region { 0, midiState };
MidiState midiState;
Region region { 0, midiState };
SECTION("sample")
{
@ -132,36 +133,36 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("loop_end")
{
REQUIRE(region.loopRange == sfz::Range<uint32_t>(0, 4294967295));
REQUIRE(region.loopRange == Range<uint32_t>(0, 4294967295));
region.parseOpcode({ "loop_end", "184" });
REQUIRE(region.loopRange == sfz::Range<uint32_t>(0, 184));
REQUIRE(region.loopRange == Range<uint32_t>(0, 184));
region.parseOpcode({ "loop_end", "-1" });
REQUIRE(region.loopRange == sfz::Range<uint32_t>(0, 0));
REQUIRE(region.loopRange == Range<uint32_t>(0, 0));
}
SECTION("loop_start")
{
region.parseOpcode({ "loop_start", "184" });
REQUIRE(region.loopRange == sfz::Range<uint32_t>(184, 4294967295));
REQUIRE(region.loopRange == Range<uint32_t>(184, 4294967295));
region.parseOpcode({ "loop_start", "-1" });
REQUIRE(region.loopRange == sfz::Range<uint32_t>(0, 4294967295));
REQUIRE(region.loopRange == Range<uint32_t>(0, 4294967295));
}
SECTION("loopend")
{
REQUIRE(region.loopRange == sfz::Range<uint32_t>(0, 4294967295));
REQUIRE(region.loopRange == Range<uint32_t>(0, 4294967295));
region.parseOpcode({ "loopend", "184" });
REQUIRE(region.loopRange == sfz::Range<uint32_t>(0, 184));
REQUIRE(region.loopRange == Range<uint32_t>(0, 184));
region.parseOpcode({ "loopend", "-1" });
REQUIRE(region.loopRange == sfz::Range<uint32_t>(0, 0));
REQUIRE(region.loopRange == Range<uint32_t>(0, 0));
}
SECTION("loopstart")
{
region.parseOpcode({ "loopstart", "184" });
REQUIRE(region.loopRange == sfz::Range<uint32_t>(184, 4294967295));
REQUIRE(region.loopRange == Range<uint32_t>(184, 4294967295));
region.parseOpcode({ "loopstart", "-1" });
REQUIRE(region.loopRange == sfz::Range<uint32_t>(0, 4294967295));
REQUIRE(region.loopRange == Range<uint32_t>(0, 4294967295));
}
SECTION("group")
@ -194,87 +195,87 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("lokey, hikey, and key")
{
REQUIRE(region.keyRange == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.keyRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "lokey", "37" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(37, 127));
REQUIRE(region.keyRange == Range<uint8_t>(37, 127));
region.parseOpcode({ "lokey", "c4" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(60, 127));
REQUIRE(region.keyRange == Range<uint8_t>(60, 127));
region.parseOpcode({ "lokey", "128" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(127, 127));
REQUIRE(region.keyRange == Range<uint8_t>(127, 127));
region.parseOpcode({ "lokey", "-3" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.keyRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "hikey", "65" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(0, 65));
REQUIRE(region.keyRange == Range<uint8_t>(0, 65));
region.parseOpcode({ "hikey", "c4" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(0, 60));
REQUIRE(region.keyRange == Range<uint8_t>(0, 60));
region.parseOpcode({ "hikey", "-1" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.keyRange == Range<uint8_t>(0, 0));
region.parseOpcode({ "hikey", "128" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.keyRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "key", "26" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(26, 26));
REQUIRE(region.keyRange == Range<uint8_t>(26, 26));
REQUIRE(region.pitchKeycenter == 26);
region.parseOpcode({ "key", "-26" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.keyRange == Range<uint8_t>(0, 0));
REQUIRE(region.pitchKeycenter == 0);
region.parseOpcode({ "key", "234" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(127, 127));
REQUIRE(region.keyRange == Range<uint8_t>(127, 127));
REQUIRE(region.pitchKeycenter == 127);
region.parseOpcode({ "key", "c4" });
REQUIRE(region.keyRange == sfz::Range<uint8_t>(60, 60));
REQUIRE(region.keyRange == Range<uint8_t>(60, 60));
REQUIRE(region.pitchKeycenter == 60);
}
SECTION("lovel, hivel")
{
REQUIRE(region.velocityRange == sfz::Range<float>(0_norm, 127_norm));
REQUIRE(region.velocityRange == Range<float>(0_norm, 127_norm));
region.parseOpcode({ "lovel", "37" });
REQUIRE(region.velocityRange == sfz::Range<float>(37_norm, 127_norm));
REQUIRE(region.velocityRange == Range<float>(37_norm, 127_norm));
region.parseOpcode({ "lovel", "128" });
REQUIRE(region.velocityRange == sfz::Range<float>(127_norm, 127_norm));
REQUIRE(region.velocityRange == Range<float>(127_norm, 127_norm));
region.parseOpcode({ "lovel", "-3" });
REQUIRE(region.velocityRange == sfz::Range<float>(0_norm, 127_norm));
REQUIRE(region.velocityRange == Range<float>(0_norm, 127_norm));
region.parseOpcode({ "hivel", "65" });
REQUIRE(region.velocityRange == sfz::Range<float>(0_norm, 65_norm));
REQUIRE(region.velocityRange == Range<float>(0_norm, 65_norm));
region.parseOpcode({ "hivel", "-1" });
REQUIRE(region.velocityRange == sfz::Range<float>(0_norm, 0_norm));
REQUIRE(region.velocityRange == Range<float>(0_norm, 0_norm));
region.parseOpcode({ "hivel", "128" });
REQUIRE(region.velocityRange == sfz::Range<float>(0_norm, 127_norm));
REQUIRE(region.velocityRange == Range<float>(0_norm, 127_norm));
}
SECTION("lobend, hibend")
{
REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
REQUIRE(region.bendRange == Range<float>(-1.0f, 1.0f));
region.parseOpcode({ "lobend", "400" });
REQUIRE(region.bendRange.getStart() == Approx(sfz::normalizeBend(400)));
REQUIRE(region.bendRange.getStart() == Approx(normalizeBend(400)));
REQUIRE(region.bendRange.getEnd() == 1.0_a);
region.parseOpcode({ "lobend", "-128" });
REQUIRE(region.bendRange.getStart() == Approx(sfz::normalizeBend(-128)));
REQUIRE(region.bendRange.getStart() == Approx(normalizeBend(-128)));
REQUIRE(region.bendRange.getEnd() == 1.0_a);
region.parseOpcode({ "lobend", "-10000" });
REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
REQUIRE(region.bendRange == Range<float>(-1.0f, 1.0f));
region.parseOpcode({ "hibend", "13" });
REQUIRE(region.bendRange.getStart() == -1.0_a);
REQUIRE(region.bendRange.getEnd() == Approx(sfz::normalizeBend(13)));
REQUIRE(region.bendRange.getEnd() == Approx(normalizeBend(13)));
region.parseOpcode({ "hibend", "-1" });
REQUIRE(region.bendRange.getStart() == -1.0_a);
REQUIRE(region.bendRange.getEnd() == Approx(sfz::normalizeBend(-1)));
REQUIRE(region.bendRange.getEnd() == Approx(normalizeBend(-1)));
region.parseOpcode({ "hibend", "10000" });
REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
REQUIRE(region.bendRange == Range<float>(-1.0f, 1.0f));
}
SECTION("locc, hicc")
{
REQUIRE(region.ccConditions.getWithDefault(0) == sfz::Range<float>(0_norm, 127_norm));
REQUIRE(region.ccConditions[127] == sfz::Range<float>(0_norm, 127_norm));
REQUIRE(region.ccConditions.getWithDefault(0) == Range<float>(0_norm, 127_norm));
REQUIRE(region.ccConditions[127] == Range<float>(0_norm, 127_norm));
region.parseOpcode({ "locc6", "4" });
REQUIRE(region.ccConditions[6] == sfz::Range<float>(4_norm, 127_norm));
REQUIRE(region.ccConditions[6] == Range<float>(4_norm, 127_norm));
region.parseOpcode({ "locc12", "-128" });
REQUIRE(region.ccConditions[12] == sfz::Range<float>(0_norm, 127_norm));
REQUIRE(region.ccConditions[12] == Range<float>(0_norm, 127_norm));
region.parseOpcode({ "hicc65", "39" });
REQUIRE(region.ccConditions[65] == sfz::Range<float>(0_norm, 39_norm));
REQUIRE(region.ccConditions[65] == Range<float>(0_norm, 39_norm));
region.parseOpcode({ "hicc127", "135" });
REQUIRE(region.ccConditions[127] == sfz::Range<float>(0_norm, 127_norm));
REQUIRE(region.ccConditions[127] == Range<float>(0_norm, 127_norm));
}
SECTION("lohdcc, hihdcc")
@ -307,19 +308,19 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("sw_lokey, sw_hikey")
{
REQUIRE(region.keyswitchRange == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.keyswitchRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "sw_lokey", "4" });
REQUIRE(region.keyswitchRange == sfz::Range<uint8_t>(4, 127));
REQUIRE(region.keyswitchRange == Range<uint8_t>(4, 127));
region.parseOpcode({ "sw_lokey", "128" });
REQUIRE(region.keyswitchRange == sfz::Range<uint8_t>(127, 127));
REQUIRE(region.keyswitchRange == Range<uint8_t>(127, 127));
region.parseOpcode({ "sw_lokey", "0" });
REQUIRE(region.keyswitchRange == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.keyswitchRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "sw_hikey", "39" });
REQUIRE(region.keyswitchRange == sfz::Range<uint8_t>(0, 39));
REQUIRE(region.keyswitchRange == Range<uint8_t>(0, 39));
region.parseOpcode({ "sw_hikey", "135" });
REQUIRE(region.keyswitchRange == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.keyswitchRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "sw_hikey", "-1" });
REQUIRE(region.keyswitchRange == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.keyswitchRange == Range<uint8_t>(0, 0));
}
SECTION("sw_label")
@ -398,53 +399,53 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("lochanaft, hichanaft")
{
REQUIRE(region.aftertouchRange == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.aftertouchRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "lochanaft", "4" });
REQUIRE(region.aftertouchRange == sfz::Range<uint8_t>(4, 127));
REQUIRE(region.aftertouchRange == Range<uint8_t>(4, 127));
region.parseOpcode({ "lochanaft", "128" });
REQUIRE(region.aftertouchRange == sfz::Range<uint8_t>(127, 127));
REQUIRE(region.aftertouchRange == Range<uint8_t>(127, 127));
region.parseOpcode({ "lochanaft", "0" });
REQUIRE(region.aftertouchRange == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.aftertouchRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "hichanaft", "39" });
REQUIRE(region.aftertouchRange == sfz::Range<uint8_t>(0, 39));
REQUIRE(region.aftertouchRange == Range<uint8_t>(0, 39));
region.parseOpcode({ "hichanaft", "135" });
REQUIRE(region.aftertouchRange == sfz::Range<uint8_t>(0, 127));
REQUIRE(region.aftertouchRange == Range<uint8_t>(0, 127));
region.parseOpcode({ "hichanaft", "-1" });
REQUIRE(region.aftertouchRange == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.aftertouchRange == Range<uint8_t>(0, 0));
}
SECTION("lobpm, hibpm")
{
REQUIRE(region.bpmRange == sfz::Range<float>(0, 500));
REQUIRE(region.bpmRange == Range<float>(0, 500));
region.parseOpcode({ "lobpm", "47.5" });
REQUIRE(region.bpmRange == sfz::Range<float>(47.5, 500));
REQUIRE(region.bpmRange == Range<float>(47.5, 500));
region.parseOpcode({ "lobpm", "594" });
REQUIRE(region.bpmRange == sfz::Range<float>(500, 500));
REQUIRE(region.bpmRange == Range<float>(500, 500));
region.parseOpcode({ "lobpm", "0" });
REQUIRE(region.bpmRange == sfz::Range<float>(0, 500));
REQUIRE(region.bpmRange == Range<float>(0, 500));
region.parseOpcode({ "hibpm", "78" });
REQUIRE(region.bpmRange == sfz::Range<float>(0, 78));
REQUIRE(region.bpmRange == Range<float>(0, 78));
region.parseOpcode({ "hibpm", "895.4" });
REQUIRE(region.bpmRange == sfz::Range<float>(0, 500));
REQUIRE(region.bpmRange == Range<float>(0, 500));
region.parseOpcode({ "hibpm", "-1" });
REQUIRE(region.bpmRange == sfz::Range<float>(0, 0));
REQUIRE(region.bpmRange == Range<float>(0, 0));
}
SECTION("lorand, hirand")
{
REQUIRE(region.randRange == sfz::Range<float>(0, 1));
REQUIRE(region.randRange == Range<float>(0, 1));
region.parseOpcode({ "lorand", "0.5" });
REQUIRE(region.randRange == sfz::Range<float>(0.5, 1));
REQUIRE(region.randRange == Range<float>(0.5, 1));
region.parseOpcode({ "lorand", "4" });
REQUIRE(region.randRange == sfz::Range<float>(1, 1));
REQUIRE(region.randRange == Range<float>(1, 1));
region.parseOpcode({ "lorand", "0" });
REQUIRE(region.randRange == sfz::Range<float>(0, 1));
REQUIRE(region.randRange == Range<float>(0, 1));
region.parseOpcode({ "hirand", "39" });
REQUIRE(region.randRange == sfz::Range<float>(0, 1));
REQUIRE(region.randRange == Range<float>(0, 1));
region.parseOpcode({ "hirand", "0.7" });
REQUIRE(region.randRange == sfz::Range<float>(0, 0.7f));
REQUIRE(region.randRange == Range<float>(0, 0.7f));
region.parseOpcode({ "hirand", "-1" });
REQUIRE(region.randRange == sfz::Range<float>(0, 0));
REQUIRE(region.randRange == Range<float>(0, 0));
}
SECTION("seq_length")
@ -489,10 +490,10 @@ TEST_CASE("[Region] Parsing opcodes")
}
region.parseOpcode({ "on_locc45", "15" });
REQUIRE(region.ccTriggers.contains(45));
REQUIRE(region.ccTriggers[45] == sfz::Range<float>(15_norm, 127_norm));
REQUIRE(region.ccTriggers[45] == Range<float>(15_norm, 127_norm));
region.parseOpcode({ "on_hicc4", "47" });
REQUIRE(region.ccTriggers.contains(45));
REQUIRE(region.ccTriggers[4] == sfz::Range<float>(0_norm, 47_norm));
REQUIRE(region.ccTriggers[4] == Range<float>(0_norm, 47_norm));
}
SECTION("on_lohdcc, on_hihdcc")
@ -540,28 +541,28 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("pan_oncc")
{
REQUIRE(region.panCC.empty());
REQUIRE(region.modifiers[Mod::pan].empty());
region.parseOpcode({ "pan_oncc45", "4.2" });
REQUIRE(region.panCC.contains(45));
REQUIRE(region.panCC[45].value == 4.2_a);
REQUIRE(region.modifiers[Mod::pan].contains(45));
REQUIRE(region.modifiers[Mod::pan][45].value == 4.2_a);
region.parseOpcode({ "pan_curvecc17", "18" });
REQUIRE(region.panCC[17].curve == 18);
REQUIRE(region.modifiers[Mod::pan][17].curve == 18);
region.parseOpcode({ "pan_curvecc17", "15482" });
REQUIRE(region.panCC[17].curve == 255);
REQUIRE(region.modifiers[Mod::pan][17].curve == 255);
region.parseOpcode({ "pan_curvecc17", "-2" });
REQUIRE(region.panCC[17].curve == 0);
REQUIRE(region.modifiers[Mod::pan][17].curve == 0);
region.parseOpcode({ "pan_smoothcc14", "85" });
REQUIRE(region.panCC[14].smooth == 85);
REQUIRE(region.modifiers[Mod::pan][14].smooth == 85);
region.parseOpcode({ "pan_smoothcc14", "15482" });
REQUIRE(region.panCC[14].smooth == 127);
REQUIRE(region.modifiers[Mod::pan][14].smooth == 100);
region.parseOpcode({ "pan_smoothcc14", "-2" });
REQUIRE(region.panCC[14].smooth == 0);
REQUIRE(region.modifiers[Mod::pan][14].smooth == 0);
region.parseOpcode({ "pan_stepcc120", "24" });
REQUIRE(region.panCC[120].step == 24.0_a);
REQUIRE(region.modifiers[Mod::pan][120].step == 24.0_a);
region.parseOpcode({ "pan_stepcc120", "15482" });
REQUIRE(region.panCC[120].step == 200.0_a);
REQUIRE(region.modifiers[Mod::pan][120].step == 200.0_a);
region.parseOpcode({ "pan_stepcc120", "-2" });
REQUIRE(region.panCC[120].step == 0.0f);
REQUIRE(region.modifiers[Mod::pan][120].step == 0.0f);
}
SECTION("width")
@ -579,28 +580,28 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("width_oncc")
{
REQUIRE(region.widthCC.empty());
REQUIRE(region.modifiers[Mod::width].empty());
region.parseOpcode({ "width_oncc45", "4.2" });
REQUIRE(region.widthCC.contains(45));
REQUIRE(region.widthCC[45].value == 4.2_a);
REQUIRE(region.modifiers[Mod::width].contains(45));
REQUIRE(region.modifiers[Mod::width][45].value == 4.2_a);
region.parseOpcode({ "width_curvecc17", "18" });
REQUIRE(region.widthCC[17].curve == 18);
REQUIRE(region.modifiers[Mod::width][17].curve == 18);
region.parseOpcode({ "width_curvecc17", "15482" });
REQUIRE(region.widthCC[17].curve == 255);
REQUIRE(region.modifiers[Mod::width][17].curve == 255);
region.parseOpcode({ "width_curvecc17", "-2" });
REQUIRE(region.widthCC[17].curve == 0);
REQUIRE(region.modifiers[Mod::width][17].curve == 0);
region.parseOpcode({ "width_smoothcc14", "85" });
REQUIRE(region.widthCC[14].smooth == 85);
REQUIRE(region.modifiers[Mod::width][14].smooth == 85);
region.parseOpcode({ "width_smoothcc14", "15482" });
REQUIRE(region.widthCC[14].smooth == 127);
REQUIRE(region.modifiers[Mod::width][14].smooth == 100);
region.parseOpcode({ "width_smoothcc14", "-2" });
REQUIRE(region.widthCC[14].smooth == 0);
REQUIRE(region.modifiers[Mod::width][14].smooth == 0);
region.parseOpcode({ "width_stepcc120", "24" });
REQUIRE(region.widthCC[120].step == 24.0_a);
REQUIRE(region.modifiers[Mod::width][120].step == 24.0_a);
region.parseOpcode({ "width_stepcc120", "15482" });
REQUIRE(region.widthCC[120].step == 200.0_a);
REQUIRE(region.modifiers[Mod::width][120].step == 200.0_a);
region.parseOpcode({ "width_stepcc120", "-20" });
REQUIRE(region.widthCC[120].step == 0.0f);
REQUIRE(region.modifiers[Mod::width][120].step == 0.0f);
}
SECTION("position")
@ -618,28 +619,28 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("position_oncc")
{
REQUIRE(region.positionCC.empty());
REQUIRE(region.modifiers[Mod::position].empty());
region.parseOpcode({ "position_oncc45", "4.2" });
REQUIRE(region.positionCC.contains(45));
REQUIRE(region.positionCC[45].value == 4.2_a);
REQUIRE(region.modifiers[Mod::position].contains(45));
REQUIRE(region.modifiers[Mod::position][45].value == 4.2_a);
region.parseOpcode({ "position_curvecc17", "18" });
REQUIRE(region.positionCC[17].curve == 18);
REQUIRE(region.modifiers[Mod::position][17].curve == 18);
region.parseOpcode({ "position_curvecc17", "15482" });
REQUIRE(region.positionCC[17].curve == 255);
REQUIRE(region.modifiers[Mod::position][17].curve == 255);
region.parseOpcode({ "position_curvecc17", "-2" });
REQUIRE(region.positionCC[17].curve == 0);
REQUIRE(region.modifiers[Mod::position][17].curve == 0);
region.parseOpcode({ "position_smoothcc14", "85" });
REQUIRE(region.positionCC[14].smooth == 85);
REQUIRE(region.modifiers[Mod::position][14].smooth == 85);
region.parseOpcode({ "position_smoothcc14", "15482" });
REQUIRE(region.positionCC[14].smooth == 127);
REQUIRE(region.modifiers[Mod::position][14].smooth == 100);
region.parseOpcode({ "position_smoothcc14", "-2" });
REQUIRE(region.positionCC[14].smooth == 0);
REQUIRE(region.modifiers[Mod::position][14].smooth == 0);
region.parseOpcode({ "position_stepcc120", "24" });
REQUIRE(region.positionCC[120].step == 24.0_a);
REQUIRE(region.modifiers[Mod::position][120].step == 24.0_a);
region.parseOpcode({ "position_stepcc120", "15482" });
REQUIRE(region.positionCC[120].step == 200.0_a);
REQUIRE(region.modifiers[Mod::position][120].step == 200.0_a);
region.parseOpcode({ "position_stepcc120", "-2" });
REQUIRE(region.positionCC[120].step == 0.0f);
REQUIRE(region.modifiers[Mod::position][120].step == 0.0f);
}
SECTION("amp_keycenter")
@ -704,116 +705,116 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("xfin_lokey, xfin_hikey")
{
REQUIRE(region.crossfadeKeyInRange == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(0, 0));
region.parseOpcode({ "xfin_lokey", "4" });
REQUIRE(region.crossfadeKeyInRange == sfz::Range<uint8_t>(4, 4));
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(4, 4));
region.parseOpcode({ "xfin_lokey", "128" });
REQUIRE(region.crossfadeKeyInRange == sfz::Range<uint8_t>(127, 127));
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(127, 127));
region.parseOpcode({ "xfin_lokey", "59" });
REQUIRE(region.crossfadeKeyInRange == sfz::Range<uint8_t>(59, 127));
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(59, 127));
region.parseOpcode({ "xfin_hikey", "59" });
REQUIRE(region.crossfadeKeyInRange == sfz::Range<uint8_t>(59, 59));
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(59, 59));
region.parseOpcode({ "xfin_hikey", "128" });
REQUIRE(region.crossfadeKeyInRange == sfz::Range<uint8_t>(59, 127));
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(59, 127));
region.parseOpcode({ "xfin_hikey", "0" });
REQUIRE(region.crossfadeKeyInRange == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(0, 0));
region.parseOpcode({ "xfin_hikey", "-1" });
REQUIRE(region.crossfadeKeyInRange == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(0, 0));
}
SECTION("xfin_lovel, xfin_hivel")
{
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(0_norm, 0_norm));
REQUIRE(region.crossfadeVelInRange == Range<float>(0_norm, 0_norm));
region.parseOpcode({ "xfin_lovel", "4" });
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(4_norm, 4_norm));
REQUIRE(region.crossfadeVelInRange == Range<float>(4_norm, 4_norm));
region.parseOpcode({ "xfin_lovel", "128" });
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(127_norm, 127_norm));
REQUIRE(region.crossfadeVelInRange == Range<float>(127_norm, 127_norm));
region.parseOpcode({ "xfin_lovel", "59" });
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(59_norm, 127_norm));
REQUIRE(region.crossfadeVelInRange == Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfin_hivel", "59" });
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(59_norm, 59_norm));
REQUIRE(region.crossfadeVelInRange == Range<float>(59_norm, 59_norm));
region.parseOpcode({ "xfin_hivel", "128" });
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(59_norm, 127_norm));
REQUIRE(region.crossfadeVelInRange == Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfin_hivel", "0" });
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(0_norm, 0_norm));
REQUIRE(region.crossfadeVelInRange == Range<float>(0_norm, 0_norm));
region.parseOpcode({ "xfin_hivel", "-1" });
REQUIRE(region.crossfadeVelInRange == sfz::Range<float>(0_norm, 0_norm));
REQUIRE(region.crossfadeVelInRange == Range<float>(0_norm, 0_norm));
}
SECTION("xfout_lokey, xfout_hikey")
{
REQUIRE(region.crossfadeKeyOutRange == sfz::Range<uint8_t>(127, 127));
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(127, 127));
region.parseOpcode({ "xfout_lokey", "4" });
REQUIRE(region.crossfadeKeyOutRange == sfz::Range<uint8_t>(4, 127));
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(4, 127));
region.parseOpcode({ "xfout_lokey", "128" });
REQUIRE(region.crossfadeKeyOutRange == sfz::Range<uint8_t>(127, 127));
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(127, 127));
region.parseOpcode({ "xfout_lokey", "59" });
REQUIRE(region.crossfadeKeyOutRange == sfz::Range<uint8_t>(59, 127));
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(59, 127));
region.parseOpcode({ "xfout_hikey", "59" });
REQUIRE(region.crossfadeKeyOutRange == sfz::Range<uint8_t>(59, 59));
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(59, 59));
region.parseOpcode({ "xfout_hikey", "128" });
REQUIRE(region.crossfadeKeyOutRange == sfz::Range<uint8_t>(59, 127));
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(59, 127));
region.parseOpcode({ "xfout_hikey", "0" });
REQUIRE(region.crossfadeKeyOutRange == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(0, 0));
region.parseOpcode({ "xfout_hikey", "-1" });
REQUIRE(region.crossfadeKeyOutRange == sfz::Range<uint8_t>(0, 0));
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(0, 0));
}
SECTION("xfout_lovel, xfout_hivel")
{
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(127_norm, 127_norm));
REQUIRE(region.crossfadeVelOutRange == Range<float>(127_norm, 127_norm));
region.parseOpcode({ "xfout_lovel", "4" });
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(4_norm, 127_norm));
REQUIRE(region.crossfadeVelOutRange == Range<float>(4_norm, 127_norm));
region.parseOpcode({ "xfout_lovel", "128" });
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(127_norm, 127_norm));
REQUIRE(region.crossfadeVelOutRange == Range<float>(127_norm, 127_norm));
region.parseOpcode({ "xfout_lovel", "59" });
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(59_norm, 127_norm));
REQUIRE(region.crossfadeVelOutRange == Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfout_hivel", "59" });
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(59_norm, 59_norm));
REQUIRE(region.crossfadeVelOutRange == Range<float>(59_norm, 59_norm));
region.parseOpcode({ "xfout_hivel", "128" });
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(59_norm, 127_norm));
REQUIRE(region.crossfadeVelOutRange == Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfout_hivel", "0" });
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(0_norm, 0_norm));
REQUIRE(region.crossfadeVelOutRange == Range<float>(0_norm, 0_norm));
region.parseOpcode({ "xfout_hivel", "-1" });
REQUIRE(region.crossfadeVelOutRange == sfz::Range<float>(0_norm, 0_norm));
REQUIRE(region.crossfadeVelOutRange == Range<float>(0_norm, 0_norm));
}
SECTION("xfin_locc, xfin_hicc")
{
REQUIRE(!region.crossfadeCCInRange.contains(4));
region.parseOpcode({ "xfin_locc4", "4" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(4_norm, 4_norm));
REQUIRE(region.crossfadeCCInRange[4] == Range<float>(4_norm, 4_norm));
region.parseOpcode({ "xfin_locc4", "128" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(127_norm, 127_norm));
REQUIRE(region.crossfadeCCInRange[4] == Range<float>(127_norm, 127_norm));
region.parseOpcode({ "xfin_locc4", "59" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(59_norm, 127_norm));
REQUIRE(region.crossfadeCCInRange[4] == Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfin_hicc4", "59" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(59_norm, 59_norm));
REQUIRE(region.crossfadeCCInRange[4] == Range<float>(59_norm, 59_norm));
region.parseOpcode({ "xfin_hicc4", "128" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(59_norm, 127_norm));
REQUIRE(region.crossfadeCCInRange[4] == Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfin_hicc4", "0" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(0_norm, 0_norm));
REQUIRE(region.crossfadeCCInRange[4] == Range<float>(0_norm, 0_norm));
region.parseOpcode({ "xfin_hicc4", "-1" });
REQUIRE(region.crossfadeCCInRange[4] == sfz::Range<float>(0_norm, 0_norm));
REQUIRE(region.crossfadeCCInRange[4] == Range<float>(0_norm, 0_norm));
}
SECTION("xfout_locc, xfout_hicc")
{
REQUIRE(!region.crossfadeCCOutRange.contains(4));
region.parseOpcode({ "xfout_locc4", "4" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(4_norm, 127_norm));
REQUIRE(region.crossfadeCCOutRange[4] == Range<float>(4_norm, 127_norm));
region.parseOpcode({ "xfout_locc4", "128" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(127_norm, 127_norm));
REQUIRE(region.crossfadeCCOutRange[4] == Range<float>(127_norm, 127_norm));
region.parseOpcode({ "xfout_locc4", "59" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(59_norm, 127_norm));
REQUIRE(region.crossfadeCCOutRange[4] == Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfout_hicc4", "59" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(59_norm, 59_norm));
REQUIRE(region.crossfadeCCOutRange[4] == Range<float>(59_norm, 59_norm));
region.parseOpcode({ "xfout_hicc4", "128" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(59_norm, 127_norm));
REQUIRE(region.crossfadeCCOutRange[4] == Range<float>(59_norm, 127_norm));
region.parseOpcode({ "xfout_hicc4", "0" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(0_norm, 0_norm));
REQUIRE(region.crossfadeCCOutRange[4] == Range<float>(0_norm, 0_norm));
region.parseOpcode({ "xfout_hicc4", "-1" });
REQUIRE(region.crossfadeCCOutRange[4] == sfz::Range<float>(0_norm, 0_norm));
REQUIRE(region.crossfadeCCOutRange[4] == Range<float>(0_norm, 0_norm));
}
SECTION("xf_keycurve")
@ -932,7 +933,7 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.tune == -9600);
}
SECTION("bend_up, bend_down, bend_step")
SECTION("bend_up, bend_down, bend_step, bend_smooth")
{
REQUIRE(region.bendUp == 200);
REQUIRE(region.bendDown == -200);
@ -959,6 +960,12 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.bendStep == 1);
region.parseOpcode({ "bend_step", "9700" });
REQUIRE(region.bendStep == 1200);
region.parseOpcode({ "bend_smooth", "10" });
REQUIRE(region.bendSmooth == 10);
region.parseOpcode({ "bend_smooth", "120" });
REQUIRE(region.bendSmooth == 100);
region.parseOpcode({ "bend_smooth", "-2" });
REQUIRE(region.bendSmooth == 0);
}
SECTION("ampeg")
@ -1173,7 +1180,7 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.filters[0].cutoff == 500.0f);
// Check filter defaults
REQUIRE(region.filters[0].keycenter == 60);
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterLpf2p);
REQUIRE(region.filters[0].type == FilterType::kFilterLpf2p);
REQUIRE(region.filters[0].keytrack == 0);
REQUIRE(region.filters[0].gain == 0);
REQUIRE(region.filters[0].veltrack == 0);
@ -1187,7 +1194,7 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.filters[1].cutoff == 5000.0f);
// Check filter defaults
REQUIRE(region.filters[1].keycenter == 60);
REQUIRE(region.filters[1].type == sfz::FilterType::kFilterLpf2p);
REQUIRE(region.filters[1].type == FilterType::kFilterLpf2p);
REQUIRE(region.filters[1].keytrack == 0);
REQUIRE(region.filters[1].gain == 0);
REQUIRE(region.filters[1].veltrack == 0);
@ -1202,7 +1209,7 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.filters[3].cutoff == 50.0f);
// Check filter defaults
REQUIRE(region.filters[2].keycenter == 60);
REQUIRE(region.filters[2].type == sfz::FilterType::kFilterLpf2p);
REQUIRE(region.filters[2].type == FilterType::kFilterLpf2p);
REQUIRE(region.filters[2].keytrack == 0);
REQUIRE(region.filters[2].gain == 0);
REQUIRE(region.filters[2].veltrack == 0);
@ -1211,7 +1218,7 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.filters[2].gainCC.empty());
REQUIRE(region.filters[2].resonanceCC.empty());
REQUIRE(region.filters[3].keycenter == 60);
REQUIRE(region.filters[3].type == sfz::FilterType::kFilterLpf2p);
REQUIRE(region.filters[3].type == FilterType::kFilterLpf2p);
REQUIRE(region.filters[3].keytrack == 0);
REQUIRE(region.filters[3].gain == 0);
REQUIRE(region.filters[3].veltrack == 0);
@ -1313,55 +1320,55 @@ TEST_CASE("[Region] Parsing opcodes")
region.parseOpcode({ "fil_type", "lpf_1p" });
REQUIRE(region.filters.size() == 1);
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterLpf1p);
REQUIRE(region.filters[0].type == FilterType::kFilterLpf1p);
region.parseOpcode({ "fil_type", "lpf_2p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterLpf2p);
REQUIRE(region.filters[0].type == FilterType::kFilterLpf2p);
region.parseOpcode({ "fil_type", "hpf_1p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterHpf1p);
REQUIRE(region.filters[0].type == FilterType::kFilterHpf1p);
region.parseOpcode({ "fil_type", "hpf_2p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterHpf2p);
REQUIRE(region.filters[0].type == FilterType::kFilterHpf2p);
region.parseOpcode({ "fil_type", "bpf_2p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterBpf2p);
REQUIRE(region.filters[0].type == FilterType::kFilterBpf2p);
region.parseOpcode({ "fil_type", "brf_2p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterBrf2p);
REQUIRE(region.filters[0].type == FilterType::kFilterBrf2p);
region.parseOpcode({ "fil_type", "bpf_1p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterBpf1p);
REQUIRE(region.filters[0].type == FilterType::kFilterBpf1p);
region.parseOpcode({ "fil_type", "brf_1p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterBrf1p);
REQUIRE(region.filters[0].type == FilterType::kFilterBrf1p);
region.parseOpcode({ "fil_type", "apf_1p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterApf1p);
REQUIRE(region.filters[0].type == FilterType::kFilterApf1p);
region.parseOpcode({ "fil_type", "lpf_2p_sv" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterLpf2pSv);
REQUIRE(region.filters[0].type == FilterType::kFilterLpf2pSv);
region.parseOpcode({ "fil_type", "hpf_2p_sv" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterHpf2pSv);
REQUIRE(region.filters[0].type == FilterType::kFilterHpf2pSv);
region.parseOpcode({ "fil_type", "bpf_2p_sv" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterBpf2pSv);
REQUIRE(region.filters[0].type == FilterType::kFilterBpf2pSv);
region.parseOpcode({ "fil_type", "brf_2p_sv" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterBrf2pSv);
REQUIRE(region.filters[0].type == FilterType::kFilterBrf2pSv);
region.parseOpcode({ "fil_type", "lpf_4p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterLpf4p);
REQUIRE(region.filters[0].type == FilterType::kFilterLpf4p);
region.parseOpcode({ "fil_type", "hpf_4p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterHpf4p);
REQUIRE(region.filters[0].type == FilterType::kFilterHpf4p);
region.parseOpcode({ "fil_type", "lpf_6p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterLpf6p);
REQUIRE(region.filters[0].type == FilterType::kFilterLpf6p);
region.parseOpcode({ "fil_type", "hpf_6p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterHpf6p);
REQUIRE(region.filters[0].type == FilterType::kFilterHpf6p);
region.parseOpcode({ "fil_type", "pink" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterPink);
REQUIRE(region.filters[0].type == FilterType::kFilterPink);
region.parseOpcode({ "fil_type", "lsh" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterLsh);
REQUIRE(region.filters[0].type == FilterType::kFilterLsh);
region.parseOpcode({ "fil_type", "hsh" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterHsh);
REQUIRE(region.filters[0].type == FilterType::kFilterHsh);
region.parseOpcode({ "fil_type", "peq" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterPeq);
REQUIRE(region.filters[0].type == FilterType::kFilterPeq);
region.parseOpcode({ "fil_type", "lpf_1p" });
region.parseOpcode({ "fil_type", "pkf_2p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterPeq);
REQUIRE(region.filters[0].type == FilterType::kFilterPeq);
region.parseOpcode({ "fil_type", "lpf_1p" });
region.parseOpcode({ "fil_type", "bpk_2p" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterPeq);
REQUIRE(region.filters[0].type == FilterType::kFilterPeq);
region.parseOpcode({ "fil_type", "unknown" });
REQUIRE(region.filters[0].type == sfz::FilterType::kFilterNone);
REQUIRE(region.filters[0].type == FilterType::kFilterNone);
}
SECTION("EQ stacking and gains")
@ -1372,7 +1379,7 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.equalizers.size() == 1);
REQUIRE(region.equalizers[0].gain == 6.0f);
// Check defaults
REQUIRE(region.equalizers[0].type == sfz::EqType::kEqPeak);
REQUIRE(region.equalizers[0].type == EqType::kEqPeak);
REQUIRE(region.equalizers[0].bandwidth == 1.0f);
REQUIRE(region.equalizers[0].frequency == 0.0f);
REQUIRE(region.equalizers[0].vel2frequency == 0);
@ -1385,7 +1392,7 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.equalizers.size() == 2);
REQUIRE(region.equalizers[1].gain == -96.0f);
// Check defaults
REQUIRE(region.equalizers[1].type == sfz::EqType::kEqPeak);
REQUIRE(region.equalizers[1].type == EqType::kEqPeak);
REQUIRE(region.equalizers[1].bandwidth == 1.0f);
REQUIRE(region.equalizers[1].frequency == 0.0f);
REQUIRE(region.equalizers[1].vel2frequency == 0);
@ -1397,7 +1404,7 @@ TEST_CASE("[Region] Parsing opcodes")
region.parseOpcode({ "eq4_gain", "500" });
REQUIRE(region.equalizers.size() == 4);
REQUIRE(region.equalizers[2].gain == 0.0f);
REQUIRE(region.equalizers[3].type == sfz::EqType::kEqPeak);
REQUIRE(region.equalizers[3].type == EqType::kEqPeak);
REQUIRE(region.equalizers[3].gain == 96.0f);
// Check defaults
REQUIRE(region.equalizers[2].bandwidth == 1.0f);
@ -1419,13 +1426,13 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("EQ types")
{
region.parseOpcode({ "eq1_type", "hshelf" });
REQUIRE(region.equalizers[0].type == sfz::EqType::kEqHighShelf);
REQUIRE(region.equalizers[0].type == EqType::kEqHighShelf);
region.parseOpcode({ "eq1_type", "somethingsomething" });
REQUIRE(region.equalizers[0].type == sfz::EqType::kEqNone);
REQUIRE(region.equalizers[0].type == EqType::kEqNone);
region.parseOpcode({ "eq1_type", "lshelf" });
REQUIRE(region.equalizers[0].type == sfz::EqType::kEqLowShelf);
REQUIRE(region.equalizers[0].type == EqType::kEqLowShelf);
region.parseOpcode({ "eq1_type", "peak" });
REQUIRE(region.equalizers[0].type == sfz::EqType::kEqPeak);
REQUIRE(region.equalizers[0].type == EqType::kEqPeak);
}
SECTION("EQ parameter dispatch")
@ -1438,7 +1445,7 @@ TEST_CASE("[Region] Parsing opcodes")
region.parseOpcode({ "eq2_freq", "300" });
REQUIRE(region.equalizers[1].frequency == 300.0f);
region.parseOpcode({ "eq3_type", "lshelf" });
REQUIRE(region.equalizers[2].type == sfz::EqType::kEqLowShelf);
REQUIRE(region.equalizers[2].type == EqType::kEqLowShelf);
region.parseOpcode({ "eq3_vel2gain", "10" });
REQUIRE(region.equalizers[2].vel2gain == 10.0f);
region.parseOpcode({ "eq1_vel2freq", "100" });
@ -1455,7 +1462,7 @@ TEST_CASE("[Region] Parsing opcodes")
region.parseOpcode({ "eq3_freq_oncc15", "20" });
REQUIRE(region.equalizers[2].frequencyCC[15] == 20.0f);
region.parseOpcode({ "eq1_type", "hshelf" });
REQUIRE(region.equalizers[0].type == sfz::EqType::kEqHighShelf);
REQUIRE(region.equalizers[0].type == EqType::kEqHighShelf);
region.parseOpcode({ "eq2_gaincc123", "2" });
REQUIRE(region.equalizers[1].gainCC.contains(123));
REQUIRE(region.equalizers[1].gainCC[123] == 2.0f);
@ -1576,103 +1583,103 @@ TEST_CASE("[Region] Parsing opcodes")
SECTION("amplitude_cc")
{
REQUIRE(region.amplitudeCC.empty());
REQUIRE(region.modifiers[Mod::amplitude].empty());
region.parseOpcode({ "amplitude_cc1", "40" });
REQUIRE(region.amplitudeCC.contains(1));
REQUIRE(region.amplitudeCC[1].value == 40.0_a);
REQUIRE(region.modifiers[Mod::amplitude].contains(1));
REQUIRE(region.modifiers[Mod::amplitude][1].value == 40.0_a);
region.parseOpcode({ "amplitude_oncc2", "30" });
REQUIRE(region.amplitudeCC.contains(2));
REQUIRE(region.amplitudeCC[2].value == 30.0_a);
REQUIRE(region.modifiers[Mod::amplitude].contains(2));
REQUIRE(region.modifiers[Mod::amplitude][2].value == 30.0_a);
region.parseOpcode({ "amplitude_curvecc17", "18" });
REQUIRE(region.amplitudeCC[17].curve == 18);
REQUIRE(region.modifiers[Mod::amplitude][17].curve == 18);
region.parseOpcode({ "amplitude_curvecc17", "15482" });
REQUIRE(region.amplitudeCC[17].curve == 255);
REQUIRE(region.modifiers[Mod::amplitude][17].curve == 255);
region.parseOpcode({ "amplitude_curvecc17", "-2" });
REQUIRE(region.amplitudeCC[17].curve == 0);
REQUIRE(region.modifiers[Mod::amplitude][17].curve == 0);
region.parseOpcode({ "amplitude_smoothcc14", "85" });
REQUIRE(region.amplitudeCC[14].smooth == 85);
REQUIRE(region.modifiers[Mod::amplitude][14].smooth == 85);
region.parseOpcode({ "amplitude_smoothcc14", "15482" });
REQUIRE(region.amplitudeCC[14].smooth == 127);
REQUIRE(region.modifiers[Mod::amplitude][14].smooth == 100);
region.parseOpcode({ "amplitude_smoothcc14", "-2" });
REQUIRE(region.amplitudeCC[14].smooth == 0);
REQUIRE(region.modifiers[Mod::amplitude][14].smooth == 0);
region.parseOpcode({ "amplitude_stepcc120", "24" });
REQUIRE(region.amplitudeCC[120].step == 24.0_a);
REQUIRE(region.modifiers[Mod::amplitude][120].step == 24.0_a);
region.parseOpcode({ "amplitude_stepcc120", "15482" });
REQUIRE(region.amplitudeCC[120].step == 100.0_a);
REQUIRE(region.modifiers[Mod::amplitude][120].step == 100.0_a);
region.parseOpcode({ "amplitude_stepcc120", "-2" });
REQUIRE(region.amplitudeCC[120].step == 0.0f);
REQUIRE(region.modifiers[Mod::amplitude][120].step == 0.0f);
}
SECTION("volume_oncc/gain_cc")
{
REQUIRE(region.volumeCC.empty());
REQUIRE(region.modifiers[Mod::volume].empty());
region.parseOpcode({ "gain_cc1", "40" });
REQUIRE(region.volumeCC.contains(1));
REQUIRE(region.volumeCC[1].value == 40_a);
REQUIRE(region.modifiers[Mod::volume].contains(1));
REQUIRE(region.modifiers[Mod::volume][1].value == 40_a);
region.parseOpcode({ "volume_oncc2", "-76" });
REQUIRE(region.volumeCC.contains(2));
REQUIRE(region.volumeCC[2].value == -76.0_a);
REQUIRE(region.modifiers[Mod::volume].contains(2));
REQUIRE(region.modifiers[Mod::volume][2].value == -76.0_a);
region.parseOpcode({ "gain_oncc4", "-1" });
REQUIRE(region.volumeCC.contains(4));
REQUIRE(region.volumeCC[4].value == -1.0_a);
REQUIRE(region.modifiers[Mod::volume].contains(4));
REQUIRE(region.modifiers[Mod::volume][4].value == -1.0_a);
region.parseOpcode({ "volume_curvecc17", "18" });
REQUIRE(region.volumeCC[17].curve == 18);
REQUIRE(region.modifiers[Mod::volume][17].curve == 18);
region.parseOpcode({ "volume_curvecc17", "15482" });
REQUIRE(region.volumeCC[17].curve == 255);
REQUIRE(region.modifiers[Mod::volume][17].curve == 255);
region.parseOpcode({ "volume_curvecc17", "-2" });
REQUIRE(region.volumeCC[17].curve == 0);
REQUIRE(region.modifiers[Mod::volume][17].curve == 0);
region.parseOpcode({ "volume_smoothcc14", "85" });
REQUIRE(region.volumeCC[14].smooth == 85);
REQUIRE(region.modifiers[Mod::volume][14].smooth == 85);
region.parseOpcode({ "volume_smoothcc14", "15482" });
REQUIRE(region.volumeCC[14].smooth == 127);
REQUIRE(region.modifiers[Mod::volume][14].smooth == 100);
region.parseOpcode({ "volume_smoothcc14", "-2" });
REQUIRE(region.volumeCC[14].smooth == 0);
REQUIRE(region.modifiers[Mod::volume][14].smooth == 0);
region.parseOpcode({ "volume_stepcc120", "24" });
REQUIRE(region.volumeCC[120].step == 24.0f);
REQUIRE(region.modifiers[Mod::volume][120].step == 24.0f);
region.parseOpcode({ "volume_stepcc120", "15482" });
REQUIRE(region.volumeCC[120].step == 144.0f);
REQUIRE(region.modifiers[Mod::volume][120].step == 144.0f);
region.parseOpcode({ "volume_stepcc120", "-2" });
REQUIRE(region.volumeCC[120].step == 0.0f);
REQUIRE(region.modifiers[Mod::volume][120].step == 0.0f);
}
SECTION("tune_cc/pitch_cc")
{
REQUIRE(region.tuneCC.empty());
REQUIRE(region.modifiers[Mod::pitch].empty());
region.parseOpcode({ "pitch_cc1", "40" });
REQUIRE(region.tuneCC.contains(1));
REQUIRE(region.tuneCC[1].value == 40.0);
REQUIRE(region.modifiers[Mod::pitch].contains(1));
REQUIRE(region.modifiers[Mod::pitch][1].value == 40.0);
region.parseOpcode({ "tune_oncc2", "-76" });
REQUIRE(region.tuneCC.contains(2));
REQUIRE(region.tuneCC[2].value == -76.0);
REQUIRE(region.modifiers[Mod::pitch].contains(2));
REQUIRE(region.modifiers[Mod::pitch][2].value == -76.0);
region.parseOpcode({ "pitch_oncc4", "-1" });
REQUIRE(region.tuneCC.contains(4));
REQUIRE(region.tuneCC[4].value == -1.0);
REQUIRE(region.modifiers[Mod::pitch].contains(4));
REQUIRE(region.modifiers[Mod::pitch][4].value == -1.0);
region.parseOpcode({ "tune_curvecc17", "18" });
REQUIRE(region.tuneCC[17].curve == 18);
REQUIRE(region.modifiers[Mod::pitch][17].curve == 18);
region.parseOpcode({ "pitch_curvecc17", "15482" });
REQUIRE(region.tuneCC[17].curve == 255);
REQUIRE(region.modifiers[Mod::pitch][17].curve == 255);
region.parseOpcode({ "tune_curvecc17", "-2" });
REQUIRE(region.tuneCC[17].curve == 0);
REQUIRE(region.modifiers[Mod::pitch][17].curve == 0);
region.parseOpcode({ "pitch_smoothcc14", "85" });
REQUIRE(region.tuneCC[14].smooth == 85);
REQUIRE(region.modifiers[Mod::pitch][14].smooth == 85);
region.parseOpcode({ "tune_smoothcc14", "15482" });
REQUIRE(region.tuneCC[14].smooth == 127);
REQUIRE(region.modifiers[Mod::pitch][14].smooth == 100);
region.parseOpcode({ "pitch_smoothcc14", "-2" });
REQUIRE(region.tuneCC[14].smooth == 0);
REQUIRE(region.modifiers[Mod::pitch][14].smooth == 0);
region.parseOpcode({ "tune_stepcc120", "24" });
REQUIRE(region.tuneCC[120].step == 24.0f);
REQUIRE(region.modifiers[Mod::pitch][120].step == 24.0f);
region.parseOpcode({ "pitch_stepcc120", "15482" });
REQUIRE(region.tuneCC[120].step == 9600.0f);
REQUIRE(region.modifiers[Mod::pitch][120].step == 9600.0f);
region.parseOpcode({ "tune_stepcc120", "-2" });
REQUIRE(region.tuneCC[120].step == 0.0f);
REQUIRE(region.modifiers[Mod::pitch][120].step == 0.0f);
}
}
// Specific region bugs
TEST_CASE("[Region] Non-conforming floating point values in integer opcodes")
{
sfz::MidiState midiState;
sfz::Region region { 0, midiState };
MidiState midiState;
Region region { 0, midiState };
region.parseOpcode({ "offset", "2014.5" });
REQUIRE(region.offset == 2014);
region.parseOpcode({ "pitch_keytrack", "-2.1" });

View file

@ -5,6 +5,7 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "sfizz/OnePoleFilter.h"
#include "sfizz/Smoothers.h"
#include "catch2/catch.hpp"
// #include "cnpy.h"
// #include "ghc/fs_std.hpp"
@ -16,14 +17,13 @@
using namespace Catch::literals;
template <class Type, size_t N>
inline bool approxEqual(const std::array<Type, N> &lhs, const std::array<Type, N> &rhs)
inline bool approxEqual(const std::array<Type, N>& lhs, const std::array<Type, N>& rhs, Type epsilon = 1e-3)
{
if (lhs.size() != rhs.size())
return false;
for (size_t i = 0; i < rhs.size(); ++i)
if (lhs[i] != Approx(rhs[i]).epsilon(1e-3))
{
if (lhs[i] != Approx(rhs[i]).epsilon(epsilon)) {
std::cerr << lhs[i] << " != " << rhs[i] << " at index " << i << '\n';
return false;
}
@ -74,7 +74,8 @@ constexpr std::array<float, 64> floatInput01 = {
1.1666758495956464f, -0.04382336233428379f, 0.43819763320865857f, 0.1740610608056625f,
0.30473671729007396f, -0.4065881153810866f, -0.9784770671900811f, -0.17674381857142665f,
0.3493213284003123f, -1.2540491577273034f, 1.2597719140599792f, 0.4198847510851298f,
0.9612865621570132f, -1.5614809857797225f, -0.31416474166626646f, -1.4741449502960542f };
0.9612865621570132f, -1.5614809857797225f, -0.31416474166626646f, -1.4741449502960542f
};
constexpr std::array<float, 64> floatOutputLow01 = {
0.06567783171600353f, 0.18655945645590016f, 0.15368937959051f, 0.05054483866245487f,
@ -92,7 +93,8 @@ constexpr std::array<float, 64> floatOutputLow01 = {
0.09460636244101792f, 0.17948270447550216f, 0.18270169192308128f, 0.20514308375655024f,
0.21137141199133533f, 0.16368102816645502f, 0.008005824629720673f, -0.0984698603721838f,
-0.06487738486552441f, -0.13532948119242824f, -0.11020387039992532f, 0.06252925741325283f,
0.1767213299964926f, 0.0900270496677931f, -0.09685475276689556f, -0.24181840607858007f };
0.1767213299964926f, 0.0900270496677931f, -0.09685475276689556f, -0.24181840607858007f
};
constexpr std::array<float, 64> floatOutputHigh01 = {
0.6567783171600353f, 0.5520379302389311f, -0.8807386988928331f, -0.1507067103877182f,
@ -110,7 +112,8 @@ constexpr std::array<float, 64> floatOutputHigh01 = {
1.0720694871546286f, -0.22330606680978596f, 0.2554959412855773f, -0.031082022950887744f,
0.09336530529873863f, -0.5702691435475415f, -0.9864828918198018f, -0.07827395819924285f,
0.41419871326583674f, -1.1187196765348753f, 1.3699757844599045f, 0.35735549367187697f,
0.7845652321605207f, -1.6515080354475156f, -0.2173099888993709f, -1.2323265442174742f };
0.7845652321605207f, -1.6515080354475156f, -0.2173099888993709f, -1.2323265442174742f
};
constexpr std::array<float, 64> floatInput05 = {
-0.8247415510202276f, -1.0299159073255513f, 0.7689727513393745f, -0.023063681797826918f,
@ -128,7 +131,8 @@ constexpr std::array<float, 64> floatInput05 = {
-0.6137810912250902f, -0.7039958189789415f, -1.33840097232278f, 0.4786946763969468f,
-0.46464793721558995f, -1.7121509287301122f, 0.7887828546774234f, -0.902172963851904f,
-0.2591368523894675f, -0.9510361177022718f, 0.5739217219088085f, -0.25730420306720403f,
0.41740839680521646f, -2.0979181310103074f, 1.1494564006889283f, 0.5059893282486726f };
0.41740839680521646f, -2.0979181310103074f, 1.1494564006889283f, 0.5059893282486726f
};
constexpr std::array<float, 64> floatOutputLow05 = {
-0.27491385034007587f, -0.7098571028952849f, -0.32360008629382064f, 0.14076966108257558f,
@ -146,7 +150,8 @@ constexpr std::array<float, 64> floatOutputLow05 = {
0.02703177262292994f, -0.4302483791937005f, -0.8242150568318073f, -0.5613071175858801f,
-0.18242012613484115f, -0.7864063306935144f, -0.5699248015820677f, -0.22777163691884944f,
-0.46302715105340697f, -0.5577333737150487f, -0.311615923169504f, 0.001667198557366828f,
0.05392379743179307f, -0.5421953122577658f, -0.49688568085971485f, 0.3861866826926287f };
0.05392379743179307f, -0.5421953122577658f, -0.49688568085971485f, 0.3861866826926287f
};
constexpr std::array<float, 64> floatOutputHigh05 = {
-0.5498277006801517f, -0.32005880443026635f, 1.0925728376331951f, -0.1638333428804025f,
@ -164,7 +169,8 @@ constexpr std::array<float, 64> floatOutputHigh05 = {
-0.6408128638480202f, -0.27374743978524096f, -0.5141859154909728f, 1.040001793982827f,
-0.2822278110807488f, -0.9257445980365978f, 1.358707656259491f, -0.6744013269330547f,
0.20389029866393948f, -0.3933027439872231f, 0.8855376450783126f, -0.2589714016245709f,
0.36348459937342337f, -1.5557228187525416f, 1.6463420815486431f, 0.11980264555604392f };
0.36348459937342337f, -1.5557228187525416f, 1.6463420815486431f, 0.11980264555604392f
};
constexpr std::array<float, 64> floatInput09 = {
-0.9629663717342508f, 1.054078826032172f, -1.0644939081323097f, -0.05328934531304567f,
@ -182,7 +188,8 @@ constexpr std::array<float, 64> floatInput09 = {
0.7517403248613556f, 1.1194691465807607f, -1.1160170942539855f, -1.0010374555669668f,
2.1609909686692763f, -0.07213993925443297f, -0.5083174992310037f, -0.7489925703250175f,
0.5119124853257149f, -0.33950253799120345f, -0.26764774112191847f, 0.10271208568438035f,
-0.09893862035889031f, -0.4154625911342657f, 0.11272544601558693f, -0.6895075000870634f };
-0.09893862035889031f, -0.4154625911342657f, 0.11272544601558693f, -0.6895075000870634f
};
constexpr std::array<float, 64> floatOutputLow09 = {
-0.45614196555832937f, 0.01915105911173487f, -0.003925509462605503f, -0.5296828837089897f,
@ -200,7 +207,8 @@ constexpr std::array<float, 64> floatOutputLow09 = {
1.3518864464016498f, 0.9575142994410892f, 0.0520306721253716f, -1.0000768566454319f,
0.496816040067124f, 1.015603963410564f, -0.22150068331359823f, -0.6072258583851468f,
-0.14426034859888792f, 0.07407521986377441f, -0.2836988048502276f, -0.09305893177831953f,
-0.003110407570995219f, -0.24382743742154736f, -0.15623482860471877f, -0.28143543764463197f };
-0.003110407570995219f, -0.24382743742154736f, -0.15623482860471877f, -0.28143543764463197f
};
constexpr std::array<float, 64> floatOutputHigh09 = {
-0.5068244061759215f, 1.034927766920437f, -1.0605683986697043f, 0.47639353839594406f,
@ -218,7 +226,8 @@ constexpr std::array<float, 64> floatOutputHigh09 = {
-0.6001461215402942f, 0.16195484713967145f, -1.1680477663793571f, -0.0009605989215348831f,
1.6641749286021523f, -1.087743902664997f, -0.28681681591740543f, -0.14176671193987078f,
0.6561728339246028f, -0.41357775785497786f, 0.016051063728309112f, 0.19577101746269987f,
-0.09582821278789509f, -0.17163515371271834f, 0.2689602746203057f, -0.40807206244243144f };
-0.09582821278789509f, -0.17163515371271834f, 0.2689602746203057f, -0.40807206244243144f
};
constexpr std::array<double, 64> doubleInput01 = {
0.7224561488760388, 0.7385973866948313, -0.7270493193023231, -0.10016187172526334,
@ -236,7 +245,8 @@ constexpr std::array<double, 64> doubleInput01 = {
1.1666758495956464, -0.04382336233428379, 0.43819763320865857, 0.1740610608056625,
0.30473671729007396, -0.4065881153810866, -0.9784770671900811, -0.17674381857142665,
0.3493213284003123, -1.2540491577273034, 1.2597719140599792, 0.4198847510851298,
0.9612865621570132, -1.5614809857797225, -0.31416474166626646, -1.4741449502960542 };
0.9612865621570132, -1.5614809857797225, -0.31416474166626646, -1.4741449502960542
};
constexpr std::array<double, 64> doubleOutputLow01 = {
0.06567783171600353, 0.18655945645590016, 0.15368937959051, 0.05054483866245487,
@ -254,7 +264,8 @@ constexpr std::array<double, 64> doubleOutputLow01 = {
0.09460636244101792, 0.17948270447550216, 0.18270169192308128, 0.20514308375655024,
0.21137141199133533, 0.16368102816645502, 0.008005824629720673, -0.0984698603721838,
-0.06487738486552441, -0.13532948119242824, -0.11020387039992532, 0.06252925741325283,
0.1767213299964926, 0.0900270496677931, -0.09685475276689556, -0.24181840607858007 };
0.1767213299964926, 0.0900270496677931, -0.09685475276689556, -0.24181840607858007
};
constexpr std::array<double, 64> doubleOutputHigh01 = {
0.6567783171600353, 0.5520379302389311, -0.8807386988928331, -0.1507067103877182,
@ -272,7 +283,8 @@ constexpr std::array<double, 64> doubleOutputHigh01 = {
1.0720694871546286, -0.22330606680978596, 0.2554959412855773, -0.031082022950887744,
0.09336530529873863, -0.5702691435475415, -0.9864828918198018, -0.07827395819924285,
0.41419871326583674, -1.1187196765348753, 1.3699757844599045, 0.35735549367187697,
0.7845652321605207, -1.6515080354475156, -0.2173099888993709, -1.2323265442174742 };
0.7845652321605207, -1.6515080354475156, -0.2173099888993709, -1.2323265442174742
};
constexpr std::array<double, 64> doubleInput05 = {
-0.8247415510202276, -1.0299159073255513, 0.7689727513393745, -0.023063681797826918,
@ -290,7 +302,8 @@ constexpr std::array<double, 64> doubleInput05 = {
-0.6137810912250902, -0.7039958189789415, -1.33840097232278, 0.4786946763969468,
-0.46464793721558995, -1.7121509287301122, 0.7887828546774234, -0.902172963851904,
-0.2591368523894675, -0.9510361177022718, 0.5739217219088085, -0.25730420306720403,
0.41740839680521646, -2.0979181310103074, 1.1494564006889283, 0.5059893282486726 };
0.41740839680521646, -2.0979181310103074, 1.1494564006889283, 0.5059893282486726
};
constexpr std::array<double, 64> doubleOutputLow05 = {
-0.27491385034007587, -0.7098571028952849, -0.32360008629382064, 0.14076966108257558,
@ -308,7 +321,8 @@ constexpr std::array<double, 64> doubleOutputLow05 = {
0.02703177262292994, -0.4302483791937005, -0.8242150568318073, -0.5613071175858801,
-0.18242012613484115, -0.7864063306935144, -0.5699248015820677, -0.22777163691884944,
-0.46302715105340697, -0.5577333737150487, -0.311615923169504, 0.001667198557366828,
0.05392379743179307, -0.5421953122577658, -0.49688568085971485, 0.3861866826926287 };
0.05392379743179307, -0.5421953122577658, -0.49688568085971485, 0.3861866826926287
};
constexpr std::array<double, 64> doubleOutputHigh05 = {
-0.5498277006801517, -0.32005880443026635, 1.0925728376331951, -0.1638333428804025,
@ -326,7 +340,8 @@ constexpr std::array<double, 64> doubleOutputHigh05 = {
-0.6408128638480202, -0.27374743978524096, -0.5141859154909728, 1.040001793982827,
-0.2822278110807488, -0.9257445980365978, 1.358707656259491, -0.6744013269330547,
0.20389029866393948, -0.3933027439872231, 0.8855376450783126, -0.2589714016245709,
0.36348459937342337, -1.5557228187525416, 1.6463420815486431, 0.11980264555604392 };
0.36348459937342337, -1.5557228187525416, 1.6463420815486431, 0.11980264555604392
};
constexpr std::array<double, 64> doubleInput09 = {
-0.9629663717342508, 1.054078826032172, -1.0644939081323097, -0.05328934531304567,
@ -344,7 +359,8 @@ constexpr std::array<double, 64> doubleInput09 = {
0.7517403248613556, 1.1194691465807607, -1.1160170942539855, -1.0010374555669668,
2.1609909686692763, -0.07213993925443297, -0.5083174992310037, -0.7489925703250175,
0.5119124853257149, -0.33950253799120345, -0.26764774112191847, 0.10271208568438035,
-0.09893862035889031, -0.4154625911342657, 0.11272544601558693, -0.6895075000870634 };
-0.09893862035889031, -0.4154625911342657, 0.11272544601558693, -0.6895075000870634
};
constexpr std::array<double, 64> doubleOutputLow09 = {
-0.45614196555832937, 0.01915105911173487, -0.003925509462605503, -0.5296828837089897,
@ -362,7 +378,8 @@ constexpr std::array<double, 64> doubleOutputLow09 = {
1.3518864464016498, 0.9575142994410892, 0.0520306721253716, -1.0000768566454319,
0.496816040067124, 1.015603963410564, -0.22150068331359823, -0.6072258583851468,
-0.14426034859888792, 0.07407521986377441, -0.2836988048502276, -0.09305893177831953,
-0.003110407570995219, -0.24382743742154736, -0.15623482860471877, -0.28143543764463197 };
-0.003110407570995219, -0.24382743742154736, -0.15623482860471877, -0.28143543764463197
};
constexpr std::array<double, 64> doubleOutputHigh09 = {
-0.5068244061759215, 1.034927766920437, -1.0605683986697043, 0.47639353839594406,
@ -380,7 +397,8 @@ constexpr std::array<double, 64> doubleOutputHigh09 = {
-0.6001461215402942, 0.16195484713967145, -1.1680477663793571, -0.0009605989215348831,
1.6641749286021523, -1.087743902664997, -0.28681681591740543, -0.14176671193987078,
0.6561728339246028, -0.41357775785497786, 0.016051063728309112, 0.19577101746269987,
-0.09582821278789509, -0.17163515371271834, 0.2689602746203057, -0.40807206244243144 };
-0.09582821278789509, -0.17163515371271834, 0.2689602746203057, -0.40807206244243144
};
TEST_CASE("[OnePoleFilter] Tests")
{

View file

@ -5,6 +5,7 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "sfizz/Tuning.h"
#include "sfizz/Opcode.h"
#include "sfizz/SfzHelpers.h"
#include "cxxopts.hpp"
#include <string>