loop: crossfade the Gate sustain seam, and unshadow the loop handles that made loop points look gone

This commit is contained in:
2026-07-31 17:36:36 -04:00
parent a90ccd9a00
commit 0fe4166d7d
25 changed files with 991 additions and 64 deletions
+34 -11
View File
@@ -52,20 +52,33 @@ bool ReaSamplerEditor::mouseDownWaveform(const FaceLayout& fl, int x, int y) {
}
}
const SetupMarkers m = pickedMarkers(frames);
// The crossfade handle first, and only when there IS a loop to fade: at a zero fade it
// sits exactly on the loop start, so it can only stay reachable by owning the top strip
// (waveform_view.h's handle-vs-column split) and being asked first.
if (m.hasLoop &&
contains(markerHandleRect(overlay, frames, m.loopStart - m.crossfade), x, y)) {
beginMarkerDrag(WaveMarker::kLoopXfade, m, frames, x);
return true;
}
const std::int64_t markerFrames[3] = {m.start, m.loopStart, m.loopEnd};
const int hit = markerAtPoint(overlay, frames, markerFrames, 3, x, y);
if (hit >= 0) {
drag_ = DragKind::kWaveMarker;
waveMarker_ = static_cast<WaveMarker>(hit);
dragStartX_ = x;
dragStartMarkers_ = m;
dragSampleFrames_ = frames;
dragStartParams_ = params_;
beginMarkerDrag(static_cast<WaveMarker>(hit), m, frames, x);
return true;
}
return false;
}
void ReaSamplerEditor::beginMarkerDrag(WaveMarker which, const SetupMarkers& m,
std::int64_t frames, int x) {
drag_ = DragKind::kWaveMarker;
waveMarker_ = which;
dragStartX_ = x;
dragStartMarkers_ = m;
dragSampleFrames_ = frames;
dragStartParams_ = params_;
}
void ReaSamplerEditor::dragWaveform(const FaceLayout& fl, int x, int y) {
const OverlayArea overlay = waveformOverlayArea(fl.bands.waveform);
const int dx = x - dragStartX_;
@@ -96,14 +109,17 @@ void ReaSamplerEditor::dragWaveform(const FaceLayout& fl, int x, int y) {
// Grabbed frame at grab time, from the snapshot (so the delta is measured from grab).
const int idx = static_cast<int>(waveMarker_);
const std::int64_t startVals[3] = {dragStartMarkers_.start, dragStartMarkers_.loopStart,
dragStartMarkers_.loopEnd};
const std::int64_t startVals[4] = {dragStartMarkers_.start, dragStartMarkers_.loopStart,
dragStartMarkers_.loopEnd,
dragStartMarkers_.loopStart -
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.
// 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.
const std::vector<AudioSample>& pcm = monoPcmFor(selectedId_);
if (!pcm.empty()) {
if (!pcm.empty() && waveMarker_ != WaveMarker::kLoopXfade) {
newFrame = nearestZeroCrossing(pcm.data(), static_cast<std::int64_t>(pcm.size()),
newFrame);
}
@@ -116,12 +132,19 @@ void ReaSamplerEditor::dragWaveform(const FaceLayout& fl, int x, int y) {
} else if (waveMarker_ == WaveMarker::kLoopStart) {
m.loopStart = (std::min)(newFrame, m.loopEnd);
m.hasLoop = true;
} else { // kLoopEnd
} else if (waveMarker_ == WaveMarker::kLoopEnd) {
m.loopEnd = (std::max)(newFrame, m.loopStart);
m.hasLoop = true;
} else { // kLoopXfade — the handle sits at loopStart - crossfade, so left lengthens it
m.crossfade = (std::max)(std::int64_t{0}, m.loopStart - newFrame);
}
if (m.start < 0) m.start = 0;
if (m.start > frames - 1) m.start = frames - 1;
// Mirror resolveLoop's own bound so the handle can't be dragged somewhere the engine
// would silently clamp back: the fade reads the material ahead of the loop, and cannot
// outrun either that material or the loop itself.
m.crossfade = (std::min)(m.crossfade, (std::min)(m.loopStart, m.loopEnd - m.loopStart));
if (m.crossfade < 0) m.crossfade = 0;
applyMarkers(m);
invalidate(); // live feedback; the commit lands on release