Files
reasampler/src/shell/instrument/editor_input_chrome.cpp
T
daniel 65f6070348 Bake window: derived note lengths carry exact durations, not ladder rungs — a long take is no longer cut at 384 beats
Hold keeps its picker. Also: one home for the %-fold, duration-ordered Hold travel, and a corrupt tail degrades to absent rather than fabricating one.
2026-08-01 21:10:38 -04:00

162 lines
6.9 KiB
C++

// editor_input_chrome.cpp — the CHROME band's input: the Browse nav, the preview trigger,
// the preview-velocity knob grab, the channel toggle, and the piano strip's root grab plus
// its live drag. Windows-only.
#include "shell/instrument/reasampler_editor.h"
#ifdef _WIN32
#include "core/instrument/ui/keyboard_strip.h" // keyAtPoint / resolveDragNote (root key)
#include "core/instrument/ui/knob_deck.h" // inKnobFace (the shared reset target rule)
#include "shell/instrument/editor_internal.h"
#include "shell/instrument/reasampler_processor.h"
namespace reasampler::vst {
using namespace reasampler::ui;
using namespace reasampler::instrument::ui;
bool ReaSamplerEditor::mouseDownChrome(const FaceLayout& fl, int x, int y) {
const ChromeRects& cr = fl.chrome;
if (contains(cr.navBrowse, x, y)) {
// Open the Browse modal; seed its pending pick from the loaded id so the current
// capture reads as pre-selected.
browsePendingId_ = selectedId_;
lastBrowseClickCard_ = -1;
view_ = View::kBrowse;
invalidate();
return true;
}
if (selectedId_.empty()) return false; // empty state — nav only
// Preview-trigger button: fire the loaded capture at its root through the voice engine
// (momentary — note-on on press, note-off on release).
if (contains(cr.preview, x, y)) {
const int note = effectiveRoot();
if (previewingNote_ >= 0) processor_->previewNoteOff(previewingNote_);
previewingNote_ = note;
processor_->previewNoteOn(note);
invalidate();
return true;
}
// Resample bake. Arm only: onSyncTimer runs it a tick later, off this handler's stack
// and with no mouse capture held (instrument_bake.h says why that matters). A click
// while the extension is absent is swallowed — the button already paints Disabled.
if (contains(cr.bake, x, y)) {
if (bakeAvailable_) bakePending_ = true;
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)) {
drag_ = DragKind::kDeckKnob;
dragParamId_ = -2; // sentinel: the preview velocity knob (a processor param)
dragInnerCellId_ = -1; // peer of mouseDownDeck's own grab-time init, for symmetry
dragKnobStartValue_ = previewVelocity01();
dragStartX_ = x;
dragStartY_ = y;
invalidate();
return true;
}
if (contains(cr.chanMono, x, y)) {
channelMode_ = ChannelMode::Mono;
processor_->setChannelMode(ChannelMode::Mono);
invalidate();
return true;
}
if (contains(cr.chanStereo, x, y)) {
channelMode_ = ChannelMode::Stereo;
processor_->setChannelMode(ChannelMode::Stereo);
invalidate();
return true;
}
// The piano strip: clicking a key sets the root, and holding tracks the pointer. The
// click itself lands below as the first (unmoved) drag resolve.
if (!cr.rootStrip.empty()) {
const StripLayout sl = layoutStrip(cr.rootStrip.width, cr.rootStrip.height);
if (keyAtPoint(sl, x - cr.rootStrip.x, y - cr.rootStrip.y) >= 0) {
drag_ = DragKind::kRootMarker;
dragStartParams_ = params_;
onMouseMove(x, y);
return true;
}
}
// A click on the strip row's background is consumed so it can't fall through to a
// band the user cannot see under the chrome.
return contains(cr.controls, x, y);
}
bool ReaSamplerEditor::doubleClickChrome(const FaceLayout& fl, int x, int y) {
// The preview-velocity dial answers the same reset gesture the deck's knobs do — it is a
// 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. A reset that changes nothing
// commits nothing — the same rule the release path applies.
const instrument::note::Division reset = InstrumentParams{}.bakeHold;
if (params_.bakeHold != reset) {
params_.bakeHold = reset;
commitAndReload();
}
invalidate();
return true;
}
if (!inKnobFace(fl.chrome.velKnob, x, y)) return false;
processor_->setPreviewVelocity(kPreviewVelocityDefault);
invalidate();
return true;
}
void ReaSamplerEditor::dragChrome(const FaceLayout& fl, int x, int y) {
const Rect& stripArea = fl.chrome.rootStrip;
if (stripArea.empty()) return;
// Absolute tracking, not a pixel delta: with black keys overlaying whites there is no
// one pixels-per-semitone rate a delta could use.
const StripLayout sl = layoutStrip(stripArea.width, stripArea.height);
const int note = resolveDragNote(sl, x - stripArea.x, y - stripArea.y);
if (note < 0) return;
params_.rootOverride = note;
invalidate(); // live feedback; the commit lands on WM_LBUTTONUP
}
ReaSamplerEditor::HoverTarget ReaSamplerEditor::hoverChrome(const FaceLayout& fl, int x,
int y) const {
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};
if (contains(cr.chanMono, x, y)) return {HoverKind::kChanMono, -1};
if (contains(cr.chanStereo, x, y)) return {HoverKind::kChanStereo, -1};
if (!cr.rootStrip.empty()) {
const StripLayout sl = layoutStrip(cr.rootStrip.width, cr.rootStrip.height);
const int note = keyAtPoint(sl, x - cr.rootStrip.x, y - cr.rootStrip.y);
if (note >= 0) return {HoverKind::kStripKey, note};
}
return {};
}
} // namespace reasampler::vst
#endif // _WIN32