Correct a bug with sfz v1 velcurve
The velcurve logic now uses the same as sfz v2 curves
This commit is contained in:
parent
60436277eb
commit
5e74f910c6
8 changed files with 85 additions and 34 deletions
|
|
@ -57,6 +57,35 @@ Curve Curve::buildCurveFromHeader(
|
|||
return curve;
|
||||
}
|
||||
|
||||
Curve Curve::buildFromVelcurvePoints(
|
||||
const std::vector<std::pair<uint8_t, float>>& points,
|
||||
Interpolator itp, bool invert)
|
||||
{
|
||||
Curve curve;
|
||||
bool fillStatus[NumValues] = {};
|
||||
const Range<float> fullRange { -HUGE_VALF, +HUGE_VALF };
|
||||
|
||||
auto setPoint = [&curve, &fillStatus](int i, float x) {
|
||||
curve._points[i] = x;
|
||||
fillStatus[i] = true;
|
||||
};
|
||||
|
||||
if (invert) {
|
||||
setPoint(0, 1.0);
|
||||
setPoint(NumValues - 1, 0.0);
|
||||
} else {
|
||||
setPoint(0, 0.0);
|
||||
setPoint(NumValues - 1, 1.0);
|
||||
}
|
||||
|
||||
for (const auto& point: points) {
|
||||
setPoint(point.first, point.second);
|
||||
}
|
||||
|
||||
curve.fill(itp, fillStatus);
|
||||
return curve;
|
||||
}
|
||||
|
||||
Curve Curve::buildPredefinedCurve(int index)
|
||||
{
|
||||
Curve curve;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,17 @@ public:
|
|||
absl::Span<const Opcode> members,
|
||||
Interpolator itp = Interpolator::Linear, bool limit = false);
|
||||
|
||||
/**
|
||||
* @brief Build a curve based on sfz v1 "amp_velcurve_&" points
|
||||
*
|
||||
* @param points the points vector
|
||||
* @param itp kind of interpolator to fill between values
|
||||
* @param invert whether to invert the curve
|
||||
*/
|
||||
static Curve buildFromVelcurvePoints(
|
||||
const std::vector<std::pair<uint8_t, float>>& points,
|
||||
Interpolator itp = Interpolator::Linear, bool invert = false);
|
||||
|
||||
/**
|
||||
* @brief Number of predefined curves
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
return false;
|
||||
|
||||
if (value)
|
||||
velocityPoints.emplace_back(normalizeVelocity(opcode.parameters.back()), *value);
|
||||
velocityPoints.emplace_back(opcode.parameters.back(), *value);
|
||||
}
|
||||
break;
|
||||
case hash("xfin_lokey"):
|
||||
|
|
@ -1130,15 +1130,8 @@ float sfz::Region::velocityCurve(float velocity) const noexcept
|
|||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||
|
||||
float gain { 1.0f };
|
||||
if (velocityPoints.size() > 0) { // Custom velocity curve
|
||||
auto after = absl::c_find_if(velocityPoints, [velocity](const std::pair<float, float>& val) { return val.first >= velocity; });
|
||||
auto before = after == velocityPoints.begin() ? velocityPoints.begin() : after - 1;
|
||||
// Linear interpolation
|
||||
float relativePositionInSegment {
|
||||
(velocity - before->first) / (after->first - before->first)
|
||||
};
|
||||
float segmentEndpoints { after->second - before->second };
|
||||
gain *= relativePositionInSegment * segmentEndpoints;
|
||||
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 = [&]() {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
#include "CCMap.h"
|
||||
#include "Curve.h"
|
||||
#include "LeakDetector.h"
|
||||
#include "Defaults.h"
|
||||
#include "EGDescription.h"
|
||||
|
|
@ -304,7 +305,8 @@ struct Region {
|
|||
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
|
||||
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
||||
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
|
||||
std::vector<std::pair<float, float>> velocityPoints; // amp_velcurve_N
|
||||
std::vector<std::pair<uint8_t, float>> velocityPoints; // amp_velcurve_N
|
||||
absl::optional<Curve> velCurve {};
|
||||
float ampRandom { Default::ampRandom }; // amp_random
|
||||
Range<uint8_t> crossfadeKeyInRange { Default::crossfadeKeyInRange };
|
||||
Range<uint8_t> crossfadeKeyOutRange { Default::crossfadeKeyOutRange };
|
||||
|
|
|
|||
|
|
@ -315,26 +315,6 @@ void sfz::Synth::handleEffectOpcodes(const std::vector<Opcode>& rawMembers)
|
|||
bus.addEffect(std::move(fx));
|
||||
}
|
||||
|
||||
void addEndpointsToVelocityCurve(sfz::Region& region)
|
||||
{
|
||||
if (region.velocityPoints.size() > 0) {
|
||||
const auto velocityStart = sfz::Default::velocityRange.getStart();
|
||||
const auto velocityEnd = sfz::Default::velocityRange.getEnd();
|
||||
absl::c_sort(region.velocityPoints, [](const std::pair<float, float>& lhs, const std::pair<float, float>& rhs) { return lhs.first < rhs.first; });
|
||||
if (region.ampVeltrack > 0) {
|
||||
if (region.velocityPoints.front().first != velocityStart)
|
||||
region.velocityPoints.insert(region.velocityPoints.begin(), std::make_pair(velocityStart, velocityStart));
|
||||
if (region.velocityPoints.back().first != velocityEnd)
|
||||
region.velocityPoints.push_back(std::make_pair(velocityEnd, velocityEnd));
|
||||
} else {
|
||||
if (region.velocityPoints.front().first != velocityEnd)
|
||||
region.velocityPoints.insert(region.velocityPoints.begin(), std::make_pair(velocityEnd, velocityStart));
|
||||
if (region.velocityPoints.back().first != velocityStart)
|
||||
region.velocityPoints.push_back(std::make_pair(velocityStart, velocityEnd));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||
{
|
||||
clear();
|
||||
|
|
@ -484,7 +464,9 @@ void sfz::Synth::finalizeSfzLoad()
|
|||
}
|
||||
}
|
||||
|
||||
addEndpointsToVelocityCurve(*region);
|
||||
if (!region->velocityPoints.empty())
|
||||
region->velCurve = Curve::buildFromVelcurvePoints(
|
||||
region->velocityPoints, Curve::Interpolator::Linear, region->ampVeltrack < 0.0f);
|
||||
region->registerPitchWheel(0);
|
||||
region->registerAftertouch(0);
|
||||
region->registerTempo(2.0f);
|
||||
|
|
|
|||
|
|
@ -694,9 +694,13 @@ TEST_CASE("[Region] Parsing opcodes")
|
|||
SECTION("amp_velcurve")
|
||||
{
|
||||
region.parseOpcode({ "amp_velcurve_6", "0.4" });
|
||||
REQUIRE(region.velocityPoints.back() == std::make_pair<float, float>(6_norm, 0.4f));
|
||||
REQUIRE(region.velocityPoints.back() == std::pair<uint8_t, float>(6, 0.4f));
|
||||
region.parseOpcode({ "amp_velcurve_127", "-1.0" });
|
||||
REQUIRE(region.velocityPoints.back() == std::make_pair<float, float>(127_norm, 0.0f));
|
||||
REQUIRE(region.velocityPoints.back() == std::pair<uint8_t, float>(127, 0.0f));
|
||||
region.parseOpcode({ "amp_velcurve_008", "0.3" });
|
||||
REQUIRE(region.velocityPoints.back() == std::pair<uint8_t, float>(8, 0.3f));
|
||||
region.parseOpcode({ "amp_velcurve_064", "0.9" });
|
||||
REQUIRE(region.velocityPoints.back() == std::pair<uint8_t, float>(64, 0.9f));
|
||||
}
|
||||
|
||||
SECTION("xfin_lokey, xfin_hikey")
|
||||
|
|
|
|||
|
|
@ -434,3 +434,31 @@ TEST_CASE("[Synth] Basic curves")
|
|||
// Default linear
|
||||
REQUIRE( curves.getCurve(16).evalCC7(63) == Approx(63_norm) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Velocity points")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/velocity_endpoints.sfz");
|
||||
REQUIRE( !synth.getRegionView(0)->velocityPoints.empty());
|
||||
REQUIRE( synth.getRegionView(0)->velocityPoints[0].first == 64 );
|
||||
REQUIRE( synth.getRegionView(0)->velocityPoints[0].second == 1.0_a );
|
||||
REQUIRE( !synth.getRegionView(1)->velocityPoints.empty());
|
||||
REQUIRE( synth.getRegionView(1)->velocityPoints[0].first == 64 );
|
||||
REQUIRE( synth.getRegionView(1)->velocityPoints[0].second == 1.0_a );
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] velcurve")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/velocity_endpoints.sfz");
|
||||
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 );
|
||||
}
|
||||
|
|
|
|||
2
tests/TestFiles/velocity_endpoints.sfz
Normal file
2
tests/TestFiles/velocity_endpoints.sfz
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<region> amp_velcurve_064=1 sample=*sine
|
||||
<region> amp_velcurve_064=1 amp_veltrack=-100 sample=*sine
|
||||
Loading…
Add table
Reference in a new issue