instrument: one VELOCITY deck for all three velocity curves, bipolar and off by default for pitch and filter
Payload v12 appends the new velocity->pitch curve and folds the retired filter velAmount into its now-bipolar curve, so pre-v12 projects reopen sounding identical. Preview button takes a drawn play triangle.
This commit is contained in:
@@ -638,7 +638,8 @@ static void testResolvePlayCarriesTheFilterAndResolvesOnlyItsEnvelope() {
|
||||
st.filter.settings.driveNorm = 0.125f;
|
||||
st.filter.settings.morphLaw = reasampler::instrument::engine::filter::MorphLaw::HighNotchLow;
|
||||
st.filter.modAmount = -0.5;
|
||||
st.filter.velAmount = 0.25;
|
||||
st.filter.velocityCurve = VelocityCurve::fromPoints(
|
||||
{{0.0, 0.0}, {127.0, 0.25}}, reasampler::instrument::engine::CurveDomain::Bipolar);
|
||||
st.filter.keyTrack = 1.25;
|
||||
st.filter.env.attackSeconds = 0.01;
|
||||
st.filter.env.holdSeconds = 0.02;
|
||||
@@ -654,7 +655,11 @@ static void testResolvePlayCarriesTheFilterAndResolvesOnlyItsEnvelope() {
|
||||
CHECK(at48.filter.settings.driveNorm == 0.125f);
|
||||
CHECK(at48.filter.settings.morphLaw == reasampler::instrument::engine::filter::MorphLaw::HighNotchLow);
|
||||
CHECK(at48.filter.modAmount == -0.5);
|
||||
CHECK(at48.filter.velAmount == 0.25);
|
||||
// The transfer curve is dimensionless, so it crosses unchanged — asserted against the
|
||||
// straight line the two stored knots describe, not against the stored object.
|
||||
CHECK(at48.filter.velocityCurve.eval(0.0) == 0.0);
|
||||
CHECK(approx(at48.filter.velocityCurve.eval(127.0), 0.25));
|
||||
CHECK(approx(at48.filter.velocityCurve.eval(63.5), 0.125));
|
||||
CHECK(at48.filter.keyTrack == 1.25);
|
||||
CHECK(at48.filter.env.attackFrames == 480);
|
||||
CHECK(at48.filter.env.holdFrames == 960);
|
||||
@@ -672,9 +677,25 @@ static void testResolvePlayCarriesTheFilterAndResolvesOnlyItsEnvelope() {
|
||||
const PlayParams bare = resolvePlay(PlaySeconds{}, 48000);
|
||||
CHECK(!bare.filter.enabled);
|
||||
CHECK(bare.filter.modAmount == 0.0);
|
||||
CHECK(bare.filter.velAmount == 0.0);
|
||||
for (int v = 0; v <= 127; ++v) CHECK(bare.filter.velocityCurve.eval(v) == 0.0);
|
||||
CHECK(bare.filter.keyTrack == 0.0);
|
||||
CHECK(bare.filter.env.sustainLevel == 1.0);
|
||||
// The velocity->pitch curve rides the same boundary and is off by the same default.
|
||||
for (int v = 0; v <= 127; ++v) CHECK(bare.pitchVelocityCurve.eval(v) == 0.0);
|
||||
}
|
||||
|
||||
// The velocity->pitch curve is dimensionless like the filter's, so resolvePlay carries it
|
||||
// across the seconds->frames boundary untouched at any rate.
|
||||
static void testResolvePlayCarriesThePitchVelocityCurve() {
|
||||
PlaySeconds st;
|
||||
st.pitchVelocityCurve = VelocityCurve::fromPoints(
|
||||
{{0.0, -1.0}, {127.0, 1.0}}, reasampler::instrument::engine::CurveDomain::Bipolar);
|
||||
for (const int rate : {44100, 96000}) {
|
||||
const PlayParams p = resolvePlay(st, rate);
|
||||
CHECK(p.pitchVelocityCurve.eval(0.0) == -1.0);
|
||||
CHECK(p.pitchVelocityCurve.eval(127.0) == 1.0);
|
||||
CHECK(approx(p.pitchVelocityCurve.eval(63.5), 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
static void testResolvePlayRoundsAndFloorsNegatives() {
|
||||
@@ -937,6 +958,7 @@ int main() {
|
||||
testLegacyLiftDecision();
|
||||
testResolvePlayConvertsWallClockAtTheRate();
|
||||
testResolvePlayCarriesTheFilterAndResolvesOnlyItsEnvelope();
|
||||
testResolvePlayCarriesThePitchVelocityCurve();
|
||||
testResolvePlayRoundsAndFloorsNegatives();
|
||||
testMigratedFadeStretchesWhenTheDecodeRateDiffersFromTheProjectRate();
|
||||
testResolveCaptureUsesIntrinsicsWhenNoOverride();
|
||||
|
||||
Reference in New Issue
Block a user