65f6070348
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.
162 lines
6.9 KiB
C++
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
|