instrument: spline EGs — hard points on the one shared spline, a drawn contour per envelope beside its staged state, payload v13
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "core/instrument/engine/loop/loop_span.h" // maxCrossfade (the shared drag-clamp bound)
|
||||
#include "core/instrument/ui/envelope_edit.h" // nodeAtPoint / resolveNodeDrag
|
||||
#include "core/instrument/ui/spline_edit.h" // the shared point-editing grammar
|
||||
#include "core/instrument/ui/waveform_view.h" // waveformOverlayArea / markerAtPoint / snap
|
||||
#include "shell/instrument/editor_internal.h"
|
||||
#include "shell/instrument/reasampler_processor.h"
|
||||
@@ -30,13 +31,24 @@ bool ReaSamplerEditor::mouseDownWaveform(const FaceLayout& fl, int x, int y) {
|
||||
if (frames <= 0) return false;
|
||||
const OverlayArea overlay = waveformOverlayArea(fl.bands.waveform);
|
||||
|
||||
// A drawn EG's own point actions come first, for the same reason the staged nodes do —
|
||||
// they sit on top of the markers. Its ADD, which any empty-space click satisfies, is held
|
||||
// back until after the markers below, or a spline overlay would make them unreachable.
|
||||
const DeckEnableState gates = deckEnableState();
|
||||
const bool splineLive = overlayIsSpline() && overlayEnvEnabled(overlayEnv_, gates);
|
||||
const SplineGesture gesture = (GetKeyState(VK_CONTROL) & 0x8000) != 0
|
||||
? SplineGesture::kControlLeft
|
||||
: SplineGesture::kLeft;
|
||||
if (splineLive && splineOverlayClick(overlay, x, y, gesture, /*addOnEmptySpace=*/false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Envelope nodes first (they sit on top of the markers), then the wave markers. With no
|
||||
// envelope overlay-active — or with its deck group's enable toggle off, which makes the
|
||||
// same params' knobs inert — there are no grabbable nodes and the markers take every grab.
|
||||
const double rate = liveSampleRate();
|
||||
const bool nodesLive =
|
||||
overlayEnv_ != OverlayEnv::kNone &&
|
||||
!overlayEnvInert(overlayEnv_, params_.play.pitchEnv.enabled, params_.play.filter.enabled);
|
||||
overlayEnv_ != OverlayEnv::kNone && !overlayEnvInert(overlayEnv_, gates);
|
||||
if (rate > 0.0 && nodesLive) {
|
||||
const std::int64_t startFrame = params_.startPoint.value_or(0);
|
||||
const StageEnvelope env = packEnvelope(overlayEnv_, params_.play, frames, startFrame);
|
||||
@@ -71,9 +83,54 @@ bool ReaSamplerEditor::mouseDownWaveform(const FaceLayout& fl, int x, int y) {
|
||||
beginMarkerDrag(static_cast<WaveMarker>(hit), m, frames, x);
|
||||
return true;
|
||||
}
|
||||
// Nothing else wanted the click: now the drawn contour may take the empty space.
|
||||
if (splineLive) return splineOverlayClick(overlay, x, y, gesture, /*addOnEmptySpace=*/true);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReaSamplerEditor::splineOverlayClick(const OverlayArea& waveArea, int x, int y,
|
||||
SplineGesture gesture, bool addOnEmptySpace) {
|
||||
const VelocityCurve::Box box = splineOverlayBox(waveArea);
|
||||
VelocityCurve& contour = splineFor(overlayEnv_);
|
||||
const SplineEdit edit = resolveSplineEdit(contour, box, gesture, x, y);
|
||||
if (edit.kind == SplineEditKind::kAdd && !addOnEmptySpace) return false;
|
||||
switch (edit.kind) {
|
||||
case SplineEditKind::kNone:
|
||||
return false;
|
||||
case SplineEditKind::kDelete:
|
||||
// deletePoint refuses the two endpoints, so a right-click on one is a safe no-op.
|
||||
if (!contour.deletePoint(static_cast<std::size_t>(edit.index))) return true;
|
||||
hover_ = HoverTarget{}; // a stale index would light a shifted node
|
||||
commitAndReload();
|
||||
return true;
|
||||
case SplineEditKind::kToggleHard:
|
||||
if (!contour.toggleHard(static_cast<std::size_t>(edit.index))) return true;
|
||||
commitAndReload();
|
||||
return true;
|
||||
case SplineEditKind::kAdd:
|
||||
case SplineEditKind::kGrab:
|
||||
break;
|
||||
}
|
||||
// Snapshot BEFORE the add so a capture-loss rollback cancels the in-flight point too
|
||||
// (the same contract the other parameter-editing drags keep).
|
||||
dragStartParams_ = params_;
|
||||
int index = edit.index;
|
||||
if (edit.kind == SplineEditKind::kAdd) {
|
||||
const VelocityPoint p = contour.pointFromPixel(box, x, y);
|
||||
index = contour.addPoint(p.velocity, p.value);
|
||||
// At the ceiling: refused, contour untouched, nothing to roll back. Swallow the click
|
||||
// rather than letting it fall through to a marker grab under the cursor.
|
||||
if (index < 0) return true;
|
||||
}
|
||||
drag_ = DragKind::kSplineNode;
|
||||
curvePointIndex_ = index;
|
||||
dragStartCurve_ = contour; // AFTER the add — resolvePointDrag's delta base
|
||||
dragStartX_ = x;
|
||||
dragStartY_ = y;
|
||||
invalidate(); // live feedback; the commit lands on WM_LBUTTONUP
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::beginMarkerDrag(WaveMarker which, const SetupMarkers& m,
|
||||
std::int64_t frames, int x) {
|
||||
drag_ = DragKind::kWaveMarker;
|
||||
@@ -88,6 +145,17 @@ void ReaSamplerEditor::dragWaveform(const FaceLayout& fl, int x, int y) {
|
||||
const OverlayArea overlay = waveformOverlayArea(fl.bands.waveform);
|
||||
const int dx = x - dragStartX_;
|
||||
|
||||
if (drag_ == DragKind::kSplineNode) {
|
||||
// Same absolute-delta contract as the popup's node drag, against the grab-time contour
|
||||
// and the overlay's own box.
|
||||
if (curvePointIndex_ < 0) return;
|
||||
splineFor(overlayEnv_) = VelocityCurve::resolvePointDrag(
|
||||
dragStartCurve_, static_cast<std::size_t>(curvePointIndex_),
|
||||
splineOverlayBox(overlay), dx, y - dragStartY_);
|
||||
invalidate(); // live feedback; commit on release
|
||||
return;
|
||||
}
|
||||
|
||||
if (drag_ == DragKind::kEnvNode) {
|
||||
// Resolve the grabbed envelope node's new params from the pixel delta (through the
|
||||
// pure envelope_edit inverse map, clamped), then unpack them back onto the parameter
|
||||
|
||||
Reference in New Issue
Block a user