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:
@@ -10,6 +10,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "core/audio/peaks.h"
|
||||
#include "core/instrument/engine/live_contours.h"
|
||||
#include "core/instrument/engine/live_params.h"
|
||||
#include "core/instrument/engine/play_params.h"
|
||||
#include "core/instrument/engine/voice.h"
|
||||
@@ -57,6 +58,11 @@ public:
|
||||
MonoTrigger monoTrigger = MonoTrigger::Retrigger,
|
||||
bool takeoverDeclick = false);
|
||||
|
||||
// Voices may hold raw pointers into this engine's own contour buffers, so a copy would
|
||||
// leave them reading the original's.
|
||||
VoiceEngine(const VoiceEngine&) = delete;
|
||||
VoiceEngine& operator=(const VoiceEngine&) = delete;
|
||||
|
||||
// MIDI note-on. Allocates a free voice, or steals one per the policy above. Returns the
|
||||
// index of the voice used, or kNoVoice when nothing is playable (no decoded PCM, an
|
||||
// out-of-range note, or a Preserve note-on past the cap) — a defined no-play, not an error.
|
||||
@@ -124,6 +130,22 @@ private:
|
||||
std::uint32_t liveGeneration_ = 0; // last generation observed; 0 = none yet
|
||||
bool haveLive_ = false;
|
||||
|
||||
// --- Drawn-contour observation (live_contours.h) ---
|
||||
// Reads a new contour generation, when one landed, into the buffer no voice is bound to and
|
||||
// rebinds EVERY active voice to it before returning — which is what keeps the other buffer
|
||||
// free for the next read. That invariant is why this, and never a bare read, is what both
|
||||
// the block boundary and startVoice call.
|
||||
void applyContoursToActive();
|
||||
|
||||
// Double-buffered because a voice's SplineCursor points INTO the buffer: reading over it
|
||||
// would change a contour under a sounding voice without the rebind that absorbs the step,
|
||||
// and a torn read (which the seqlock only reports after the fact) must land somewhere no
|
||||
// voice reads. ~9 KB each, in the engine, so nothing on the audio thread allocates.
|
||||
std::array<instrument::engine::LiveContours, 2> contours_{};
|
||||
std::size_t contourFront_ = 0;
|
||||
std::uint32_t contourGeneration_ = 0; // last generation observed; 0 = none yet
|
||||
bool haveContours_ = false;
|
||||
|
||||
// Count of active Preserve-engine voices (for the Preserve cap). Rescanned per note-on
|
||||
// (cheap: bounded by maxVoices) rather than maintained as a running tally.
|
||||
std::size_t activePreserveVoices() const;
|
||||
|
||||
Reference in New Issue
Block a user