S-VIEW-9: velocity->amp transfer curve (pure velocity_curve module + zones-payload v7 + Voice::start apply)
Default flat y=1 (R10-F1 Option A) replaces the linear velocity/127 at note-on — a deliberate, non-back-compat behavior change; v1-v6 blobs lift to the flat default.
This commit is contained in:
+43
-10
@@ -422,6 +422,10 @@ static void testNoteOffReleasesNewestSameNote() {
|
||||
sd.play.adsr = flatAdsr();
|
||||
sd.play.adsr.releaseFrames = 10; // short but non-zero so voice stays active through release
|
||||
Keymap km = Keymap::singleSampleChromatic(sd);
|
||||
// A LINEAR velocity curve keeps the two velocities distinguishable (velocity/127). The default
|
||||
// flat y=1 curve (S-VIEW-9 R10-F1) would render both at unity, collapsing the distinction this
|
||||
// note-off-selection test relies on — so we opt this zone back to the linear response.
|
||||
km.zones[0].velocityCurve = vst::VelocityCurve::linear();
|
||||
VoiceEngine eng(8, km);
|
||||
|
||||
std::size_t first = eng.noteOn(60, velOld); // older voice, lower gain
|
||||
@@ -728,33 +732,60 @@ static void testStartAfterLoopEndWrapsIntoLoop() {
|
||||
// velocity -> volume.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void testVelocityToVolume() {
|
||||
// S-VIEW-9 BEHAVIOR CHANGE (R10-F1 Option A): the DEFAULT velocity curve on a KeyZone is now flat
|
||||
// y=1, so EVERY velocity plays at unity — NOT the old linear velocity/127. singleSampleChromatic
|
||||
// builds a zone with the flat default, so the DC-1 sample renders 1.0 at any velocity.
|
||||
static void testVelocityDefaultCurveIsFlatUnity() {
|
||||
Keymap km = Keymap::singleSampleChromatic(dcSample(100, 60)); // DC 1.0, flat default curve
|
||||
for (int vel : {1, 64, 100, 127}) {
|
||||
VoiceEngine eng(1, km);
|
||||
eng.noteOn(60, vel);
|
||||
std::vector<AudioSample> out;
|
||||
eng.render(out, 1);
|
||||
CHECK(approx(out[0], 1.0, 1e-4)); // flat y=1: any velocity -> unity gain
|
||||
}
|
||||
}
|
||||
|
||||
// A LINEAR curve on the zone reproduces the pre-r10 velocity/127 ramp exactly — proving the curve
|
||||
// (not a hardcoded map) drives the gain, and that eval is applied at note-on.
|
||||
static void testVelocityLinearCurveReproducesRamp() {
|
||||
Keymap km = Keymap::singleSampleChromatic(dcSample(100, 60)); // DC 1.0
|
||||
// Full velocity -> full gain; half velocity -> ~half gain (flat envelope so the
|
||||
// rendered value is exactly velocity/127 on a DC-1 sample).
|
||||
km.zones[0].velocityCurve = vst::VelocityCurve::linear();
|
||||
{
|
||||
VoiceEngine eng(1, km);
|
||||
eng.noteOn(60, 127);
|
||||
std::vector<AudioSample> out;
|
||||
eng.render(out, 1);
|
||||
std::vector<AudioSample> out; eng.render(out, 1);
|
||||
CHECK(approx(out[0], 1.0, 1e-4));
|
||||
}
|
||||
{
|
||||
VoiceEngine eng(1, km);
|
||||
eng.noteOn(60, 64);
|
||||
std::vector<AudioSample> out;
|
||||
eng.render(out, 1);
|
||||
std::vector<AudioSample> out; eng.render(out, 1);
|
||||
CHECK(approx(out[0], 64.0 / 127.0, 1e-4));
|
||||
}
|
||||
{
|
||||
VoiceEngine eng(1, km);
|
||||
eng.noteOn(60, 1);
|
||||
std::vector<AudioSample> out;
|
||||
eng.render(out, 1);
|
||||
std::vector<AudioSample> out; eng.render(out, 1);
|
||||
CHECK(approx(out[0], 1.0 / 127.0, 1e-4));
|
||||
}
|
||||
}
|
||||
|
||||
// A shaped curve (a single interior knot) drives the gain through eval — a mid velocity reads the
|
||||
// curve's shaped value, not the linear one. Proves the whole curve, not just the endpoints, applies.
|
||||
static void testVelocityShapedCurveDrivesGain() {
|
||||
Keymap km = Keymap::singleSampleChromatic(dcSample(100, 60)); // DC 1.0
|
||||
vst::VelocityCurve curve = vst::VelocityCurve::linear();
|
||||
curve.addPoint(64.0, 0.9); // pull the mid-velocity response UP to 0.9
|
||||
km.zones[0].velocityCurve = curve;
|
||||
VoiceEngine eng(1, km);
|
||||
eng.noteOn(60, 64);
|
||||
std::vector<AudioSample> out; eng.render(out, 1);
|
||||
// At exactly velocity 64 the curve passes through the knot -> gain 0.9 (well above the linear
|
||||
// 64/127 ~= 0.504), so the rendered DC value is the shaped 0.9.
|
||||
CHECK(approx(out[0], 0.9, 1e-4));
|
||||
}
|
||||
|
||||
// Two voices summed: polyphony mixes additively.
|
||||
static void testPolyphonyMixesAdditively() {
|
||||
Keymap km = Keymap::singleSampleChromatic(dcSample(100, 60)); // DC 1.0
|
||||
@@ -1348,7 +1379,9 @@ int main() {
|
||||
testStartFrameOutOfRangeClampsToZero();
|
||||
testStartFrameWithLoop();
|
||||
testStartAfterLoopEndWrapsIntoLoop();
|
||||
testVelocityToVolume();
|
||||
testVelocityDefaultCurveIsFlatUnity();
|
||||
testVelocityLinearCurveReproducesRamp();
|
||||
testVelocityShapedCurveDrivesGain();
|
||||
testPolyphonyMixesAdditively();
|
||||
testChannelCount();
|
||||
testStereoRenderKeepsChannelsDistinct();
|
||||
|
||||
Reference in New Issue
Block a user