#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 #include #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, "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 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 {}; } // namespace reasampler::instrument::engine