Make spline EG drags audible live: held and new notes follow a dragged knot

Contours ride their own seqlock block into a double-buffered engine copy, and the swap step is smoothed. Each drag move publishes contours without a model write; release still commits and reloads.
This commit is contained in:
2026-09-25 13:42:25 -04:00
parent 9ff34b0a9e
commit f7505d0e53
21 changed files with 788 additions and 97 deletions
@@ -0,0 +1,50 @@
#pragma once
// live_contours.h — the three drawn EG contours as one fixed-capacity plain value, and the block
// that publishes them to the audio thread so a sounding voice follows a contour edit. Separate
// from LiveValues on purpose: at kMaxCurvePoints x 3 this is ~9 KB, and riding the live block
// would put it on every knob publish, automation merge and field compare.
#include <cstdint>
#include <type_traits>
#include "core/instrument/engine/seqlock.h"
#include "core/instrument/engine/velocity_curve.h"
namespace reasampler::instrument::engine {
// One contour's points, copied out of its VelocityCurve so the audio thread can hold it without
// a vector. `count` never exceeds kMaxCurvePoints — the curve's own ceiling — and the points
// past it are ignored.
struct ContourPoints {
std::uint32_t count = 0;
VelocityPoint points[kMaxCurvePoints];
};
// All three are carried whatever each envelope's mode: Staged|Spline is a note-on fact that
// travels by reload, so the voice decides which cursors to rebind and the block stays one shape.
struct LiveContours {
ContourPoints amp;
ContourPoints pitch;
ContourPoints filter;
};
static_assert(std::is_trivially_copyable_v<LiveContours>,
"the contour block is copied under a seqlock — it must stay a plain value");
ContourPoints contourPoints(const VelocityCurve& curve);
// Templated over the frames and seconds parameter representations for the same reason
// splineActive is (play_params.h): both spell the three fields identically. Off the audio thread.
template <class Play>
LiveContours foldContours(const Play& p) {
LiveContours c{};
c.amp = contourPoints(p.ampSpline.contour);
c.pitch = contourPoints(p.pitchSpline.contour);
c.filter = contourPoints(p.filterSpline.contour);
return c;
}
// A class rather than an alias so play_params.h can forward-declare it.
class LiveContourBlock : public Seqlock<LiveContours> {};
} // namespace reasampler::instrument::engine