fix(trigger-seam): thread startFrame into both envelope converters; extract pure triggerPlayLength module + tests
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
#include "app_version.h" // vstPluginName (channel-derived editor title band, S18)
|
||||
#include "sample_map.h"
|
||||
#include "wav_trim.h" // parseWavLayout, extractFloatFrames
|
||||
#include "trigger_seam.h" // triggerPlayLength / framesToFadeFraction / fadeFractionToFrames (S-VIEW-3)
|
||||
#include "waveform_view.h" // frame<->pixel markers + zero-crossing snap (S11)
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -462,19 +463,8 @@ EnvClampBounds ReaSamplerEditor::envClampBounds() const {
|
||||
return b;
|
||||
}
|
||||
|
||||
// The played-span source-frame length for the Trigger fade fraction conversion: the post-start
|
||||
// span scaled by lengthFraction (envelope_overlay.h's TRIGGER SEAM math). `frames` is the total
|
||||
// source frame count; startFrame comes from the zone's startPoint (0 when unset).
|
||||
namespace {
|
||||
std::int64_t triggerPlayLengthFrames(const ZonePlaySeconds& play, std::int64_t frames,
|
||||
std::int64_t startFrame) {
|
||||
const std::int64_t postStart = (std::max)(std::int64_t{0}, frames - startFrame);
|
||||
const double len = play.trigger.lengthFraction * static_cast<double>(postStart);
|
||||
return static_cast<std::int64_t>(len + 0.5);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
AmpEnvelope ReaSamplerEditor::packEnvelope(const ZonePlaySeconds& play, std::int64_t frames) const {
|
||||
AmpEnvelope ReaSamplerEditor::packEnvelope(const ZonePlaySeconds& play, std::int64_t frames,
|
||||
std::int64_t startFrame) const {
|
||||
AmpEnvelope env;
|
||||
env.mode = (play.playMode == PlayMode::Trigger) ? EnvMode::Trigger : EnvMode::Gate;
|
||||
// AHDSR seconds copy 1-to-1 (rate-free, the same domain the overlay draws).
|
||||
@@ -484,21 +474,19 @@ AmpEnvelope ReaSamplerEditor::packEnvelope(const ZonePlaySeconds& play, std::int
|
||||
env.sustainLevel = play.adsr.sustainLevel;
|
||||
env.releaseSeconds = play.adsr.releaseSeconds;
|
||||
// Trigger: lengthFraction copies 1-to-1; the fades are DERIVED — source frames over the played
|
||||
// span (the TRIGGER SEAM converter, PACK direction). A zero play length yields 0 fractions.
|
||||
// span (the TRIGGER SEAM converter, PACK direction). startFrame is the zone's effective start
|
||||
// point so the fraction denominator matches the voice's actual post-start span. A zero play
|
||||
// length yields 0 fractions.
|
||||
env.lengthFraction = play.trigger.lengthFraction;
|
||||
const std::int64_t playLen = triggerPlayLengthFrames(play, frames, /*startFrame=*/0);
|
||||
if (playLen > 0) {
|
||||
env.fadeInFraction = static_cast<double>(play.trigger.fadeInFrames) / static_cast<double>(playLen);
|
||||
env.fadeOutFraction = static_cast<double>(play.trigger.fadeOutFrames) / static_cast<double>(playLen);
|
||||
} else {
|
||||
env.fadeInFraction = 0.0;
|
||||
env.fadeOutFraction = 0.0;
|
||||
}
|
||||
const std::int64_t playLen =
|
||||
triggerPlayLength(play.trigger.lengthFraction, frames, startFrame);
|
||||
env.fadeInFraction = framesToFadeFraction(play.trigger.fadeInFrames, playLen);
|
||||
env.fadeOutFraction = framesToFadeFraction(play.trigger.fadeOutFrames, playLen);
|
||||
return env;
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::unpackEnvelope(const AmpEnvelope& env, std::int64_t frames,
|
||||
ZonePlaySeconds& play) const {
|
||||
std::int64_t startFrame, ZonePlaySeconds& play) const {
|
||||
if (env.mode == EnvMode::Gate) {
|
||||
play.adsr.attackSeconds = env.attackSeconds;
|
||||
play.adsr.holdSeconds = env.holdSeconds;
|
||||
@@ -507,14 +495,15 @@ void ReaSamplerEditor::unpackEnvelope(const AmpEnvelope& env, std::int64_t frame
|
||||
play.adsr.releaseSeconds = env.releaseSeconds;
|
||||
} else {
|
||||
// Trigger: lengthFraction copies back; the fades convert fractions -> source frames over
|
||||
// the played span (the TRIGGER SEAM converter, UNPACK direction). Keep the same (0,1] floor
|
||||
// on lengthFraction the slider path enforces so a zero-length trigger never plays nothing.
|
||||
// the played span (the TRIGGER SEAM converter, UNPACK direction). startFrame is the zone's
|
||||
// effective start point so the frame denominator matches the voice's actual post-start span.
|
||||
// Keep the same (0,1] floor on lengthFraction the slider path enforces so a zero-length
|
||||
// trigger never plays nothing.
|
||||
play.trigger.lengthFraction = (std::max)(0.01, env.lengthFraction);
|
||||
const std::int64_t playLen = triggerPlayLengthFrames(play, frames, /*startFrame=*/0);
|
||||
play.trigger.fadeInFrames =
|
||||
static_cast<std::int64_t>(env.fadeInFraction * static_cast<double>(playLen) + 0.5);
|
||||
play.trigger.fadeOutFrames =
|
||||
static_cast<std::int64_t>(env.fadeOutFraction * static_cast<double>(playLen) + 0.5);
|
||||
const std::int64_t playLen =
|
||||
triggerPlayLength(play.trigger.lengthFraction, frames, startFrame);
|
||||
play.trigger.fadeInFrames = fadeFractionToFrames(env.fadeInFraction, playLen);
|
||||
play.trigger.fadeOutFrames = fadeFractionToFrames(env.fadeOutFraction, playLen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1056,7 +1045,8 @@ void ReaSamplerEditor::paintEnvelopeOverlay(LICE_IBitmap* bmp, const Rect& waveA
|
||||
const double rate = liveSampleRate();
|
||||
if (rate <= 0.0) return;
|
||||
const double totalSeconds = static_cast<double>(frames) / rate;
|
||||
const AmpEnvelope env = packEnvelope(zone.play, frames);
|
||||
const std::int64_t startFrame = zone.startPoint.value_or(0);
|
||||
const AmpEnvelope env = packEnvelope(zone.play, frames, startFrame);
|
||||
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, waveArea, totalSeconds);
|
||||
|
||||
// Trace the polyline in the categorical secondary accent (teal) so it reads as a distinct
|
||||
@@ -1687,7 +1677,8 @@ void ReaSamplerEditor::onMouseDown(int x, int y) {
|
||||
const double rate = liveSampleRate();
|
||||
if (rate > 0.0) {
|
||||
const PerformanceZone zone = effectiveSampleZone();
|
||||
const AmpEnvelope env = packEnvelope(zone.play, frames);
|
||||
const std::int64_t startFrame = zone.startPoint.value_or(0);
|
||||
const AmpEnvelope env = packEnvelope(zone.play, frames, startFrame);
|
||||
const double totalSeconds = static_cast<double>(frames) / rate;
|
||||
const NodeHit nh = nodeAtPoint(env, waveArea, totalSeconds, x, y);
|
||||
if (nh.hit) {
|
||||
@@ -1697,6 +1688,7 @@ void ReaSamplerEditor::onMouseDown(int x, int y) {
|
||||
dragStartY_ = y;
|
||||
dragStartEnv_ = env;
|
||||
dragSampleFrames_ = frames;
|
||||
dragStartFrame_ = startFrame;
|
||||
dragStartMap_ = map_;
|
||||
return; // node moves once the cursor drags
|
||||
}
|
||||
@@ -1941,7 +1933,8 @@ void ReaSamplerEditor::onMouseMove(int x, int y) {
|
||||
totalSeconds, envClampBounds(), dx, dy);
|
||||
const int zi = ensureSampleZone();
|
||||
if (zi >= 0) {
|
||||
unpackEnvelope(edited, frames, map_.zones[static_cast<std::size_t>(zi)].play);
|
||||
unpackEnvelope(edited, frames, dragStartFrame_,
|
||||
map_.zones[static_cast<std::size_t>(zi)].play);
|
||||
selectedZone_ = zi;
|
||||
}
|
||||
invalidate(); // live feedback; commit on WM_LBUTTONUP
|
||||
|
||||
Reference in New Issue
Block a user