Bake window derives itself: %-knob fold, declick pad, Gate held to exhaustion, preview velocity; Hold is the one knob a loop needs
This commit is contained in:
@@ -11,7 +11,7 @@ The pure engine/geometry core this shell wraps (`sampler_core`, `pitch_shift`,
|
||||
`sample_map`, `component_state_io`, `play_params.h`, `editor_geometry`, `sample_bands`,
|
||||
`sample_chrome`, `keyboard_strip`, `waveform_view`, `capture_browser`, `browser_scroll`,
|
||||
`param_slider`, `trigger_seam`, `velocity_curve`, `embed_strip`, `knob_deck`,
|
||||
`deck_groups`, `deck_values`, `curve_popup`, `spline_edit`, `master_gain`, `reasampler_uid.h`) lives in `core/instrument/*` and
|
||||
`deck_groups`, `deck_values`, `bake_hold`, `curve_popup`, `spline_edit`, `master_gain`, `reasampler_uid.h`) lives in `core/instrument/*` and
|
||||
`core/wire` and is documented there — this directory consumes it but does not own it.
|
||||
|
||||
## Invariants
|
||||
@@ -108,7 +108,7 @@ declared ahead of the instrument slots at that member in `reasampler_processor.h
|
||||
- `reasampler_editor` — VST3 `IPlugView` LICE editor shell: hosts a LICE-drawn child window; the Sample face is home and Browse is a modal picker over it. Split on the Sample face's BAND axis, mirroring the pure `sample_bands` allocator: `editor_session` (session/bridge state, caches, commit-and-reload), `editor_controls` (the ONE `faceLayout` band resolve every paint and hit-test path shares, the node-drag bounds, the value labels, and the per-instance controls the parameter set does not carry — the parameter-set binding itself is the pure `core/instrument/ui/deck_values` module this only adapts int ids onto), `editor_models` (the orthogonal half: which stored struct each transient editor selection names — the staged-envelope pack/unpack, the drawn contour, and the three velocity curves), then matching paint and input sets — `editor_paint`/`editor_input` (dispatch + drag router + hover dispatch), `_chrome`, `_waveform`, `_deck` — plus the two band-independent surfaces (`_browse` for the modal picker, `_curve` for the velocity-curve popup) and `editor_platform` (IPlugView/Win32 window plumbing). Shared internals in `editor_internal.h`, no TU of its own. Drop-onto-editor ingest is NOT shipped (deferred).
|
||||
- `reasampler_embed` — implements `IReaperUIEmbedInterface` so the instrument draws inline in the TCP/MCP without a plugin-owned HWND; delegates layout to `embed_strip`. A read-only readout: the loaded capture across the keyboard span with its root marked, plus the activity level. It takes no mouse input (there is nothing on the strip to select).
|
||||
- `editor_stroke` — the editor's LICE side of the analytic stroker: builds a coverage mask with the pure `core/ui/stroke_aa` and blends it into the bitmap ONCE, writing straight to the bitmap's bits (the arithmetic matches LICE's own mode-0 combine, so a stroke composites identically to every other kit draw). Every radial and spline stroke on the editor routes through `strokeArcAA` / `strokePolylineAA` / `strokeLineAA`. Holds the draw-thread-only scratch mask and arc point list — reuse, not a hidden dependency: threading a canvas through the eight paint sites would grow those signatures to carry an allocation detail. Deliberately does NOT touch `shell/panel/draw_kit`: the waveform stroke, the docked bank panel and the browse cards are out of this seam's blast radius.
|
||||
- `instrument_bake` — the instrument's half of the resample chain, on the UI thread: render the dialed sound through the pure `core/instrument/bake` modules, stage the WAV OUTSIDE the bank folder, publish one `rsbake_<guid>` request, invoke the extension's landing action SYNCHRONOUSLY, read the outcome back over the same key, then adopt + reset in one act. Two stack-RAII guards mirror `FxBypassGuard`'s discipline: the staged file and the request key are both cleared on every exit path, so a failed bake leaves no temp, no bank entry and no parameter reset. `bakeAvailable` is the affordance's paint gate.
|
||||
- `instrument_bake` — the instrument's half of the resample chain, on the UI thread: render the dialed sound through the pure `core/instrument/bake` modules at the instance's PERSISTED PREVIEW VELOCITY (the velocity the user has been auditioning at — three velocity curves are live, so it is a property of the sound and not a render detail), stage the WAV OUTSIDE the bank folder, publish one `rsbake_<guid>` request, invoke the extension's landing action SYNCHRONOUSLY, read the outcome back over the same key, then adopt + reset in one act. Two stack-RAII guards mirror `FxBypassGuard`'s discipline: the staged file and the request key are both cleared on every exit path, so a failed bake leaves no temp, no bank entry and no parameter reset. `bakeAvailable` is the affordance's paint gate.
|
||||
- `vst_entry` — VST3 entry point: `GetPluginFactory` export, class registration, channel-forked class UIDs.
|
||||
- `editor_internal.h` — INTERNAL shared helpers for the `reasampler_editor` TU family, included only by the editor's own shell TUs (`editor_session` / `editor_controls` / `editor_paint_*` / `editor_input_*` / `editor_platform`), never a public seam: the `Rect`↔kit adapters, small draw primitives (knob face / title band), label helpers, and the velocity-curve box derivation — the helpers more than one band TU needs. The deck's control ids, group ids and group composition are the pure `deck_groups` module's, not this file's. The piano-strip and root-key draws live in `editor_paint_chrome`, their only consumer, not here.
|
||||
- `reasampler_vst.h` — shared identity constants for the ReaSampler VST3 instrument (Phase S): the plugin's class UID (the channel-selected `Steinberg::FUID`, built from the FOREVER-FROZEN macros in `core/wire/reasampler_uid.h`), vendor name/URL/email, so the processor, factory, and editor agree. A class UID is FOREVER-STABLE once shipped — minted once, never regenerated. *(Newly authored per this dispatch's brief — no existing root-CLAUDE.md bullet; verified by reading `src/shell/instrument/reasampler_vst.h` directly.)*
|
||||
@@ -122,7 +122,10 @@ declared ahead of the instrument slots at that member in `reasampler_processor.h
|
||||
in-instance — it is NOT a cross-process poller/nonce handshake, and it must not grow into
|
||||
one.
|
||||
- The bake's availability probe runs on the SAME tick that paints the button, so the
|
||||
control can never be enabled on one tick and refuse on the next.
|
||||
control can never be enabled on one tick and refuse on the next. The bake Hold control's
|
||||
applicability (`resolveBakeHoldNeeded`) rides the same tick for the same reason, and
|
||||
because answering it costs a bridge read + bank parse whenever no loop override is set —
|
||||
do not move either into the paint path.
|
||||
- `editor_internal.h` is include-only — it has no TU of its own and must never become
|
||||
a public seam; only the `reasampler_editor` band-axis TUs include it.
|
||||
- **The editor window class carries `CS_DBLCLKS`, which REPLACES the second button-down of
|
||||
|
||||
@@ -89,6 +89,7 @@ if(WIN32 AND EXISTS "${VST3_SDK}/public.sdk/source/main/pluginfactory.cpp")
|
||||
waveform_view bank_sync browser_scroll param_slider tooltip
|
||||
theme component_geometry bank_grid trigger_seam envelope_overlay envelope_edit
|
||||
knob_deck deck_groups deck_values curve_popup spline_edit master_gain sample_usage
|
||||
bake_hold
|
||||
file_bytes curve_law stroke_aa
|
||||
bake_plan bake_render bake_reset bake_wire wav_codec)
|
||||
# SDK_INC gives the REAPER VST3 interfaces + API header for the bridge; WDL_INC gives
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "core/instrument/engine/filter/filter_params.h" // the filter's own control laws
|
||||
#include "core/instrument/engine/master_gain.h" // master-gain dB<->linear<->knob taper
|
||||
#include "core/instrument/ui/bake_hold.h" // the Hold knob's ladder map
|
||||
#include "core/instrument/ui/deck_groups.h" // sampleDeckGroups (the deck's composition)
|
||||
#include "core/instrument/ui/deck_values.h" // the parameter-set binding + its ms units
|
||||
#include "core/instrument/ui/knob_deck.h" // deckHeight / kDeckKnobSize (the band's own height)
|
||||
@@ -112,8 +113,13 @@ double ReaSamplerEditor::previewVelocity01() const {
|
||||
return static_cast<double>(processor_->previewVelocity()) / 127.0;
|
||||
}
|
||||
|
||||
double ReaSamplerEditor::bakeHoldNorm() const {
|
||||
return instrument::ui::bakeHoldNorm(params_.bakeHold);
|
||||
}
|
||||
|
||||
double ReaSamplerEditor::deckControlNorm(int id) const {
|
||||
if (id == -2) return previewVelocity01(); // the chrome preview-velocity knob
|
||||
if (id == kBakeHoldKnobId) return bakeHoldNorm();
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kKeyTrack:
|
||||
return clamp01(params_.keyTrack / kKeyTrackMax);
|
||||
@@ -145,6 +151,11 @@ void ReaSamplerEditor::applyDeckKnob(int id, double norm) {
|
||||
processor_->setPreviewVelocity(static_cast<std::uint8_t>(norm * 127.0 + 0.5));
|
||||
return;
|
||||
}
|
||||
if (id == kBakeHoldKnobId) {
|
||||
// A parameter-set edit like the deck's own knobs: the commit lands on release.
|
||||
params_.bakeHold = instrument::ui::bakeHoldFromNorm(norm);
|
||||
return;
|
||||
}
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kVoiceCount: {
|
||||
// Stepped: quantize the continuous drag to the integer count and track it live
|
||||
@@ -171,7 +182,7 @@ std::string ReaSamplerEditor::deckValueLabel(int id) const {
|
||||
char buf[24];
|
||||
buf[0] = '\0';
|
||||
const PlaySeconds& play = params_.play;
|
||||
switch (id == -2 ? ParamControl::kCount : static_cast<ParamControl>(id)) {
|
||||
switch (id < 0 ? ParamControl::kCount : static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kAttack:
|
||||
formatEnvTimeMs(play.adsr.attackSeconds, buf, sizeof(buf)); break;
|
||||
case ParamControl::kHold:
|
||||
@@ -263,7 +274,8 @@ std::string ReaSamplerEditor::deckValueLabel(int id) const {
|
||||
snprintf(buf, sizeof(buf), "^%.2f", curveExponentFor(id, play));
|
||||
break;
|
||||
default:
|
||||
// -2 (preview velocity) is labeled at its chrome call site; nothing else here.
|
||||
// The chrome knobs (preview velocity, bake Hold) are labeled at their own call
|
||||
// site; nothing else here.
|
||||
break;
|
||||
}
|
||||
return std::string(buf);
|
||||
|
||||
@@ -48,6 +48,19 @@ bool ReaSamplerEditor::mouseDownChrome(const FaceLayout& fl, int x, int y) {
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
// Bake Hold: the same grab-anchored knob drag, and inert on exactly the ticks it is not
|
||||
// drawn — the cell's width is reserved either way (sample_chrome.h says why).
|
||||
if (bakeHoldNeeded_ && contains(cr.holdCell, x, y)) {
|
||||
drag_ = DragKind::kDeckKnob;
|
||||
dragParamId_ = kBakeHoldKnobId;
|
||||
dragInnerCellId_ = -1;
|
||||
dragKnobStartValue_ = bakeHoldNorm();
|
||||
dragStartParams_ = params_;
|
||||
dragStartX_ = x;
|
||||
dragStartY_ = y;
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
// Radial preview-velocity knob: grab-anchored vertical drag — the grab itself never
|
||||
// jumps the value; the delta from the grab point maps via knobDragValue.
|
||||
if (contains(cr.velCell, x, y)) {
|
||||
@@ -94,6 +107,14 @@ bool ReaSamplerEditor::doubleClickChrome(const FaceLayout& fl, int x, int y) {
|
||||
// radial knob drawn by the same primitive, so it resolves through the same target rule
|
||||
// (inKnobFace), against the drawn circle rather than the cell's label band.
|
||||
if (selectedId_.empty() || !processor_) return false;
|
||||
if (bakeHoldNeeded_ && inKnobFace(fl.chrome.holdKnob, x, y)) {
|
||||
// The default is READ off a default-constructed parameter set, so there is no second
|
||||
// table of defaults to drift from the codec's own lift.
|
||||
params_.bakeHold = InstrumentParams{}.bakeHold;
|
||||
commitAndReload();
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
if (!inKnobFace(fl.chrome.velKnob, x, y)) return false;
|
||||
processor_->setPreviewVelocity(kPreviewVelocityDefault);
|
||||
invalidate();
|
||||
@@ -117,6 +138,7 @@ ReaSamplerEditor::HoverTarget ReaSamplerEditor::hoverChrome(const FaceLayout& fl
|
||||
const ChromeRects& cr = fl.chrome;
|
||||
if (contains(cr.navBrowse, x, y)) return {HoverKind::kNavBrowse, -1};
|
||||
if (selectedId_.empty()) return {}; // empty state — no interactive surfaces beyond nav
|
||||
if (bakeHoldNeeded_ && contains(cr.holdCell, x, y)) return {HoverKind::kHoldKnob, -1};
|
||||
if (contains(cr.bake, x, y)) return {HoverKind::kBake, -1};
|
||||
if (contains(cr.preview, x, y)) return {HoverKind::kPreview, -1};
|
||||
if (contains(cr.velCell, x, y)) return {HoverKind::kVelKnob, -1};
|
||||
|
||||
@@ -67,16 +67,15 @@ StageEnvelope ReaSamplerEditor::packEnvelope(OverlayEnv which, const PlaySeconds
|
||||
const double t0 = rate > 0.0 ? static_cast<double>(startFrame) / rate : 0.0;
|
||||
// The Trigger amp and filter AHDs live over the PLAY span; the pitch AHD over the whole
|
||||
// post-start span, since it keeps running after a Trigger one-shot's amplitude has ended.
|
||||
// effectiveLengthFraction, not the stored knob: the overlay's amp/filter AHD must read the
|
||||
// SAME span the engine plays, or its drawn shape and node drags cover only a fraction of
|
||||
// what the note actually does.
|
||||
const std::int64_t playLen =
|
||||
triggerPlayLength(play.trigger.lengthFraction, frames, startFrame);
|
||||
triggerPlayLength(effectiveLengthFraction(play), frames, startFrame);
|
||||
const double fullSpan =
|
||||
rate > 0.0 ? static_cast<double>((std::max)(std::int64_t{0}, frames - startFrame)) / rate
|
||||
: 0.0;
|
||||
// Voice::start makes kTrigLength inert while any spline EG is active (trigSpan == postStart,
|
||||
// not the %-length) — the overlay's amp/filter AHD must read the SAME span the engine plays,
|
||||
// or its drawn shape and node drags cover only a fraction of what the note actually does.
|
||||
const double playSpan =
|
||||
splineActive(play) ? fullSpan : (rate > 0.0 ? static_cast<double>(playLen) / rate : 0.0);
|
||||
const double playSpan = rate > 0.0 ? static_cast<double>(playLen) / rate : 0.0;
|
||||
const bool trigger = (play.playMode == PlayMode::Trigger);
|
||||
switch (which) {
|
||||
case OverlayEnv::kPitch:
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "core/instrument/note/musical_division.h" // divisionLabel (the Hold readout)
|
||||
#include "core/instrument/ui/keyboard_strip.h" // StripLayout / keyRect / noteName
|
||||
#include "core/instrument/ui/knob_deck.h" // kDeckKnobSize (the shared knob square)
|
||||
#include "core/ui/tooltip.h" // computeTooltip (shared placement math)
|
||||
@@ -145,6 +146,22 @@ void ReaSamplerEditor::paintChrome(LICE_IBitmap* bmp, const FaceLayout& fl, bool
|
||||
drawButton(bmp, box, "Bake", st, /*warn=*/false);
|
||||
}
|
||||
|
||||
// Bake Hold: the note length the bake holds the gate for. Drawn ONLY while the window
|
||||
// cannot be derived without it — every other dialed sound bakes with no user input, and a
|
||||
// control that did nothing there would read as one that did.
|
||||
if (bakeHoldNeeded_) {
|
||||
const bool dragging = (drag_ == DragKind::kDeckKnob && dragParamId_ == kBakeHoldKnobId);
|
||||
const bool hov = isHovered(HoverKind::kHoldKnob, -1);
|
||||
const InteractionState st = dragging ? InteractionState::Dragging
|
||||
: (hov ? InteractionState::Hover
|
||||
: InteractionState::Rest);
|
||||
drawKnobFace(bmp, cr.holdKnob, bakeHoldNorm(), st);
|
||||
const std::string label = dragging || hov
|
||||
? instrument::note::divisionLabel(params_.bakeHold)
|
||||
: std::string("Hold");
|
||||
kitTextCentered(bmp, cr.holdLabel, label.c_str(), Font::Micro, Role::TextDim);
|
||||
}
|
||||
|
||||
// Preview-trigger button (fires the loaded capture at root through the live voice engine).
|
||||
// A drawn play triangle rather than a label or an embedded image: it inherits the button's
|
||||
// own foreground role, so it stays legible in every interaction state at no build cost.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "core/ui/bank_grid.h" // ThumbnailKey / thumbnailKeyString (the pure key)
|
||||
#include "core/util/file_bytes.h" // shared whole-file loader
|
||||
#include "ext_keys.h"
|
||||
#include "core/instrument/bake/bake_plan.h" // bakeWindowNeedsHold (the Hold predicate)
|
||||
#include "core/instrument/engine/loop/loop_span.h" // defaultLoopBounds (the shared ghost span)
|
||||
#include "core/instrument/ui/browser_scroll.h" // nameMatchesQuery (type-to-filter)
|
||||
#include "shell/instrument/instrument_bake.h" // the deferred bake the sync tick runs
|
||||
@@ -116,6 +117,13 @@ void ReaSamplerEditor::onSyncTimer() {
|
||||
bakeAvailable_ = available;
|
||||
invalidate();
|
||||
}
|
||||
// Same cadence for the Hold control's applicability, so it too can never paint live on
|
||||
// one tick and be inert on the next.
|
||||
const bool holdNeeded = resolveBakeHoldNeeded();
|
||||
if (holdNeeded != bakeHoldNeeded_) {
|
||||
bakeHoldNeeded_ = holdNeeded;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// The click armed it; this is where it runs — same thread, one tick later, no mouse
|
||||
// capture held, and no REAPER action nested inside a mouse handler.
|
||||
@@ -254,6 +262,18 @@ ReaSamplerEditor::SetupMarkers ReaSamplerEditor::pickedMarkers(std::int64_t fram
|
||||
return m;
|
||||
}
|
||||
|
||||
bool ReaSamplerEditor::resolveBakeHoldNeeded() {
|
||||
// The mode test comes first so the common Trigger case never pays pickedMarkers' bridge
|
||||
// read. `m.hasLoop` alone is not the answer — the engine's fold also refuses a span that
|
||||
// reaches outside the PCM, which is exactly what the pure predicate is asked for.
|
||||
if (selectedId_.empty() || params_.play.playMode != PlayMode::Gate) return false;
|
||||
const auto frames = static_cast<std::int64_t>(monoPcmFor(selectedId_).size());
|
||||
if (frames <= 0) return false;
|
||||
const SetupMarkers m = pickedMarkers(frames);
|
||||
return instrument::bake::bakeWindowNeedsHold(
|
||||
PlayMode::Gate, SampleLoop{m.hasLoop, m.loopStart, m.loopEnd}, m.crossfade, frames);
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::applyMarkers(const SetupMarkers& m) {
|
||||
// Write the edited markers into the parameter set as the loop/start override. The bank
|
||||
// intrinsic is never written (read-only bank consumer).
|
||||
|
||||
@@ -30,6 +30,7 @@ using instrument::bake::resetAfterBake;
|
||||
using instrument::map::SampleRefEntry;
|
||||
using instrument::map::SelectedSample;
|
||||
using instrument::note::Tempo;
|
||||
using instrument::note::Velocity;
|
||||
using instrument::note::resolveNote;
|
||||
using wire::BakeOutcome;
|
||||
using wire::BakeRequest;
|
||||
@@ -127,13 +128,23 @@ BakeChainResult runBake(ReaSamplerProcessor& processor) {
|
||||
std::optional<SampleData> snapshot = processor.bakeSnapshot();
|
||||
if (!snapshot) return fail("the loaded capture could not be decoded for the render");
|
||||
|
||||
const auto plan = planBake(
|
||||
resolveNote(defaultBakeProgram(*snapshot, sampleRate, *tempo), *tempo), sampleRate,
|
||||
rootNote);
|
||||
if (!plan) return fail("the programmed capture window is empty");
|
||||
// The note fires at the velocity the user has been auditioning at: three velocity curves
|
||||
// are live, so a sound dialed at 127 does not bake as one dialed at 40.
|
||||
const Velocity velocity = Velocity::of(processor.previewVelocity());
|
||||
const instrument::bake::PlannedBake planned = planBake(
|
||||
resolveNote(defaultBakeProgram(*snapshot, sampleRate, *tempo, dialed.bakeHold,
|
||||
velocity),
|
||||
*tempo),
|
||||
sampleRate, rootNote);
|
||||
if (!planned.plan) {
|
||||
return fail(planned.refusal == instrument::bake::BakeRefusal::PastFrameCeiling
|
||||
? "the dialed sound is longer than one bake can hold"
|
||||
: "the programmed capture window is empty");
|
||||
}
|
||||
const instrument::bake::BakePlan& plan = *planned.plan;
|
||||
|
||||
const instrument::bake::BakeAudio audio =
|
||||
renderBake(std::move(*snapshot), *plan, processor.masterGainLinear());
|
||||
renderBake(std::move(*snapshot), plan, processor.masterGainLinear());
|
||||
if (audio.empty()) return fail("the offline pass produced no audio");
|
||||
|
||||
// buildFloat32Wav takes doubles and narrows; the narrowing back to float is the bank's
|
||||
@@ -164,7 +175,7 @@ BakeChainResult runBake(ReaSamplerProcessor& processor) {
|
||||
request.sourceRelativePath = source->relativePath;
|
||||
request.sourceDisplayName = sourceEntry->displayName;
|
||||
request.ownUsageKey = usageKeyFor(instanceGuid);
|
||||
request.rootNote = plan->note;
|
||||
request.rootNote = plan.note;
|
||||
request.generation = stamp;
|
||||
|
||||
const std::string key = bakeKeyFor(instanceGuid);
|
||||
|
||||
@@ -99,6 +99,11 @@ private:
|
||||
// the pure deck_groups module's — this alias keeps the shell's spelling.
|
||||
using ParamControl = instrument::ui::DeckParam;
|
||||
|
||||
// The chrome knobs are drags of kind kDeckKnob with no place in the deck's id space, so
|
||||
// they take negative sentinels (-2 is the preview velocity). Being outside
|
||||
// [0, DeckParam::kCount) is what keeps liveCommitFor answering "not a live control".
|
||||
static constexpr int kBakeHoldKnobId = -3;
|
||||
|
||||
// The waveform markers on the waveform band: start-point + the sustain loop's two ends,
|
||||
// in draw + hit order, then the crossfade handle. The crossfade is NOT part of the
|
||||
// full-height column hit-test — it answers only in its top-strip handle (waveform_view's
|
||||
@@ -127,6 +132,7 @@ private:
|
||||
kEnvRadio, // an envelope deck's overlay-select radio (index = radio control id)
|
||||
kCurveNode, // a velocity-curve control point (index = point index)
|
||||
kVelKnob, // the chrome preview-velocity radial knob
|
||||
kHoldKnob, // the chrome bake-Hold radial knob
|
||||
kStripKey, // a piano-strip key (index = MIDI note); carries the name tooltip
|
||||
kPopupClose, // the curve popup's Close (x) button
|
||||
};
|
||||
@@ -334,6 +340,11 @@ private:
|
||||
};
|
||||
SetupMarkers pickedMarkers(std::int64_t frames) const;
|
||||
|
||||
// Whether the loaded sound's bake window needs the user's Hold — the pure predicate
|
||||
// (bake_plan.h) answered against the markers this face is showing. Decodes and reads the
|
||||
// bank, so it is called on the sync tick, not per paint.
|
||||
bool resolveBakeHoldNeeded();
|
||||
|
||||
// Writes `m` into params_ as the loop/start override. Does NOT call commitAndReload —
|
||||
// callers decide live-drag vs final commit.
|
||||
void applyMarkers(const SetupMarkers& m);
|
||||
@@ -378,6 +389,8 @@ private:
|
||||
double liveSampleRate() const;
|
||||
// Persisted preview velocity as a 0..1 slider value (MIDI 1..127 -> [0,1]).
|
||||
double previewVelocity01() const;
|
||||
// The bake Hold division as a 0..1 knob position, via the pure bake_hold map.
|
||||
double bakeHoldNorm() const;
|
||||
|
||||
// The normalized [0,1] value a deck knob shows — parameter-set ids route through
|
||||
// controlValue/keyTrack; processor-side ids (voice count, master gain, preview velocity
|
||||
@@ -430,6 +443,10 @@ private:
|
||||
// Whether the extension's bake action is registered, resolved on the same tick that
|
||||
// governs the button's paint, so the control is never enabled and then refusing.
|
||||
bool bakeAvailable_ = false;
|
||||
// Whether the bake's window still needs the Hold control (bake_plan.h's
|
||||
// bakeWindowNeedsHold). Resolved on the same tick as bakeAvailable_ rather than per paint:
|
||||
// answering it costs a bridge read + bank parse whenever no loop override is set.
|
||||
bool bakeHoldNeeded_ = false;
|
||||
std::string bakeMessage_; // last outcome, shown in the title band
|
||||
int bakeMessageTicks_ = 0; // sync ticks the message survives
|
||||
|
||||
|
||||
Reference in New Issue
Block a user