diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index c3649054..2d96835a 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -432,7 +432,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode) setValueFromOpcode(opcode, ampKeytrack, Default::ampKeytrackRange); break; case hash("amp_veltrack"): - setValueFromOpcode(opcode, ampVeltrack, Default::ampVeltrackRange); + if (auto value = readOpcode(opcode.value, Default::ampVeltrackRange)) + ampVeltrack = normalizePercents(*value); break; case hash("amp_random"): setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange); @@ -1517,19 +1518,14 @@ float sfz::Region::velocityCurve(float velocity) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); - float gain { 1.0f }; - if (velCurve) { // Custom velocity curve - return velCurve->evalNormalized(velocity); - } else { // Standard velocity curve - // FIXME: Maybe there's a prettier way to check the boundaries? - const float gaindB = [&]() { - if (ampVeltrack >= 0) - return velocity == 0.0f ? -90.0f : 40 * std::log(velocity) / std::log(10.0f); - else - return velocity == 1.0f ? -90.0f : 40 * std::log(1 - velocity) / std::log(10.0f); - }(); - gain *= db2mag( gaindB * std::abs(ampVeltrack) / sfz::Default::ampVeltrackRange.getEnd()); - } + float gain; + if (velCurve) + gain = velCurve->evalNormalized(velocity); + else + gain = velocity * velocity; + + gain = std::fabs(ampVeltrack) * (1.0f - gain); + gain = (ampVeltrack < 0) ? gain : (1.0f - gain); return gain; } diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 4bc1a91f..4935a85e 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -331,7 +331,7 @@ struct Region { float position { normalizePercents(Default::position) }; // position uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter float ampKeytrack { Default::ampKeytrack }; // amp_keytrack - float ampVeltrack { Default::ampVeltrack }; // amp_keytrack + float ampVeltrack { normalizePercents(Default::ampVeltrack) }; // amp_keytrack std::vector> velocityPoints; // amp_velcurve_N absl::optional velCurve {}; float ampRandom { Default::ampRandom }; // amp_random diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 3c15addf..9f095c4c 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -568,7 +568,8 @@ void sfz::Synth::finalizeSfzLoad() if (!region->velocityPoints.empty()) region->velCurve = Curve::buildFromVelcurvePoints( - region->velocityPoints, Curve::Interpolator::Linear, region->ampVeltrack < 0.0f); + region->velocityPoints, Curve::Interpolator::Linear); + region->registerPitchWheel(0); region->registerAftertouch(0); region->registerTempo(2.0f); diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 051a4fe5..9ea9b924 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -692,15 +692,15 @@ TEST_CASE("[Region] Parsing opcodes") SECTION("amp_veltrack") { - REQUIRE(region.ampVeltrack == 100.0f); + REQUIRE(region.ampVeltrack == 1.0f); region.parseOpcode({ "amp_veltrack", "4.2" }); - REQUIRE(region.ampVeltrack == 4.2f); + REQUIRE(region.ampVeltrack == Approx(0.042f)); region.parseOpcode({ "amp_veltrack", "-4.2" }); - REQUIRE(region.ampVeltrack == -4.2f); + REQUIRE(region.ampVeltrack == Approx(-0.042f)); region.parseOpcode({ "amp_veltrack", "-123" }); - REQUIRE(region.ampVeltrack == -100.0f); + REQUIRE(region.ampVeltrack == -1.0f); region.parseOpcode({ "amp_veltrack", "132" }); - REQUIRE(region.ampVeltrack == 100.0f); + REQUIRE(region.ampVeltrack == 1.0f); } SECTION("amp_random") diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 7795db93..8f0e94a5 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -448,16 +448,89 @@ TEST_CASE("[Synth] velcurve") amp_velcurve_064=1 sample=*sine amp_velcurve_064=1 amp_veltrack=-100 sample=*sine )"); - REQUIRE( synth.getRegionView(0)->velocityCurve(0_norm) == 0.0_a ); - REQUIRE( synth.getRegionView(0)->velocityCurve(32_norm) == Approx(0.5f).margin(1e-2) ); - REQUIRE( synth.getRegionView(0)->velocityCurve(64_norm) == 1.0_a ); - REQUIRE( synth.getRegionView(0)->velocityCurve(96_norm) == 1.0_a ); - REQUIRE( synth.getRegionView(0)->velocityCurve(127_norm) == 1.0_a ); - REQUIRE( synth.getRegionView(1)->velocityCurve(0_norm) == 1.0_a ); - REQUIRE( synth.getRegionView(1)->velocityCurve(32_norm) == 1.0_a ); - REQUIRE( synth.getRegionView(1)->velocityCurve(64_norm) == 1.0_a ); - REQUIRE( synth.getRegionView(1)->velocityCurve(96_norm) == Approx(0.5f).margin(1e-2) ); - REQUIRE( synth.getRegionView(1)->velocityCurve(127_norm) == 0.0_a ); + + struct VelocityData { float velocity, gain; bool exact; }; + + static const VelocityData veldata[] = { + { 0_norm, 0.0, true }, + { 32_norm, 0.5f, false }, + { 64_norm, 1.0, true }, + { 96_norm, 1.0, true }, + { 127_norm, 1.0, true }, + }; + + REQUIRE(synth.getNumRegions() == 2); + const sfz::Region* r1 = synth.getRegionView(0); + const sfz::Region* r2 = synth.getRegionView(1); + + for (const VelocityData& vd : veldata) { + if (vd.exact) { + REQUIRE(r1->velocityCurve(vd.velocity) == vd.gain); + REQUIRE(r2->velocityCurve(vd.velocity) == 1.0f - vd.gain); + } + else { + REQUIRE(r1->velocityCurve(vd.velocity) == Approx(vd.gain).margin(1e-2)); + REQUIRE(r2->velocityCurve(vd.velocity) == Approx(1.0f - vd.gain).margin(1e-2)); + } + } +} + +TEST_CASE("[Synth] veltrack") +{ + struct VelocityData { float velocity, dBGain; }; + struct VeltrackData { float veltrack; absl::Span veldata; }; + + // measured on ARIA + const VelocityData veldata25[] = { + { 127_norm, 0.0 }, + { 96_norm, -1 }, + { 64_norm, -1.8 }, + { 32_norm, -2.3 }, + { 1_norm, -2.5 }, + }; + const VelocityData veldata50[] = { + { 127_norm, 0.0 }, + { 96_norm, -2.1 }, + { 64_norm, -4.1 }, + { 32_norm, -5.5 }, + { 1_norm, -6.0 }, + }; + const VelocityData veldata75[] = { + { 127_norm, 0.0 }, + { 96_norm, -3.4 }, + { 64_norm, -7.2 }, + { 32_norm, -10.5 }, + { 1_norm, -12.0 }, + }; + const VelocityData veldata100[] = { + { 127_norm, 0.0 }, + { 96_norm, -4.9 }, + { 64_norm, -12.0 }, + { 32_norm, -24.0 }, + { 1_norm, -84.1 }, + }; + + const VeltrackData veltrackdata[] = { + { 25, veldata25 }, + { 50, veldata50 }, + { 75, veldata75 }, + { 100, veldata100 }, + }; + + for (const VeltrackData& vt : veltrackdata) { + sfz::Synth synth; + const std::string sfzCode = "sample=*sine amp_veltrack=" + + std::to_string(vt.veltrack); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/veltrack.sfz", sfzCode); + + REQUIRE(synth.getNumRegions() == 1); + const sfz::Region* r = synth.getRegionView(0); + + for (const VelocityData& vd : vt.veldata) { + float dBGain = 20.0f * std::log10(r->velocityCurve(vd.velocity)); + REQUIRE(dBGain == Approx(vd.dBGain).margin(0.1)); + } + } } TEST_CASE("[Synth] Region by identifier")