Phase S Wave 2: three-view ReaSampler 9000 editor (Sample home / Browse modal / Zone)

Retire the flat toggle; Sample-home face wires the Wave-1 envelope overlay + draggable nodes, preview button + velocity knob, Mono/Stereo, key-track. Browse reduced to a confirm/cancel modal; Zone retained with piano-pattern strip. RT-safe off-thread preview-note mailbox added.
This commit is contained in:
2026-07-27 14:17:58 -04:00
parent 203005e6cf
commit c12a374b88
5 changed files with 1094 additions and 542 deletions
+45 -3
View File
@@ -149,6 +149,13 @@ public:
// The bridge, for the editor's live-state readout + sample list. Owned here; the
// editor borrows it (outlives the editor).
ReaperBridge& bridge() { return bridge_; }
// The live host sample rate latched from setupProcessing (the SAME rate reloadFromBank
// resolves seconds->frames against). The editor's S-VIEW-3 envelope overlay reads it to place
// its wall-clock seconds on the same time base the voice engine plays them over. 0.0 before
// setupProcessing runs (the editor guards). Read on the UI thread; a plain load — sampleRate_
// is set once by setupProcessing before any audio and does not change under the editor.
double sampleRate() const { return sampleRate_; }
// The current single-capture selection id (main/UI thread reads for the editor). Guarded
// by selectionMutex_ — never touched on the audio thread. Since S10 this is the ONE picked
// capture the default face plays chromatically when the performance map is empty; an EMPTY
@@ -173,6 +180,22 @@ public:
// (same mode) does neither. UI thread only.
void setChannelMode(ChannelMode mode);
// The per-instance preview-trigger velocity (S-VIEW-4, MIDI 1..127). Read/written on the
// UI thread (the Sample-view velocity knob) and by getState/setState (host load-save thread);
// guarded by previewMutex_. Persisted in component state (v6). NOT read on the audio thread.
std::uint8_t previewVelocity();
void setPreviewVelocity(std::uint8_t velocity);
// Fire a one-shot PREVIEW note-on / note-off through the live voice engine (S-VIEW-4), OFF
// the audio thread (the editor's preview-trigger button drives these on the UI thread). The
// request is handed to process() via a lock-free single-slot mailbox drained at block start —
// no allocation, no lock on the audio thread. previewNoteOn plays `note` at the current
// previewVelocity(); previewNoteOff releases it (Gate) — Trigger zones ignore note-off and
// play through. A momentary button (down = on, up = off) reads as a natural key press. This
// is PLAYBACK ONLY: it never captures, never inserts a timeline item.
void previewNoteOn(int note);
void previewNoteOff(int note);
private:
// Apply `mode` to the output audio bus's SpeakerArrangement (kMono / kStereo). Called from
// initialize (topology) and setChannelMode (runtime change). Does NOT re-negotiate — the
@@ -251,11 +274,30 @@ private:
std::int64_t lastSeenBankGeneration_ = -1;
// S-VIEW-4 preview-trigger velocity (MIDI 1..127). Persisted in component state (v6) so the
// user's chosen strike velocity survives a project save/reload. No concurrent writer before
// Wave 2 (no editor knob yet) — setState and getState are the sole accessors, both on the
// load/save thread (host-serialized). Default kPreviewVelocityDefault (64).
// user's chosen strike velocity survives a project save/reload. Since Wave 2 the Sample-view
// velocity knob writes it on the UI thread, so it is guarded by previewMutex_; setState and
// getState (load/save thread) share the same guard. Default kPreviewVelocityDefault (64). NOT
// read on the audio thread.
std::mutex previewMutex_;
std::uint8_t previewVelocity_ = kPreviewVelocityDefault;
// --- S-VIEW-4 preview-trigger mailbox (off-thread -> audio thread, lock-free) ---------
// The editor's preview-trigger button posts a note-on/off request from the UI thread; process()
// drains it at block start and drives the live engine. ONE slot per direction, each a packed
// request whose high bits are a monotonically-incrementing sequence so process() detects a NEW
// request by comparing against the last sequence it consumed (never re-firing a stale one). The
// low 8 bits carry the note (on) / note (off); the on request also carries the velocity in the
// next 8 bits, latched at post time so the audio thread reads no shared velocity field. A single
// relaxed atomic load per block on the audio thread — RT-safe (no alloc, no lock).
// packed = (seq << 16) | (velocity << 8) | note [note-on]
// packed = (seq << 16) | note [note-off]
std::atomic<std::uint32_t> previewOnRequest_{0}; // 0 = no request posted yet
std::atomic<std::uint32_t> previewOffRequest_{0};
std::uint16_t previewOnSeq_ = 0; // UI-thread post counter (never 0 after first post)
std::uint16_t previewOffSeq_ = 0;
std::uint16_t previewOnConsumed_ = 0; // audio-thread: last on-seq fired
std::uint16_t previewOffConsumed_ = 0; // audio-thread: last off-seq fired
// Latched from setupProcessing so setActive/reload can size against it. Read
// off-thread only. 0.0 is explicitly invalid — setupProcessing sets the real host rate
// before any audio, and reloadFromBank guards on it before use.