Merge pull request #263 from paulfd/gcsw

GCSW drumkit bugs
This commit is contained in:
Paul Ferrand 2020-05-30 15:28:08 +02:00 committed by GitHub
commit 014f9ff5af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 123 additions and 51 deletions

View file

@ -57,6 +57,35 @@ Curve Curve::buildCurveFromHeader(
return curve;
}
Curve Curve::buildFromVelcurvePoints(
absl::Span<const 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;

View file

@ -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(
absl::Span<const std::pair<uint8_t, float>> points,
Interpolator itp = Interpolator::Linear, bool invert = false);
/**
* @brief Number of predefined curves
*/

View file

@ -115,20 +115,23 @@ private:
template <typename ValueType, absl::enable_if_t<std::is_integral<ValueType>::value, int> = 0>
inline absl::optional<ValueType> readOpcode(absl::string_view value, const Range<ValueType>& validRange)
{
int64_t returnedValue;
if (!absl::SimpleAtoi(value, &returnedValue)) {
float floatValue;
if (!absl::SimpleAtof(value, &floatValue))
return {};
returnedValue = static_cast<int64_t>(floatValue);
}
const auto numberEnd = value.find_first_not_of("-1234567890.");
value = value.substr(0, numberEnd);
if (returnedValue > std::numeric_limits<ValueType>::max())
returnedValue = std::numeric_limits<ValueType>::max();
if (returnedValue < std::numeric_limits<ValueType>::min())
returnedValue = std::numeric_limits<ValueType>::min();
int64_t returnedValue;
if (!absl::SimpleAtoi(value, &returnedValue)) {
float floatValue;
if (!absl::SimpleAtof(value, &floatValue))
return absl::nullopt;
returnedValue = static_cast<int64_t>(floatValue);
}
return validRange.clamp(static_cast<ValueType>(returnedValue));
if (returnedValue > std::numeric_limits<ValueType>::max())
returnedValue = std::numeric_limits<ValueType>::max();
if (returnedValue < std::numeric_limits<ValueType>::min())
returnedValue = std::numeric_limits<ValueType>::min();
return validRange.clamp(static_cast<ValueType>(returnedValue));
}
/**
@ -144,6 +147,9 @@ inline absl::optional<ValueType> readOpcode(absl::string_view value, const Range
template <typename ValueType, absl::enable_if_t<std::is_floating_point<ValueType>::value, int> = 0>
inline absl::optional<ValueType> readOpcode(absl::string_view value, const Range<ValueType>& validRange)
{
const auto numberEnd = value.find_first_not_of("-1234567890.");
value = value.substr(0, numberEnd);
float returnedValue;
if (!absl::SimpleAtof(value, &returnedValue))
return absl::nullopt;

View file

@ -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 = [&]() {

View file

@ -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 };

View file

@ -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);

View file

@ -346,14 +346,12 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines")
REQUIRE( synth.getNumRegions() == 3 );
#if 0
// Note: test checked to be wrong under Sforzando 1.961
// It is the shorter matching $-variable which matches among both.
// The rest of the variable name creates some trailing junk text
// which Sforzando accepts without warning. (eg. `key=52Edge`)
REQUIRE( synth.getRegionView(0)->keyRange.getStart() == 52 );
REQUIRE( synth.getRegionView(0)->keyRange.getEnd() == 52 );
#endif
// which Sforzando accepts without warning. (eg. `key=57Edge`)
REQUIRE( synth.getRegionView(0)->keyRange.getStart() == 57 );
REQUIRE( synth.getRegionView(0)->keyRange.getEnd() == 57 );
REQUIRE( synth.getRegionView(1)->keyRange.getStart() == 57 );
REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 );

View file

@ -246,3 +246,20 @@ TEST_CASE("[Opcode] Normalization")
REQUIRE(sfz::Opcode("SaMpLe", "").cleanUp(sfz::kOpcodeScopeRegion).opcode == "sample");
}
TEST_CASE("[Opcode] readOpcode")
{
REQUIRE( sfz::readOpcode("16", sfz::Range<uint8_t>(0, 100)).value() == 16 );
REQUIRE( sfz::readOpcode("110", sfz::Range<uint8_t>(0, 100)).value() == 100 );
REQUIRE( sfz::readOpcode("-1", sfz::Range<uint8_t>(0, 100)).value() == 0 );
REQUIRE( sfz::readOpcode("12.5", sfz::Range<int>(-100, 100)).value() == 12 );
REQUIRE( sfz::readOpcode("-40", sfz::Range<int>(-100, 100)).value() == -40 );
REQUIRE( sfz::readOpcode("-140", sfz::Range<int>(-100, 100)).value() == -100 );
REQUIRE( sfz::readOpcode("12.5", sfz::Range<float>(0.0f, 100.0f)).value() == 12.5_a );
REQUIRE( sfz::readOpcode("-22.5", sfz::Range<float>(-20.0f, 100.0f)).value() == -20.0_a );
REQUIRE( sfz::readOpcode("150.5", sfz::Range<float>(-20.0f, 100.0f)).value() == 100.0_a );
REQUIRE( sfz::readOpcode("50.25garbage", sfz::Range<float>(-20.0f, 100.0f)).value() == 50.25_a );
REQUIRE( sfz::readOpcode("50.25garbage", sfz::Range<int>(-20, 100)).value() == 50 );
REQUIRE( !sfz::readOpcode("garbage50.25", sfz::Range<int>(-20, 100)) );
REQUIRE( !sfz::readOpcode("garbage", sfz::Range<int>(-20, 100)) );
}

View file

@ -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")

View file

@ -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 );
}

View file

@ -0,0 +1,2 @@
<region> amp_velcurve_064=1 sample=*sine
<region> amp_velcurve_064=1 amp_veltrack=-100 sample=*sine