Q-W2v: split VST god-modules — editor 8 face-axis TUs (+pure layout hoist), processor 3 TUs, component_state_io codec split (extension drops the voice engine), zone_params.h, core/wire putLE; formats frozen, 61/61 green
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
// editor_internal.h — INTERNAL shared helpers for the ReaSamplerEditor TU family
|
||||
// (Q-W2v: the eight face-axis TUs split out of the former reasampler_editor.cpp).
|
||||
// Included ONLY by the editor's own shell TUs (editor_session / editor_controls /
|
||||
// editor_paint_* / editor_input_* / editor_platform) — never a public seam. Holds the
|
||||
// former god-TU's anonymous-namespace helpers that more than one split TU needs: the
|
||||
// Rect<->kit adapters, the small draw primitives (knob face / spectral strip / root
|
||||
// marker / title band), the label helpers, the deck group ids, and the velocity-curve
|
||||
// box derivation. All inline; behavior-identical to the pre-split definitions.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/instrument/engine/velocity_curve.h" // VelocityCurve::Box (curveBoxFromRect)
|
||||
#include "core/instrument/map/sample_map.h" // SampleChoice / SampleRefs (sampleLabel)
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect (the shared sub-rect type)
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "wdltypes.h"
|
||||
#include "lice/lice.h"
|
||||
|
||||
#include "core/audio/peaks.h" // Envelope (drawEnvelope)
|
||||
#include "core/instrument/ui/capture_browser.h" // BrowserLayout / cardThumbnailRect (thumbBins)
|
||||
#include "core/instrument/ui/param_slider.h" // KnobGeometry / KnobArc (drawKnobFace, FA4)
|
||||
#include "core/instrument/ui/keyboard_strip.h" // StripLayout / keyRect / isNaturalKey (spectral strip)
|
||||
#include "core/ui/component_geometry.h" // KitBox / waveformColumnCount
|
||||
#include "core/ui/theme.h" // Role / InteractionState / KitColor / spectralColor
|
||||
#include "shell/panel/draw_kit.h" // the L1 draw kit: fillSurface/text/drawWaveform/toLice
|
||||
#endif
|
||||
|
||||
namespace reasampler::vst {
|
||||
|
||||
// The deck group ids (shell-owned; knob_deck treats them opaquely). Left-to-right deck
|
||||
// order. Shared by the deck-desc builders (editor_controls) and the deck painter.
|
||||
enum DeckGroup {
|
||||
kGroupAmpEnv = 0,
|
||||
kGroupPitch,
|
||||
kGroupPitchEnv,
|
||||
kGroupVoice,
|
||||
kGroupMaster,
|
||||
};
|
||||
|
||||
// The S-VIEW-10 velocity-curve editor box metrics. Since r11/FB2 BOTH surfaces host the
|
||||
// curve in the POPUP (curve_popup), each summoned from its own mini preview button. The
|
||||
// INSET keeps node handles + the pick radius inside the border so an endpoint at amp 0/1
|
||||
// stays grabbable — the ONE curveBoxFromRect grammar the popup derives its mapping box
|
||||
// through. Drag-off: release beyond box+margin deletes the dragged node.
|
||||
inline constexpr int kVelCurveInset = 14;
|
||||
inline constexpr int kCurveDragOffMargin = 24;
|
||||
|
||||
// The pure-module mapping Box for a drawn curve rect: inset from the border so node
|
||||
// handles and the pick radius stay inside the box. Every consumer (paint, hit-test, add,
|
||||
// drag) derives the Box through this ONE formula, so drawn nodes and grabs never drift.
|
||||
inline instrument::engine::VelocityCurve::Box curveBoxFromRect(
|
||||
const instrument::ui::Rect& r) {
|
||||
return instrument::engine::VelocityCurve::Box{
|
||||
r.x + kVelCurveInset, r.y + kVelCurveInset,
|
||||
(std::max)(0, r.width - 2 * kVelCurveInset),
|
||||
(std::max)(0, r.height - 2 * kVelCurveInset)};
|
||||
}
|
||||
|
||||
// A short MIDI-note label ("C4", "F#3") for the root badge. Middle C (60) is C4 (the
|
||||
// common DAW convention REAPER uses).
|
||||
inline std::string noteLabel(int note) {
|
||||
static const char* kNames[12] = {"C", "C#", "D", "D#", "E", "F",
|
||||
"F#", "G", "G#", "A", "A#", "B"};
|
||||
if (note < 0) note = 0;
|
||||
if (note > 127) note = 127;
|
||||
const int octave = note / 12 - 1; // MIDI 0 = C-1; 60 = C4
|
||||
return std::string(kNames[note % 12]) + std::to_string(octave);
|
||||
}
|
||||
|
||||
// A display name for a bank sample id: the snapshotted bank list first, then the
|
||||
// instance-OWNED ref's displayName (pS — the label survives with the extension absent /
|
||||
// bank unreadable). "?" only when neither source knows the id.
|
||||
inline std::string sampleLabel(const std::vector<instrument::map::SampleChoice>& samples,
|
||||
const instrument::map::SampleRefs& refs,
|
||||
const std::string& id) {
|
||||
for (const instrument::map::SampleChoice& c : samples) {
|
||||
if (c.id == id) return c.displayName.empty() ? c.id : c.displayName;
|
||||
}
|
||||
for (const instrument::map::SampleRefEntry& e : refs) {
|
||||
if (e.sampleId == id && !e.displayName.empty()) return e.displayName;
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// --- Rect <-> kit adapters (Phase L, L3) -------------------------------------
|
||||
//
|
||||
// The editor's own sub-rect type is `Rect` (editor_geometry); the kit draws against
|
||||
// `KitBox` (component_geometry). This is the single boundary that bridges them so every
|
||||
// draw routes through the L1 kit (theme roles + draw_kit).
|
||||
inline ui::KitBox toKitBox(const instrument::ui::Rect& r) {
|
||||
return ui::KitBox{r.x, r.y, r.width, r.height};
|
||||
}
|
||||
|
||||
// Kit text in a palette ROLE (the common case). Left/Right/Center via Align.
|
||||
inline void kitText(LICE_IBitmap* bmp, const instrument::ui::Rect& r, const char* s,
|
||||
Font font, ui::Role role, Align align = Align::Left) {
|
||||
text(bmp, toKitBox(r), s, font, role, align);
|
||||
}
|
||||
|
||||
inline void kitTextCentered(LICE_IBitmap* bmp, const instrument::ui::Rect& r,
|
||||
const char* s, Font font, ui::Role role) {
|
||||
text(bmp, toKitBox(r), s, font, role, Align::Center);
|
||||
}
|
||||
|
||||
// Draw a peak envelope in `r` through the kit's shared waveform primitive (Phase L, L3).
|
||||
inline void drawEnvelope(LICE_IBitmap* bmp, const instrument::ui::Rect& r,
|
||||
const audio::Envelope& env) {
|
||||
drawWaveform(bmp, toKitBox(r), env);
|
||||
}
|
||||
|
||||
// The bin count a card's thumbnail is computed at: one bin per drawn pixel column — the
|
||||
// gap-free render comes from peaks::columnMinMax's exact partition, not from extra bins.
|
||||
// thumbnailFor clamps the request to the decoded frame count.
|
||||
inline int thumbBins(const instrument::ui::BrowserLayout& layout) {
|
||||
return (std::max)(1, kWaveformOversample *
|
||||
ui::waveformColumnCount(toKitBox(
|
||||
instrument::ui::cardThumbnailRect(layout, 0))));
|
||||
}
|
||||
|
||||
// Draw the title band with the live readout. Shared by the Sample face (nav visible) —
|
||||
// Browse/Zone draw their own back button in place of the nav.
|
||||
inline void drawTitleBand(LICE_IBitmap* bmp, const instrument::ui::Rect& title,
|
||||
const std::string& readout) {
|
||||
fillSurface(bmp, toKitBox(title), ui::Role::BgPanel, ui::InteractionState::Rest);
|
||||
instrument::ui::Rect titleText =
|
||||
instrument::ui::Rect::ltrb(title.x + 8, title.y, title.right() - 8, title.bottom());
|
||||
kitText(bmp, titleText, readout.c_str(), Font::Title, ui::Role::TextPrimary);
|
||||
}
|
||||
|
||||
// Draw one radial knob face (r11): the FA4 param_slider primitive owns the value<->angle
|
||||
// map; this turns it into LICE calls through the kit's palette roles. LICE's arc
|
||||
// convention matches param_slider's (angle 0 = 12 o'clock, positive clockwise) — but LICE
|
||||
// takes RADIANS, and drawing the 7->5 o'clock sweep THROUGH the top needs a continuous
|
||||
// angle span, so the degrees convert as (deg - 360) * pi/180, mapping 210..510 onto
|
||||
// -150..+150 degrees. One conversion, both arcs.
|
||||
inline void drawKnobFace(LICE_IBitmap* bmp, const instrument::ui::Rect& knobRect,
|
||||
double value01, ui::InteractionState st) {
|
||||
using instrument::ui::KnobArc;
|
||||
using instrument::ui::KnobGeometry;
|
||||
using instrument::ui::KnobPoint;
|
||||
const KnobGeometry kg = instrument::ui::computeKnob(knobRect);
|
||||
if (kg.radius <= 1.0) return;
|
||||
constexpr double kDegToRad = 3.14159265358979323846 / 180.0;
|
||||
const KnobArc arc{}; // the FA4 default 7->5 o'clock sweep
|
||||
const float cx = static_cast<float>(kg.centerX);
|
||||
const float cy = static_cast<float>(kg.centerY);
|
||||
const float rOuter = static_cast<float>(kg.radius) - 0.5f;
|
||||
const bool disabled = (st == ui::InteractionState::Disabled);
|
||||
const bool hot = (st == ui::InteractionState::Dragging || st == ui::InteractionState::Hover);
|
||||
|
||||
// Face: a filled circle in the cell surface color under the interaction state.
|
||||
LICE_FillCircle(bmp, cx, cy, rOuter - 1.f, toLice(ui::roleColorState(ui::Role::BgCell, st)),
|
||||
1.0f, 0, true);
|
||||
// Track: the full sweep as a hairline arc (the dead 60-degree arc at the bottom stays bare).
|
||||
const float a0 = static_cast<float>((arc.startDeg - 360.0) * kDegToRad);
|
||||
const float a1 = static_cast<float>(
|
||||
(arc.startDeg + instrument::ui::knobSweepDeg(arc) - 360.0) * kDegToRad);
|
||||
LICE_Arc(bmp, cx, cy, rOuter, a0, a1, toLice(ui::roleColor(ui::Role::LineHairline)), 1.0f, 0,
|
||||
true);
|
||||
// Value arc: start -> the value's angle, in the live accent (hot while under the pointer /
|
||||
// dragging, dim when disabled).
|
||||
const double v = value01 < 0.0 ? 0.0 : (value01 > 1.0 ? 1.0 : value01);
|
||||
if (v > 0.0) {
|
||||
const float av = static_cast<float>(
|
||||
(arc.startDeg + v * instrument::ui::knobSweepDeg(arc) - 360.0) * kDegToRad);
|
||||
const ui::Role valueRole = disabled ? ui::Role::TextDim
|
||||
: (hot ? ui::Role::AccentHot : ui::Role::AccentPrimary);
|
||||
LICE_Arc(bmp, cx, cy, rOuter, a0, av, toLice(ui::roleColor(valueRole)), 1.0f, 0, true);
|
||||
}
|
||||
// Needle: from ~35% radius out to the rim at the value's angle.
|
||||
const KnobPoint tip = instrument::ui::knobNeedlePoint(kg, arc, v);
|
||||
const float ix = cx + static_cast<float>((tip.x - kg.centerX) * 0.35);
|
||||
const float iy = cy + static_cast<float>((tip.y - kg.centerY) * 0.35);
|
||||
const ui::Role needleRole = disabled ? ui::Role::TextDim : ui::Role::TextPrimary;
|
||||
LICE_Line(bmp, static_cast<int>(ix + 0.5f), static_cast<int>(iy + 0.5f),
|
||||
static_cast<int>(tip.x + 0.5f), static_cast<int>(tip.y + 0.5f),
|
||||
toLice(ui::roleColor(needleRole)), 1.0f, 0, true);
|
||||
}
|
||||
|
||||
// Draw the pastel spectral keyboard-strip background (Phase L, L3) — the signature
|
||||
// surface. Fills each MIDI key column with its spectral hue, then draws faint per-octave
|
||||
// hairline ticks. Shared by the setup face + the Zones strip so both read as the same
|
||||
// spectrum. S-VIEW-7: accidentals get a dark bg/base wash over the hue (an OVERLAY, not
|
||||
// a keyboard shape) so pitch position reads as a keyboard at a glance.
|
||||
inline void drawSpectralStrip(LICE_IBitmap* bmp, const instrument::ui::Rect& stripArea) {
|
||||
using instrument::ui::StripLayout;
|
||||
if (stripArea.width <= 0 || stripArea.height <= 0) return;
|
||||
const StripLayout sl = instrument::ui::layoutStrip(stripArea.width, stripArea.height);
|
||||
const int sx = stripArea.x;
|
||||
const int sy = stripArea.y;
|
||||
const int h = stripArea.height;
|
||||
const LICE_pixel darkKey = toLice(ui::roleColor(ui::Role::BgBase));
|
||||
for (int n = 0; n <= 127; ++n) {
|
||||
const instrument::ui::Rect k = instrument::ui::keyRect(sl, n);
|
||||
const int x0 = k.x + sx;
|
||||
const int x1 =
|
||||
(n < 127) ? instrument::ui::keyRect(sl, n + 1).x + sx : stripArea.right();
|
||||
const int cw = (std::max)(1, x1 - x0);
|
||||
const ui::KitColor hue = ui::spectralColor(static_cast<double>(n) / 127.0);
|
||||
LICE_FillRect(bmp, x0, sy, cw, h, toLice(hue), 0.55f, 0);
|
||||
if (!instrument::ui::isNaturalKey(n)) {
|
||||
LICE_FillRect(bmp, x0, sy, cw, h, darkKey, 0.55f, 0);
|
||||
}
|
||||
}
|
||||
// Faint per-octave key ticks (hairline role) for orientation.
|
||||
const LICE_pixel tick = toLice(ui::roleColor(ui::Role::LineHairline));
|
||||
for (int n = 0; n <= 127; n += 12) {
|
||||
const instrument::ui::Rect k = instrument::ui::keyRect(sl, n);
|
||||
LICE_Line(bmp, k.x + sx, sy, k.x + sx, sy + h, tick, 1.0f, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the single-capture root marker on the strip: an accent-primary bar with a soft
|
||||
// STATIC glow (a wider, lower-alpha accent bar behind it) — the "this is live" mark.
|
||||
inline void drawRootMarker(LICE_IBitmap* bmp, const instrument::ui::Rect& stripArea,
|
||||
const instrument::ui::StripLayout& sl, int root) {
|
||||
const int sx = stripArea.x;
|
||||
const int sy = stripArea.y;
|
||||
const int h = stripArea.height;
|
||||
const instrument::ui::Rect marker = instrument::ui::rootMarkerRect(sl, root);
|
||||
const int mw = (std::max)(2, marker.width);
|
||||
const LICE_pixel accent = toLice(ui::roleColor(ui::Role::AccentPrimary));
|
||||
const LICE_pixel glow = toLice(ui::roleColor(ui::Role::AccentHot));
|
||||
// Static glow: a wider low-alpha halo behind the crisp bar (a drawn state, not a pulse).
|
||||
LICE_FillRect(bmp, marker.x + sx - 3, sy, mw + 6, h, glow, 0.30f, 0);
|
||||
LICE_FillRect(bmp, marker.x + sx, sy, mw, h, accent, 1.0f, 0);
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
} // namespace reasampler::vst
|
||||
Reference in New Issue
Block a user