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:
@@ -65,6 +65,20 @@ struct AhdParams {
|
||||
double decayCurve = util::kCurveNeutral;
|
||||
};
|
||||
|
||||
// Which shape an envelope takes: the STAGED knobs, or a free-drawn SPLINE contour. Both states
|
||||
// are stored side by side and neither converts into the other, so a mode flip is reversible and
|
||||
// lossless — the inactive one is saved but inert, edited only by switching back to it.
|
||||
enum class EnvMode { Staged, Spline };
|
||||
|
||||
// The free-drawn alternative to a staged envelope: a contour over NORMALIZED sample time,
|
||||
// covering the full sample length. Normalized is what makes it length-independent — a
|
||||
// different-length capture replays the same shape proportionally, with no stored seconds to
|
||||
// rescale. The default is the smooth y = 1 - x downward slope.
|
||||
struct SplineEnv {
|
||||
EnvMode mode = EnvMode::Staged;
|
||||
VelocityCurve contour = VelocityCurve::rampDown();
|
||||
};
|
||||
|
||||
// GATE = classic held note (AHDSR + sustain loop + note-off release). TRIGGER = one-shot:
|
||||
// note-off-immune, no sustain loop, plays a % of sample length shaped by the AHD. Both honor
|
||||
// the start point. Default Gate so an instrument with no params set plays as before.
|
||||
@@ -150,8 +164,31 @@ struct PlayParams {
|
||||
// baseRatio_ at note-on — it is fixed for the note's lifetime, so it costs no per-frame work.
|
||||
VelocityCurve pitchVelocityCurve = VelocityCurve::zero();
|
||||
FilterParams filter;
|
||||
// The three drawn contours: the alternative to adsr/trigAhd, to pitchEnv.shape, and to
|
||||
// filter.env/trigEnv respectively. They sit HERE rather than inside the three envelope
|
||||
// structs because those are copied whole into the live block, which must stay trivially
|
||||
// copyable (live_params.h) — and a contour is not a live control anyway: like the velocity
|
||||
// curves it travels by reload.
|
||||
SplineEnv ampSpline;
|
||||
SplineEnv pitchSpline;
|
||||
SplineEnv filterSpline;
|
||||
};
|
||||
|
||||
// Whether ANY of the three envelopes is drawn rather than staged. Templated over the two
|
||||
// parameter representations (frames and the editor's seconds mirror) because both spell the
|
||||
// three fields identically and the rule must not be written twice — compile-time dispatch,
|
||||
// no runtime cost, off every hot path.
|
||||
//
|
||||
// THE consequence, and its one home: a spline contour is a pure time function over the full
|
||||
// sample length, which IS the Trigger/one-shot playback model — so Gate is not available while
|
||||
// any spline EG is active. resolvePlay enforces it on the way to the engine; the editor's
|
||||
// play-mode toggle refuses the Gate segment so the two agree.
|
||||
template <class Play>
|
||||
bool splineActive(const Play& p) {
|
||||
return p.ampSpline.mode == EnvMode::Spline || p.pitchSpline.mode == EnvMode::Spline ||
|
||||
p.filterSpline.mode == EnvMode::Spline;
|
||||
}
|
||||
|
||||
// [start, end) frames, half-open. A zero-length loop (start == end) is the "no sustain loop"
|
||||
// marker — a held note past the sample end goes silent rather than looping a zero span.
|
||||
struct SampleLoop {
|
||||
|
||||
Reference in New Issue
Block a user