S15/S16: Gate(AHDSR)/Trigger play modes + Varispeed/Preserve pitch engines + AD pitch envelope
Per-zone play params on SampleData; hand-rolled pure pitch_shift OLA for Preserve (WDL drags windows.h); zone-payload v3 tail; RT-safe pre-warmed shifters + Preserve voice cap.
This commit is contained in:
@@ -979,6 +979,113 @@ static void testComponentStateV4StereoWithZoneOverridesRoundTrip() {
|
||||
!back.map.zones[1].startPoint.has_value());
|
||||
}
|
||||
|
||||
// --- S15/S16 zone-payload v3: per-zone play params round-trip + back-compat lift -------------
|
||||
|
||||
static void testPlayParamsRoundTrip() {
|
||||
// A zone carrying explicit S15/S16 play params (Trigger mode, hold, fades, Varispeed engine,
|
||||
// pitch env on) must round-trip ALL fields losslessly through the payload-v3 tail.
|
||||
PerformanceMap m;
|
||||
PerformanceZone z = zone("lead", 20, 100, /*override=*/55);
|
||||
z.play.playMode = PlayMode::Trigger;
|
||||
z.play.adsr.holdFrames = 1234;
|
||||
z.play.trigger.lengthFraction = 0.375;
|
||||
z.play.trigger.fadeInFrames = 64;
|
||||
z.play.trigger.fadeOutFrames = 128;
|
||||
z.play.pitchEngine = PitchEngine::Varispeed;
|
||||
z.play.pitchEnv.enabled = true;
|
||||
z.play.pitchEnv.attackFrames = 10;
|
||||
z.play.pitchEnv.decayFrames = 500;
|
||||
z.play.pitchEnv.peakSemitones = -7.5;
|
||||
m.zones.push_back(z);
|
||||
const PerformanceMap back = deserializePerformance(serializePerformance(m));
|
||||
CHECK(back.zones.size() == 1);
|
||||
if (back.zones.size() != 1) return;
|
||||
const ZonePlayParams& p = back.zones[0].play;
|
||||
CHECK(p.playMode == PlayMode::Trigger);
|
||||
CHECK(p.adsr.holdFrames == 1234);
|
||||
CHECK(p.trigger.lengthFraction == 0.375); // exact double round-trip
|
||||
CHECK(p.trigger.fadeInFrames == 64);
|
||||
CHECK(p.trigger.fadeOutFrames == 128);
|
||||
CHECK(p.pitchEngine == PitchEngine::Varispeed);
|
||||
CHECK(p.pitchEnv.enabled == true);
|
||||
CHECK(p.pitchEnv.attackFrames == 10);
|
||||
CHECK(p.pitchEnv.decayFrames == 500);
|
||||
CHECK(p.pitchEnv.peakSemitones == -7.5); // exact double round-trip
|
||||
}
|
||||
|
||||
static void testPlayParamsComposeWithLoopStart() {
|
||||
// S11 (loop/start) x S15/S16 (play params) tails co-exist per zone: both round-trip together.
|
||||
PerformanceMap m;
|
||||
PerformanceZone z = zone("pad", 0, 60);
|
||||
SampleLoop lp; lp.hasLoop = true; lp.start = 111; lp.end = 222;
|
||||
z.loopOverride = lp;
|
||||
z.startPoint = 333;
|
||||
z.play.playMode = PlayMode::Gate;
|
||||
z.play.adsr.holdFrames = 999;
|
||||
z.play.pitchEngine = PitchEngine::Preserve;
|
||||
m.zones.push_back(z);
|
||||
const PerformanceMap back = deserializePerformance(serializePerformance(m));
|
||||
CHECK(back.zones.size() == 1);
|
||||
if (back.zones.size() != 1) return;
|
||||
CHECK(back.zones[0].loopOverride.has_value() &&
|
||||
back.zones[0].loopOverride->start == 111 && back.zones[0].loopOverride->end == 222);
|
||||
CHECK(back.zones[0].startPoint.has_value() && *back.zones[0].startPoint == 333);
|
||||
CHECK(back.zones[0].play.adsr.holdFrames == 999);
|
||||
CHECK(back.zones[0].play.pitchEngine == PitchEngine::Preserve);
|
||||
}
|
||||
|
||||
static void testPlayParamsV2BackCompatLiftsToDefaults() {
|
||||
// A pre-S15 PAYLOAD v2 blob (marker + version 2 + record with the S11 tail but NO play tail)
|
||||
// lifts each zone to the PRODUCT defaults: Gate + Preserve (S16-F1) + no fades + env off — the
|
||||
// deliberate behavior change for already-saved instruments. Hand-build a v2 record exactly.
|
||||
std::vector<std::uint8_t> b;
|
||||
auto u32 = [&](std::uint32_t v) {
|
||||
b.push_back(v & 0xFF); b.push_back((v >> 8) & 0xFF);
|
||||
b.push_back((v >> 16) & 0xFF); b.push_back((v >> 24) & 0xFF);
|
||||
};
|
||||
u32(kPerformanceStateVersion); // envelope version (2)
|
||||
u32(kZonesFormatMarker); // marker -> a versioned payload
|
||||
u32(2); // PAYLOAD VERSION 2 (S11, no play tail)
|
||||
u32(1); // zone count 1
|
||||
const std::string id = "old";
|
||||
u32(static_cast<std::uint32_t>(id.size()));
|
||||
b.insert(b.end(), id.begin(), id.end());
|
||||
u32(5); // lowNote
|
||||
u32(80); // highNote
|
||||
b.push_back(0); // hasRootOverride = 0
|
||||
b.push_back(0); // hasLoopOverride = 0
|
||||
b.push_back(0); // hasStartPoint = 0 (record ends here in v2)
|
||||
const PerformanceMap back = deserializePerformance(b);
|
||||
CHECK(back.zones.size() == 1);
|
||||
if (back.zones.size() != 1) return;
|
||||
CHECK(back.zones[0].sampleId == "old");
|
||||
// Lifted to product defaults: Gate play mode, PRESERVE engine (the S16-F1 default), env off.
|
||||
CHECK(back.zones[0].play.playMode == PlayMode::Gate);
|
||||
CHECK(back.zones[0].play.pitchEngine == kDefaultPitchEngine); // == Preserve
|
||||
CHECK(back.zones[0].play.pitchEnv.enabled == false);
|
||||
CHECK(back.zones[0].play.adsr.holdFrames == 0);
|
||||
}
|
||||
|
||||
static void testPlayParamsThroughComponentEnvelope() {
|
||||
// The play params round-trip through the v4 COMPONENT envelope too (the composition property:
|
||||
// the zones payload is envelope-independent, so v4 {channelMode, selection, zones} carries them).
|
||||
ComponentState s;
|
||||
s.selectionId = "pick";
|
||||
s.channelMode = ChannelMode::Stereo;
|
||||
PerformanceZone z = zone("z", 0, 127);
|
||||
z.play.playMode = PlayMode::Trigger;
|
||||
z.play.trigger.lengthFraction = 0.9;
|
||||
z.play.pitchEngine = PitchEngine::Varispeed;
|
||||
s.map.zones.push_back(z);
|
||||
const ComponentState back = deserializeComponentState(serializeComponentState(s));
|
||||
CHECK(back.channelMode == ChannelMode::Stereo);
|
||||
CHECK(back.map.zones.size() == 1);
|
||||
if (back.map.zones.size() != 1) return;
|
||||
CHECK(back.map.zones[0].play.playMode == PlayMode::Trigger);
|
||||
CHECK(back.map.zones[0].play.trigger.lengthFraction == 0.9);
|
||||
CHECK(back.map.zones[0].play.pitchEngine == PitchEngine::Varispeed);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testSelectByIdHit();
|
||||
testSelectEmptyIdIsSilence();
|
||||
@@ -1026,6 +1133,10 @@ int main() {
|
||||
testPerformanceStateV1BackCompat();
|
||||
testPerformanceStateGarbage();
|
||||
testPerformanceStateNegativeNotesRoundTrip();
|
||||
testPlayParamsRoundTrip();
|
||||
testPlayParamsComposeWithLoopStart();
|
||||
testPlayParamsV2BackCompatLiftsToDefaults();
|
||||
testPlayParamsThroughComponentEnvelope();
|
||||
testComponentStateRoundTrip();
|
||||
testComponentStateLoopStartRoundTrip();
|
||||
testComponentStateSelectionOnlyNoZones();
|
||||
|
||||
Reference in New Issue
Block a user