fix(instrument): bound the waveform zero-crossing snap to a pixel radius so single-cycle loop marks stop teleporting

Ctrl during a marker drag defeats the snap outright. nearestZeroCrossing keeps its
unbounded contract; snapToZeroCrossing is the decision a drag applies.
This commit is contained in:
2026-08-03 15:15:25 -04:00
parent 1159d364c2
commit b7b7e88195
5 changed files with 205 additions and 24 deletions
@@ -282,13 +282,15 @@ void ReaSamplerEditor::dragWaveform(const FaceLayout& fl, int x, int y) {
dragStartMarkers_.crossfade};
std::int64_t newFrame = resolveDragFrame(overlay, frames, startVals[idx], dx);
// Snap to the nearest zero crossing in the decoded PCM. Pure over the cached mono
// frames — no host types, no file I/O. The crossfade handle is exempt: it sets a fade
// LENGTH, and the whole point of the fade is that its edges need no zero crossing.
// Snap to a zero crossing in the decoded PCM, but only one within kZeroCrossingSnapPx of
// where the mark was dropped. Pure over the cached mono frames — no host types, no file I/O.
// The crossfade handle is exempt: it sets a fade LENGTH, and the whole point of the fade is
// that its edges need no zero crossing. Ctrl defeats the snap outright, the same "get out of
// the way, I want exactness" role it plays as the knobs' fine drag.
const std::vector<AudioSample>& pcm = monoPcmFor(selectedId_);
if (!pcm.empty() && waveMarker_ != WaveMarker::kCrossfade) {
newFrame = nearestZeroCrossing(pcm.data(), static_cast<std::int64_t>(pcm.size()),
newFrame);
if (!pcm.empty() && waveMarker_ != WaveMarker::kCrossfade && !dragModifiers().ctrl) {
newFrame = snapToZeroCrossing(pcm.data(), static_cast<std::int64_t>(pcm.size()), newFrame,
zeroCrossingSnapFrames(overlay, frames));
}
// Build the edited marker set from the snapshot, moving only the grabbed marker, then