Add flex eg tests, move test helpers around, and add a more verbose mod matrix graph output
This commit is contained in:
parent
f969761b5a
commit
f317e2f6b9
7 changed files with 330 additions and 53 deletions
|
|
@ -71,22 +71,22 @@ std::string ModKey::toString() const
|
|||
" {curve=", params_.curve, ", smooth=", params_.smooth,
|
||||
", value=", params_.value, ", step=", params_.step, "}");
|
||||
case ModId::Envelope:
|
||||
return absl::StrCat("EG ", 1 + params_.N);
|
||||
return absl::StrCat("EG ", 1 + params_.N, " {region=", region_.number(), "}");
|
||||
case ModId::LFO:
|
||||
return absl::StrCat("LFO ", 1 + params_.N);
|
||||
return absl::StrCat("LFO ", 1 + params_.N, " {region=", region_.number(), "}");
|
||||
|
||||
case ModId::Amplitude:
|
||||
return "Amplitude";
|
||||
return absl::StrCat("Amplitude", " {region=", region_.number(), "}");
|
||||
case ModId::Pan:
|
||||
return "Pan";
|
||||
return absl::StrCat("Pan", " {region=", region_.number(), "}");
|
||||
case ModId::Width:
|
||||
return "Width";
|
||||
return absl::StrCat("Width", " {region=", region_.number(), "}");
|
||||
case ModId::Position:
|
||||
return "Position";
|
||||
return absl::StrCat("Position", " {region=", region_.number(), "}");
|
||||
case ModId::Pitch:
|
||||
return "Pitch";
|
||||
return absl::StrCat("Pitch", " {region=", region_.number(), "}");
|
||||
case ModId::Volume:
|
||||
return "Volume";
|
||||
return absl::StrCat("Volume", " {region=", region_.number(), "}");
|
||||
|
||||
default:
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "sfizz/ADSREnvelope.h"
|
||||
#include "TestHelpers.h"
|
||||
#include "catch2/catch.hpp"
|
||||
#include <absl/algorithm/container.h>
|
||||
#include <absl/types/span.h>
|
||||
|
|
@ -13,21 +14,6 @@
|
|||
#include <iostream>
|
||||
using namespace Catch::literals;
|
||||
|
||||
template <class Type>
|
||||
inline bool approxEqual(absl::Span<const Type> lhs, absl::Span<const Type> rhs, Type eps = 1e-3)
|
||||
{
|
||||
if (lhs.size() != rhs.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < rhs.size(); ++i)
|
||||
if (rhs[i] != Approx(lhs[i]).epsilon(eps)) {
|
||||
std::cerr << lhs[i] << " != " << rhs[i] << " at index " << i << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CASE("[ADSREnvelope] Basic state")
|
||||
{
|
||||
sfz::ADSREnvelope<float> envelope;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ set(SFIZZ_TEST_SOURCES
|
|||
# If we're tweaking the curves this kind of tests does not make sense
|
||||
# Use integration tests with comparison curves
|
||||
# ADSREnvelopeT.cpp
|
||||
FlexEGT.cpp
|
||||
EventEnvelopesT.cpp
|
||||
MainT.cpp
|
||||
SynthT.cpp
|
||||
|
|
|
|||
263
tests/FlexEGT.cpp
Normal file
263
tests/FlexEGT.cpp
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
// 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/Synth.h"
|
||||
#include "sfizz/FlexEnvelope.h"
|
||||
#include "catch2/catch.hpp"
|
||||
#include "TestHelpers.h"
|
||||
using namespace Catch::literals;
|
||||
using namespace sfz::literals;
|
||||
|
||||
TEST_CASE("[FlexEG] Values")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> sample=*sine
|
||||
eg1_amplitude=1
|
||||
eg1_time1=.1 eg1_level1=.25
|
||||
eg1_time2=.2 eg1_level2=1
|
||||
eg1_time3=.2 eg1_level3=.5 eg1_sustain=3
|
||||
eg1_time4=.4 eg1_level4=1
|
||||
)");
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
const auto* region = synth.getRegionView(0);
|
||||
REQUIRE( region->flexEGs.size() == 1 );
|
||||
const auto& egDescription = region->flexEGs[0];
|
||||
REQUIRE( egDescription.points.size() == 5 );
|
||||
REQUIRE( egDescription.points[0].time == 0.0_a );
|
||||
REQUIRE( egDescription.points[0].level == 0.0_a );
|
||||
REQUIRE( egDescription.points[1].time == .1_a );
|
||||
REQUIRE( egDescription.points[1].level == .25_a );
|
||||
REQUIRE( egDescription.points[2].time == .2_a );
|
||||
REQUIRE( egDescription.points[2].level == 1.0_a );
|
||||
REQUIRE( egDescription.points[3].time == .2_a );
|
||||
REQUIRE( egDescription.points[3].level == .5_a );
|
||||
REQUIRE( egDescription.points[4].time == .4_a );
|
||||
REQUIRE( egDescription.points[4].level == 1.0_a );
|
||||
REQUIRE( egDescription.sustain == 3 );
|
||||
REQUIRE(synth.getResources().modMatrix.toDotGraph() == createReferenceGraph({
|
||||
R"("EG 1 {region=0}" -> "Amplitude {region=0}")",
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_CASE("[FlexEG] Default values")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> sample=*sine
|
||||
eg3_time2=.1 eg3_level2=.25
|
||||
)");
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
const auto* region = synth.getRegionView(0);
|
||||
REQUIRE( region->flexEGs.size() == 3 );
|
||||
REQUIRE( region->flexEGs[0].points.size() == 0 );
|
||||
REQUIRE( region->flexEGs[1].points.size() == 0 );
|
||||
const auto& egDescription = region->flexEGs[2];
|
||||
REQUIRE( egDescription.points.size() == 3 );
|
||||
REQUIRE( egDescription.points[0].time == 0.0_a );
|
||||
REQUIRE( egDescription.points[0].level == 0.0_a );
|
||||
REQUIRE( egDescription.points[1].time == 0.0_a );
|
||||
REQUIRE( egDescription.points[1].level == 0.0_a );
|
||||
REQUIRE( egDescription.points[2].time == .1_a );
|
||||
REQUIRE( egDescription.points[2].level == .25_a );
|
||||
REQUIRE( synth.getResources().modMatrix.toDotGraph() == createReferenceGraph({}) );
|
||||
}
|
||||
|
||||
TEST_CASE("[FlexEG] Connections")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> sample=*sine eg1_amplitude=1 eg1_time1=.1 eg1_level1=.25
|
||||
<region> sample=*sine eg1_pan=1 eg1_time1=.1 eg1_level1=.25
|
||||
<region> sample=*sine eg1_width=1 eg1_time1=.1 eg1_level1=.25
|
||||
<region> sample=*sine eg1_position=1 eg1_time1=.1 eg1_level1=.25
|
||||
<region> sample=*sine eg1_pitch=1 eg1_time1=.1 eg1_level1=.25
|
||||
<region> sample=*sine eg1_volume=1 eg1_time1=.1 eg1_level1=.25
|
||||
)");
|
||||
REQUIRE(synth.getNumRegions() == 6);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs[0].points.size() == 2 );
|
||||
REQUIRE( synth.getResources().modMatrix.toDotGraph() == createReferenceGraph({
|
||||
R"("EG 1 {region=0}" -> "Amplitude {region=0}")",
|
||||
R"("EG 1 {region=1}" -> "Pan {region=1}")",
|
||||
R"("EG 1 {region=2}" -> "Width {region=2}")",
|
||||
R"("EG 1 {region=3}" -> "Position {region=3}")",
|
||||
R"("EG 1 {region=4}" -> "Pitch {region=4}")",
|
||||
R"("EG 1 {region=5}" -> "Volume {region=5}")",
|
||||
}, 6));
|
||||
}
|
||||
|
||||
TEST_CASE("[FlexEG] Coarse numerical envelope test (No release)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> sample=*sine
|
||||
eg1_time1=.5 eg1_level1=.25
|
||||
eg1_time2=0.5 eg1_level2=1
|
||||
eg1_sustain=2
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
std::vector<float> output;
|
||||
envelope.setSampleRate(10);
|
||||
output.resize(16);
|
||||
envelope.start(1);
|
||||
envelope.process(absl::MakeSpan(output));
|
||||
REQUIRE( output[0] == 0.0_a ); // Trigger delay
|
||||
REQUIRE( output[5] == 0.25_a ); // 0.25 at time == 0.5s (5 samples at samplerate 10 + trigger delay)
|
||||
REQUIRE( output[10] == 1.0_a ); // 1 at time == 1s (5 samples at samplerate 10 + trigger delay)
|
||||
REQUIRE( output[15] == 1.0_a ); // sustaining
|
||||
}
|
||||
|
||||
TEST_CASE("[FlexEG] Detailed numerical envelope test")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> sample=*sine
|
||||
eg1_time1=.5 eg1_level1=.25
|
||||
eg1_time2=0.5 eg1_level2=1
|
||||
eg1_sustain=2
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
std::vector<float> output;
|
||||
std::vector<float> expected { 0.0f, 0.05f, 0.1f, 0.15f, 0.2f, 0.25f, 0.4f, 0.55f, 0.7f, 0.85f, 1.0f, 1.0f, 1.0f };
|
||||
output.resize(expected.size());
|
||||
envelope.setSampleRate(10);
|
||||
envelope.start(1);
|
||||
envelope.process(absl::MakeSpan(output));
|
||||
REQUIRE( approxEqual<float>(output, expected) );
|
||||
}
|
||||
|
||||
TEST_CASE("[FlexEG] Coarse numerical envelope test (with release)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> sample=*sine
|
||||
eg1_time1=.5 eg1_level1=.25
|
||||
eg1_time2=0.5 eg1_level2=1
|
||||
eg1_sustain=2
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
std::vector<float> output;
|
||||
envelope.setSampleRate(10);
|
||||
output.resize(32);
|
||||
envelope.start(1);
|
||||
envelope.release(15);
|
||||
envelope.process(absl::MakeSpan(output));
|
||||
REQUIRE( output[0] == 0.0_a ); // Trigger delay
|
||||
REQUIRE( output[5] == 0.25_a ); // 0.25 at time == 0.5s (5 samples at samplerate 10 + trigger delay)
|
||||
REQUIRE( output[10] == 1.0_a ); // 1 at time == 1s (5 samples at samplerate 10 + trigger delay)
|
||||
REQUIRE( output[15] == 1.0_a ); // sustaining
|
||||
REQUIRE( output[16] == 0.0_a ); // released
|
||||
REQUIRE( output[31] == 0.0_a ); // released
|
||||
}
|
||||
|
||||
TEST_CASE("[FlexEG] Detailed numerical envelope test (with release and release ramp)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> sample=*sine
|
||||
eg1_time1=.5 eg1_level1=.25
|
||||
eg1_time2=0.5 eg1_level2=1
|
||||
eg1_time3=0.5 eg1_level3=0
|
||||
eg1_sustain=2
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
std::vector<float> output;
|
||||
std::vector<float> expected {
|
||||
0.0f,
|
||||
0.05f, 0.1f, 0.15f, 0.2f, 0.25f,
|
||||
0.4f, 0.55f, 0.7f, 0.85f, 1.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
0.8f, 0.6f, 0.4f, 0.2f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f
|
||||
};
|
||||
output.resize(expected.size());
|
||||
envelope.setSampleRate(10);
|
||||
envelope.start(1);
|
||||
envelope.release(15);
|
||||
envelope.process(absl::MakeSpan(output));
|
||||
REQUIRE( approxEqual<float>(output, expected) );
|
||||
}
|
||||
|
||||
TEST_CASE("[FlexEG] Coarse numerical envelope test (with shapes)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> sample=*sine
|
||||
eg1_time1=.5 eg1_level1=.25 eg1_shape1=2
|
||||
eg1_time2=0.5 eg1_level2=1 eg1_shape2=0.5
|
||||
eg1_sustain=2
|
||||
eg1_time3=0.5 eg1_level3=0 eg1_shape3=4
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
std::vector<float> output;
|
||||
envelope.setSampleRate(10);
|
||||
output.resize(32);
|
||||
envelope.start(1);
|
||||
envelope.release(15);
|
||||
envelope.process(absl::MakeSpan(output));
|
||||
REQUIRE( output[0] == 0.0_a ); // Trigger delay
|
||||
REQUIRE( output[5] == 0.25_a ); // 0.25 at time == 0.5s (5 samples at samplerate 10 + trigger delay)
|
||||
REQUIRE( output[10] == 1.0_a ); // 1 at time == 1s (5 samples at samplerate 10 + trigger delay)
|
||||
REQUIRE( output[15] == 1.0_a ); // sustaining
|
||||
REQUIRE( output[31] == 0.0_a ); // released
|
||||
}
|
||||
|
||||
TEST_CASE("[FlexEG] Detailed numerical envelope test (with shapes)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> sample=*sine
|
||||
eg1_time1=.5 eg1_level1=.25 eg1_shape1=2
|
||||
eg1_time2=0.5 eg1_level2=1 eg1_shape2=0.5
|
||||
eg1_time3=0.5 eg1_level3=0 eg1_shape3=4
|
||||
eg1_sustain=2
|
||||
)");
|
||||
sfz::FlexEnvelope envelope;
|
||||
REQUIRE(synth.getNumRegions() == 1);
|
||||
REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 );
|
||||
envelope.configure(&synth.getRegionView(0)->flexEGs[0]);
|
||||
std::vector<float> output;
|
||||
std::vector<float> expected {
|
||||
0.0f,
|
||||
0.01f, 0.04f, 0.09f, 0.16f, 0.25f,
|
||||
0.58f, 0.72f, 0.83f, 0.92f, 1.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
0.99f, 0.97f, 0.87f, 0.59f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f
|
||||
};
|
||||
output.resize(expected.size());
|
||||
envelope.setSampleRate(10);
|
||||
envelope.start(1);
|
||||
envelope.release(15);
|
||||
envelope.process(absl::MakeSpan(output));
|
||||
REQUIRE( approxEqual<float>(output, expected, 0.01f) );
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
#include "sfizz/modulations/ModId.h"
|
||||
#include "sfizz/modulations/ModKey.h"
|
||||
#include "sfizz/Synth.h"
|
||||
#include "TestHelpers.h"
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
TEST_CASE("[Modulations] Identifiers")
|
||||
|
|
@ -78,32 +79,6 @@ TEST_CASE("[Modulations] Display names")
|
|||
});
|
||||
}
|
||||
|
||||
static std::string createReferenceGraph(std::vector<std::string> lines)
|
||||
{
|
||||
const char* defaultConnections[] = {
|
||||
R"("Controller 7 {curve=4, smooth=10, value=100, step=0}" -> "Amplitude")",
|
||||
R"("Controller 10 {curve=1, smooth=10, value=100, step=0}" -> "Pan")"
|
||||
};
|
||||
|
||||
for (const char* line : defaultConnections)
|
||||
lines.push_back(line);
|
||||
|
||||
std::sort(lines.begin(), lines.end());
|
||||
|
||||
std::string graph;
|
||||
graph.reserve(1024);
|
||||
|
||||
graph += "digraph {\n";
|
||||
for (const std::string& line : lines) {
|
||||
graph.push_back('\t');
|
||||
graph += line;
|
||||
graph.push_back('\n');
|
||||
}
|
||||
graph += "}\n";
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
||||
TEST_CASE("[Modulations] Connection graph from SFZ")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
|
|
@ -118,9 +93,9 @@ width_oncc425=29
|
|||
|
||||
const std::string graph = synth.getResources().modMatrix.toDotGraph();
|
||||
REQUIRE(graph == createReferenceGraph({
|
||||
R"("Controller 20 {curve=3, smooth=0, value=59, step=0}" -> "Amplitude")",
|
||||
R"("Controller 42 {curve=0, smooth=32, value=71, step=0}" -> "Pitch")",
|
||||
R"("Controller 36 {curve=0, smooth=0, value=14.5, step=1.5}" -> "Pan")",
|
||||
R"("Controller 425 {curve=0, smooth=0, value=29, step=0}" -> "Width")",
|
||||
R"("Controller 20 {curve=3, smooth=0, value=59, step=0}" -> "Amplitude {region=0}")",
|
||||
R"("Controller 42 {curve=0, smooth=32, value=71, step=0}" -> "Pitch {region=0}")",
|
||||
R"("Controller 36 {curve=0, smooth=0, value=14.5, step=1.5}" -> "Pan {region=0}")",
|
||||
R"("Controller 425 {curve=0, smooth=0, value=29, step=0}" -> "Width {region=0}")",
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,3 +68,34 @@ unsigned numPlayingVoices(const sfz::Synth& synth)
|
|||
return !v->releasedOrFree();
|
||||
});
|
||||
}
|
||||
|
||||
std::string createReferenceGraph(std::vector<std::string> lines, int numRegions)
|
||||
{
|
||||
for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) {
|
||||
lines.push_back(absl::StrCat(
|
||||
R"("Controller 7 {curve=4, smooth=10, value=100, step=0}" -> "Amplitude {region=)",
|
||||
regionIdx,
|
||||
R"(}")"
|
||||
));
|
||||
lines.push_back(absl::StrCat(
|
||||
R"("Controller 10 {curve=1, smooth=10, value=100, step=0}" -> "Pan {region=)",
|
||||
regionIdx,
|
||||
R"(}")"
|
||||
));
|
||||
}
|
||||
|
||||
std::sort(lines.begin(), lines.end());
|
||||
|
||||
std::string graph;
|
||||
graph.reserve(1024);
|
||||
|
||||
graph += "digraph {\n";
|
||||
for (const std::string& line : lines) {
|
||||
graph.push_back('\t');
|
||||
graph += line;
|
||||
graph.push_back('\n');
|
||||
}
|
||||
graph += "}\n";
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -65,3 +65,24 @@ const std::vector<const sfz::Voice*> getPlayingVoices(const sfz::Synth& synth);
|
|||
* @return unsigned
|
||||
*/
|
||||
unsigned numPlayingVoices(const sfz::Synth& synth);
|
||||
|
||||
/**
|
||||
* @brief Create the dot graph representation from a list of strings
|
||||
*
|
||||
*/
|
||||
std::string createReferenceGraph(std::vector<std::string> lines, int numRegions = 1);
|
||||
|
||||
template <class Type>
|
||||
inline bool approxEqual(absl::Span<const Type> lhs, absl::Span<const Type> rhs, Type eps = 1e-3)
|
||||
{
|
||||
if (lhs.size() != rhs.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < rhs.size(); ++i)
|
||||
if (rhs[i] != Approx(lhs[i]).epsilon(eps)) {
|
||||
std::cerr << lhs[i] << " != " << rhs[i] << " at index " << i << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue