feat: run the per-voice filter between the pitch and amp stages, with its own deck
Params ride the one parameter set; payload v8 -> v9, off by default. Deck composition moves to a pure deck_groups module in pitch -> filter -> amp order.
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`,
|
||||
`curve_popup`, `master_gain`, `reasampler_uid.h`) lives in `core/instrument/*` and
|
||||
`deck_groups`, `curve_popup`, `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
|
||||
@@ -90,7 +90,7 @@ scattered `#ifdef`s in the VST shell, except the one described below).
|
||||
- `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` (parameter plumbing + the ONE `faceLayout` band resolve every paint and hit-test path shares), 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).
|
||||
- `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, deck group ids, and the velocity-curve box derivation — the helpers more than one band TU needs. The piano-strip and root-key draws live in `editor_paint_chrome`, their only consumer, not here.
|
||||
- `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.)*
|
||||
|
||||
## Gotchas
|
||||
|
||||
@@ -85,7 +85,7 @@ if(WIN32 AND EXISTS "${VST3_SDK}/public.sdk/source/main/pluginfactory.cpp")
|
||||
capture_browser keyboard_strip sample_bands sample_chrome
|
||||
waveform_view bank_sync browser_scroll param_slider tooltip
|
||||
theme component_geometry bank_grid trigger_seam envelope_overlay envelope_edit
|
||||
knob_deck curve_popup master_gain sample_usage file_bytes)
|
||||
knob_deck deck_groups curve_popup master_gain sample_usage file_bytes)
|
||||
# SDK_INC gives the REAPER VST3 interfaces + API header for the bridge; WDL_INC gives
|
||||
# LICE for the editor. The VST3 SDK headers arrive via vst3_sdk PUBLIC.
|
||||
target_include_directories(reasampler_vst PRIVATE ${REASAMPLER_SRC_DIR} ${SDK_INC} ${WDL_INC})
|
||||
|
||||
@@ -12,11 +12,13 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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/map/trigger_seam.h" // triggerPlayLength / fade fraction converters
|
||||
#include "core/instrument/ui/deck_groups.h" // sampleDeckGroups (the deck's composition)
|
||||
#include "core/instrument/ui/knob_deck.h" // deckHeight / kDeckKnobSize (the band's own height)
|
||||
#include "core/util/clamp01.h"
|
||||
#include "shell/instrument/editor_internal.h" // DeckGroup ids
|
||||
#include "shell/instrument/editor_internal.h"
|
||||
#include "shell/instrument/reasampler_processor.h"
|
||||
|
||||
namespace reasampler::vst {
|
||||
@@ -28,9 +30,16 @@ using instrument::ui::chromeRects;
|
||||
using instrument::ui::deckHeight;
|
||||
using instrument::ui::kDeckKnobSize;
|
||||
using instrument::ui::kPad;
|
||||
using instrument::ui::deckBipolarFromNorm;
|
||||
using instrument::ui::deckNormFromBipolar;
|
||||
using instrument::ui::sampleDeckGroups;
|
||||
using instrument::engine::formatMasterGainLabel;
|
||||
using instrument::engine::masterGainLinearFromNorm;
|
||||
using instrument::engine::masterGainNormFromLinear;
|
||||
using instrument::engine::filter::MorphLaw;
|
||||
using instrument::engine::filter::filterCutoffHzFromNorm;
|
||||
using instrument::engine::filter::filterDriveDepthFromNorm;
|
||||
using instrument::engine::filter::filterQFromNorm;
|
||||
using util::clamp01;
|
||||
|
||||
namespace {
|
||||
@@ -53,7 +62,7 @@ ReaSamplerEditor::FaceLayout ReaSamplerEditor::faceLayout(int w, int h) const {
|
||||
// chrome interior, and the deck descriptors can never be derived three different ways.
|
||||
// The deck's own wrapped height is the only interior measurement the allocator needs.
|
||||
FaceLayout fl;
|
||||
fl.deckDescs = deckGroupDescs(params_.play);
|
||||
fl.deckDescs = sampleDeckGroups(params_.play.playMode);
|
||||
fl.bands = computeSampleBands(w, h, deckHeight(fl.deckDescs, w - 2 * kPad));
|
||||
fl.chrome = chromeRects(fl.bands.chrome, kDeckKnobSize);
|
||||
return fl;
|
||||
@@ -87,6 +96,23 @@ double ReaSamplerEditor::controlValue(int id, const PlaySeconds& play) const {
|
||||
case ParamControl::kPitchEnvDepth:
|
||||
// Signed depth centered at 0.5 (0.5 == 0 semitones).
|
||||
return clamp01(0.5 + play.pitchEnv.peakSemitones / (2.0 * kPitchDepthMaxSemis));
|
||||
// Filter. The four tone controls ARE the module's normalized positions — stored and
|
||||
// shown as-is, so the knob travel is exactly filter_params' own law.
|
||||
case ParamControl::kFilterEnable: return play.filter.enabled ? 1.0 : 0.0;
|
||||
case ParamControl::kFilterLaw:
|
||||
return play.filter.settings.morphLaw == MorphLaw::HighNotchLow ? 1.0 : 0.0;
|
||||
case ParamControl::kFilterMorph: return clamp01(play.filter.settings.morphNorm);
|
||||
case ParamControl::kFilterCutoff: return clamp01(play.filter.settings.cutoffNorm);
|
||||
case ParamControl::kFilterQ: return clamp01(play.filter.settings.resonanceNorm);
|
||||
case ParamControl::kFilterDrive: return clamp01(play.filter.settings.driveNorm);
|
||||
case ParamControl::kFilterModAmt: return deckNormFromBipolar(play.filter.modAmount);
|
||||
case ParamControl::kFilterVel: return deckNormFromBipolar(play.filter.velAmount);
|
||||
case ParamControl::kFilterKeyTrack:return clamp01(play.filter.keyTrack / kKeyTrackMax);
|
||||
case ParamControl::kFilterEnvAttack: return secToNorm(play.filter.env.attackSeconds);
|
||||
case ParamControl::kFilterEnvHold: return secToNorm(play.filter.env.holdSeconds);
|
||||
case ParamControl::kFilterEnvDecay: return secToNorm(play.filter.env.decaySeconds);
|
||||
case ParamControl::kFilterEnvSustain: return clamp01(play.filter.env.sustainLevel);
|
||||
case ParamControl::kFilterEnvRelease: return secToNorm(play.filter.env.releaseSeconds);
|
||||
default: return 0.0;
|
||||
}
|
||||
}
|
||||
@@ -126,6 +152,33 @@ void ReaSamplerEditor::applyControl(int id, PlaySeconds& play, double value,
|
||||
case ParamControl::kPitchEnvDepth:
|
||||
play.pitchEnv.peakSemitones = (clamp01(value) - 0.5) * 2.0 * kPitchDepthMaxSemis;
|
||||
break;
|
||||
case ParamControl::kFilterEnable: play.filter.enabled = (segment == 1); break;
|
||||
case ParamControl::kFilterLaw:
|
||||
play.filter.settings.morphLaw =
|
||||
(segment == 1) ? MorphLaw::HighNotchLow : MorphLaw::HighBandLow;
|
||||
break;
|
||||
case ParamControl::kFilterMorph:
|
||||
play.filter.settings.morphNorm = static_cast<float>(clamp01(value)); break;
|
||||
case ParamControl::kFilterCutoff:
|
||||
play.filter.settings.cutoffNorm = static_cast<float>(clamp01(value)); break;
|
||||
case ParamControl::kFilterQ:
|
||||
play.filter.settings.resonanceNorm = static_cast<float>(clamp01(value)); break;
|
||||
case ParamControl::kFilterDrive:
|
||||
play.filter.settings.driveNorm = static_cast<float>(clamp01(value)); break;
|
||||
case ParamControl::kFilterModAmt: play.filter.modAmount = deckBipolarFromNorm(value); break;
|
||||
case ParamControl::kFilterVel: play.filter.velAmount = deckBipolarFromNorm(value); break;
|
||||
case ParamControl::kFilterKeyTrack:
|
||||
play.filter.keyTrack = clamp01(value) * kKeyTrackMax; break;
|
||||
case ParamControl::kFilterEnvAttack:
|
||||
play.filter.env.attackSeconds = normToSec(value); break;
|
||||
case ParamControl::kFilterEnvHold:
|
||||
play.filter.env.holdSeconds = normToSec(value); break;
|
||||
case ParamControl::kFilterEnvDecay:
|
||||
play.filter.env.decaySeconds = normToSec(value); break;
|
||||
case ParamControl::kFilterEnvSustain:
|
||||
play.filter.env.sustainLevel = clamp01(value); break;
|
||||
case ParamControl::kFilterEnvRelease:
|
||||
play.filter.env.releaseSeconds = normToSec(value); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -151,68 +204,6 @@ double ReaSamplerEditor::previewVelocity01() const {
|
||||
return static_cast<double>(processor_->previewVelocity()) / 127.0;
|
||||
}
|
||||
|
||||
std::vector<DeckGroupDesc> ReaSamplerEditor::deckGroupDescs(const PlaySeconds& play) const {
|
||||
// The deck band's groups, left to right. Group widths are mode-independent: AMP ENVELOPE
|
||||
// reserves its 5-cell Gate width (Trigger leaves two blank cells), so a Gate<->Trigger
|
||||
// flip repopulates in place and never reflows the neighbouring groups.
|
||||
std::vector<DeckGroupDesc> out;
|
||||
{
|
||||
DeckGroupDesc amp;
|
||||
amp.id = kGroupAmpEnv;
|
||||
amp.captionWidth = 78;
|
||||
amp.captionToggle = {static_cast<int>(ParamControl::kPlayMode), 44};
|
||||
if (play.playMode == PlayMode::Gate) {
|
||||
amp.cellIds = {static_cast<int>(ParamControl::kAttack),
|
||||
static_cast<int>(ParamControl::kHold),
|
||||
static_cast<int>(ParamControl::kDecay),
|
||||
static_cast<int>(ParamControl::kSustain),
|
||||
static_cast<int>(ParamControl::kRelease)};
|
||||
} else {
|
||||
// Trigger, time-ordered left-to-right (Fade In / Length % / Fade Out — matches
|
||||
// the drawn envelope), plus the two reserved blanks.
|
||||
amp.cellIds = {static_cast<int>(ParamControl::kTrigFadeIn),
|
||||
static_cast<int>(ParamControl::kTrigLength),
|
||||
static_cast<int>(ParamControl::kTrigFadeOut), -1, -1};
|
||||
}
|
||||
out.push_back(std::move(amp));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc pitch;
|
||||
pitch.id = kGroupPitch;
|
||||
pitch.captionWidth = 38;
|
||||
pitch.captionToggle = {static_cast<int>(ParamControl::kPitchEngine), 48};
|
||||
pitch.cellIds = {static_cast<int>(ParamControl::kKeyTrack)};
|
||||
out.push_back(std::move(pitch));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc penv;
|
||||
penv.id = kGroupPitchEnv;
|
||||
penv.captionWidth = 58;
|
||||
penv.captionToggle = {static_cast<int>(ParamControl::kPitchEnvEnable), 32};
|
||||
penv.cellIds = {static_cast<int>(ParamControl::kPitchEnvAttack),
|
||||
static_cast<int>(ParamControl::kPitchEnvDecay),
|
||||
static_cast<int>(ParamControl::kPitchEnvDepth)};
|
||||
out.push_back(std::move(penv));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc voice;
|
||||
voice.id = kGroupVoice;
|
||||
voice.captionWidth = 38;
|
||||
voice.captionToggle = {static_cast<int>(ParamControl::kVoiceMode), 40};
|
||||
voice.cellIds = {static_cast<int>(ParamControl::kVoiceCount)};
|
||||
voice.rowToggle = {static_cast<int>(ParamControl::kMonoTrigger), 44};
|
||||
out.push_back(std::move(voice));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc master;
|
||||
master.id = kGroupMaster;
|
||||
master.captionWidth = 46;
|
||||
master.cellIds = {static_cast<int>(ParamControl::kMasterGain)};
|
||||
out.push_back(std::move(master));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
double ReaSamplerEditor::deckControlNorm(int id) const {
|
||||
if (id == -2) return previewVelocity01(); // the chrome preview-velocity knob
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
@@ -294,6 +285,43 @@ std::string ReaSamplerEditor::deckValueLabel(int id) const {
|
||||
snprintf(buf, sizeof(buf), "%d", voiceCount_); break;
|
||||
case ParamControl::kMasterGain:
|
||||
formatMasterGainLabel(deckControlNorm(id), buf, sizeof(buf)); break;
|
||||
// Filter readouts run the stored normalized positions back through the module's OWN
|
||||
// laws, so what the label says is what the kernel is solved for.
|
||||
case ParamControl::kFilterMorph: {
|
||||
const double m = play.filter.settings.morphNorm;
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", m * 100.0);
|
||||
break;
|
||||
}
|
||||
case ParamControl::kFilterCutoff: {
|
||||
const float hz = filterCutoffHzFromNorm(play.filter.settings.cutoffNorm);
|
||||
if (hz >= 1000.0f) snprintf(buf, sizeof(buf), "%.2fk", hz / 1000.0f);
|
||||
else snprintf(buf, sizeof(buf), "%.0fHz", hz);
|
||||
break;
|
||||
}
|
||||
case ParamControl::kFilterQ:
|
||||
snprintf(buf, sizeof(buf), "%.2f",
|
||||
static_cast<double>(filterQFromNorm(play.filter.settings.resonanceNorm)));
|
||||
break;
|
||||
case ParamControl::kFilterDrive:
|
||||
snprintf(buf, sizeof(buf), "%.2f",
|
||||
static_cast<double>(filterDriveDepthFromNorm(play.filter.settings.driveNorm)));
|
||||
break;
|
||||
case ParamControl::kFilterModAmt:
|
||||
snprintf(buf, sizeof(buf), "%+.0f%%", play.filter.modAmount * 100.0); break;
|
||||
case ParamControl::kFilterVel:
|
||||
snprintf(buf, sizeof(buf), "%+.0f%%", play.filter.velAmount * 100.0); break;
|
||||
case ParamControl::kFilterKeyTrack:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.filter.keyTrack * 100.0); break;
|
||||
case ParamControl::kFilterEnvAttack:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.filter.env.attackSeconds); break;
|
||||
case ParamControl::kFilterEnvHold:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.filter.env.holdSeconds); break;
|
||||
case ParamControl::kFilterEnvDecay:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.filter.env.decaySeconds); break;
|
||||
case ParamControl::kFilterEnvSustain:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", play.filter.env.sustainLevel * 100.0); break;
|
||||
case ParamControl::kFilterEnvRelease:
|
||||
snprintf(buf, sizeof(buf), "%.3fs", play.filter.env.releaseSeconds); break;
|
||||
default:
|
||||
// -2 (preview velocity) is labeled at its chrome call site; nothing else here.
|
||||
break;
|
||||
|
||||
@@ -16,6 +16,30 @@ namespace reasampler::vst {
|
||||
using namespace reasampler::ui;
|
||||
using namespace reasampler::instrument::ui;
|
||||
|
||||
bool ReaSamplerEditor::deckKnobDisabled(int id) const {
|
||||
switch (static_cast<ParamControl>(id)) {
|
||||
case ParamControl::kPitchEnvAttack:
|
||||
case ParamControl::kPitchEnvDecay:
|
||||
case ParamControl::kPitchEnvDepth:
|
||||
return !params_.play.pitchEnv.enabled;
|
||||
case ParamControl::kFilterMorph:
|
||||
case ParamControl::kFilterCutoff:
|
||||
case ParamControl::kFilterQ:
|
||||
case ParamControl::kFilterDrive:
|
||||
case ParamControl::kFilterModAmt:
|
||||
case ParamControl::kFilterVel:
|
||||
case ParamControl::kFilterKeyTrack:
|
||||
case ParamControl::kFilterEnvAttack:
|
||||
case ParamControl::kFilterEnvHold:
|
||||
case ParamControl::kFilterEnvDecay:
|
||||
case ParamControl::kFilterEnvSustain:
|
||||
case ParamControl::kFilterEnvRelease:
|
||||
return !params_.play.filter.enabled;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ReaSamplerEditor::mouseDownDeck(const FaceLayout& fl, int x, int y) {
|
||||
const Rect& band = fl.bands.decks;
|
||||
if (!contains(band, x, y)) return false;
|
||||
@@ -46,8 +70,14 @@ bool ReaSamplerEditor::mouseDownDeck(const FaceLayout& fl, int x, int y) {
|
||||
invalidate();
|
||||
break;
|
||||
}
|
||||
case ParamControl::kFilterLaw:
|
||||
// Inert while the filter is off, matching its Disabled paint.
|
||||
if (!params_.play.filter.enabled) break;
|
||||
applyParamControl(hit.id, 0.0, hit.segment);
|
||||
commitAndReload();
|
||||
break;
|
||||
default:
|
||||
// Parameter-set toggles (play mode / pitch engine / pitch-env enable).
|
||||
// Parameter-set toggles (play mode / pitch engine / pitch-env + filter enable).
|
||||
applyParamControl(hit.id, 0.0, hit.segment);
|
||||
commitAndReload();
|
||||
break;
|
||||
@@ -55,12 +85,8 @@ bool ReaSamplerEditor::mouseDownDeck(const FaceLayout& fl, int x, int y) {
|
||||
return true;
|
||||
}
|
||||
if (hit.kind == DeckHitKind::Knob) {
|
||||
// PITCH ENV knobs are Disabled (drawn, inert) while the envelope is off.
|
||||
const bool pitchEnvKnob =
|
||||
hit.id == static_cast<int>(ParamControl::kPitchEnvAttack) ||
|
||||
hit.id == static_cast<int>(ParamControl::kPitchEnvDecay) ||
|
||||
hit.id == static_cast<int>(ParamControl::kPitchEnvDepth);
|
||||
if (pitchEnvKnob && !params_.play.pitchEnv.enabled) return true;
|
||||
// Knobs of a disabled group are drawn but inert.
|
||||
if (deckKnobDisabled(hit.id)) return true;
|
||||
drag_ = DragKind::kDeckKnob;
|
||||
dragParamId_ = hit.id;
|
||||
dragKnobStartValue_ = deckControlNorm(hit.id);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// editor_internal.h — shared helpers for the ReaSamplerEditor 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. Holds the Rect<->kit adapters,
|
||||
// small draw primitives (knob face / title band), label helpers, deck group ids, and the
|
||||
// velocity-curve box derivation. All inline.
|
||||
// small draw primitives (knob face / title band), label helpers, and the velocity-curve box
|
||||
// derivation. All inline.
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -30,15 +30,6 @@
|
||||
|
||||
namespace reasampler::vst {
|
||||
|
||||
// Deck group ids (shell-owned; knob_deck treats them opaquely), left-to-right order.
|
||||
enum DeckGroup {
|
||||
kGroupAmpEnv = 0,
|
||||
kGroupPitch,
|
||||
kGroupPitchEnv,
|
||||
kGroupVoice,
|
||||
kGroupMaster,
|
||||
};
|
||||
|
||||
// Velocity-curve editor box metrics. The inset keeps node handles + the pick radius
|
||||
// inside the border so an endpoint at amp 0/1 stays grabbable; drag-off beyond
|
||||
// box+margin deletes the dragged node.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// editor_paint_deck.cpp — the DECKS band's painter: the fenced control groups (AMP
|
||||
// ENVELOPE / PITCH / PITCH ENV / VOICE / MASTER), their captions, the compact caption and
|
||||
// row toggles, and the radial knobs with the label<->value swap on hover/drag.
|
||||
// Windows-only; the deck's cell geometry is the pure knob_deck layout.
|
||||
// editor_paint_deck.cpp — the DECKS band's painter: the fenced control groups, their
|
||||
// captions, the compact caption and row toggles, and the radial knobs with the label<->value
|
||||
// swap on hover/drag. Windows-only; the deck's cell geometry is the pure knob_deck layout and
|
||||
// its group composition the pure deck_groups list.
|
||||
|
||||
#include "shell/instrument/reasampler_editor.h"
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/instrument/engine/filter/filter_morph.h" // MorphLaw (the law toggle's state)
|
||||
#include "core/instrument/ui/knob_deck.h" // deck layout + kDeckKnobSize
|
||||
#include "shell/instrument/editor_internal.h" // kit adapters + knob face + DeckGroup ids
|
||||
#include "shell/instrument/editor_internal.h" // kit adapters + knob face
|
||||
#include "shell/instrument/reasampler_processor.h"
|
||||
|
||||
namespace reasampler::vst {
|
||||
@@ -68,6 +69,18 @@ void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) {
|
||||
case ParamControl::kPitchEnvDepth: return "P.Depth";
|
||||
case ParamControl::kVoiceCount: return "Voices";
|
||||
case ParamControl::kMasterGain: return "Gain";
|
||||
case ParamControl::kFilterMorph: return "Mode";
|
||||
case ParamControl::kFilterCutoff: return "Cutoff";
|
||||
case ParamControl::kFilterQ: return "Res";
|
||||
case ParamControl::kFilterDrive: return "Drive";
|
||||
case ParamControl::kFilterModAmt: return "Mod";
|
||||
case ParamControl::kFilterVel: return "Vel";
|
||||
case ParamControl::kFilterKeyTrack: return "Key Trk";
|
||||
case ParamControl::kFilterEnvAttack: return "F.Att";
|
||||
case ParamControl::kFilterEnvHold: return "F.Hold";
|
||||
case ParamControl::kFilterEnvDecay: return "F.Dec";
|
||||
case ParamControl::kFilterEnvSustain: return "F.Sus";
|
||||
case ParamControl::kFilterEnvRelease: return "F.Rel";
|
||||
default: return "";
|
||||
}
|
||||
};
|
||||
@@ -79,11 +92,13 @@ void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) {
|
||||
hairline, 1.0f, 0);
|
||||
const char* caption = "";
|
||||
switch (g.id) {
|
||||
case kGroupAmpEnv: caption = "AMP ENVELOPE"; break;
|
||||
case kGroupPitch: caption = "PITCH"; break;
|
||||
case kGroupPitchEnv: caption = "PITCH ENV"; break;
|
||||
case kGroupVoice: caption = "VOICE"; break;
|
||||
case kGroupMaster: caption = "MASTER"; break;
|
||||
case kGroupAmpEnv: caption = "AMP ENVELOPE"; break;
|
||||
case kGroupPitch: caption = "PITCH"; break;
|
||||
case kGroupPitchEnv: caption = "PITCH ENV"; break;
|
||||
case kGroupFilter: caption = "FILTER"; break;
|
||||
case kGroupFilterEnv: caption = "FILTER ENV"; break;
|
||||
case kGroupVoice: caption = "VOICE"; break;
|
||||
case kGroupMaster: caption = "MASTER"; break;
|
||||
default: break;
|
||||
}
|
||||
kitText(bmp, g.caption, caption, Font::Micro, Role::TextDim);
|
||||
@@ -105,20 +120,30 @@ void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) {
|
||||
case ParamControl::kVoiceMode:
|
||||
drawToggle(g.captionToggle, "Poly", "Mono", isMono, false);
|
||||
break;
|
||||
case ParamControl::kFilterEnable:
|
||||
drawToggle(g.captionToggle, "Off", "On", play.filter.enabled, false);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
// The row toggle (VOICE group's Retrig|Legato) — live only in Mono.
|
||||
// Row toggles: VOICE's Retrig|Legato (live only in Mono) and FILTER's morph law.
|
||||
if (g.rowToggle.id >= 0) {
|
||||
drawToggle(g.rowToggle, "Retrig", "Legato",
|
||||
monoTrigger_ == MonoTrigger::Legato, !isMono);
|
||||
if (static_cast<ParamControl>(g.rowToggle.id) == ParamControl::kFilterLaw) {
|
||||
drawToggle(g.rowToggle, "Band", "Notch",
|
||||
play.filter.settings.morphLaw ==
|
||||
instrument::engine::filter::MorphLaw::HighNotchLow,
|
||||
!play.filter.enabled);
|
||||
} else {
|
||||
drawToggle(g.rowToggle, "Retrig", "Legato",
|
||||
monoTrigger_ == MonoTrigger::Legato, !isMono);
|
||||
}
|
||||
}
|
||||
|
||||
// The knobs. PITCH ENV knobs draw Disabled (not hidden) while the envelope is off —
|
||||
// stable geometry.
|
||||
// The knobs. A dependent group's knobs draw Disabled (not hidden) — stable geometry.
|
||||
// The predicate is the input side's, so the drawn state and the inert grab agree.
|
||||
for (const DeckCellLayout& c : g.cells) {
|
||||
if (c.id < 0) continue; // reserved blank cell (the Trigger face's two spares)
|
||||
const bool disabled = (g.id == kGroupPitchEnv && !play.pitchEnv.enabled);
|
||||
const bool disabled = deckKnobDisabled(c.id);
|
||||
const bool dragging = (drag_ == DragKind::kDeckKnob && dragParamId_ == c.id);
|
||||
const bool hov = !disabled && isHovered(HoverKind::kControl, c.id);
|
||||
const InteractionState st =
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "public.sdk/source/common/pluginview.h"
|
||||
|
||||
#include "core/instrument/ui/deck_groups.h" // DeckParam / DeckGroupId / sampleDeckGroups
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect (shared sub-rect type)
|
||||
#include "core/instrument/ui/envelope_edit.h" // EnvClampBounds / NodeHit (envelope node hit-test/edit)
|
||||
#include "core/instrument/ui/envelope_overlay.h" // AmpEnvelope / EnvNode (envelope overlay draw seam)
|
||||
@@ -82,31 +83,9 @@ private:
|
||||
|
||||
// Controls on the setup surface. The int value is the opaque control id the pure
|
||||
// knob_deck hit-test returns; the shell maps it to the one parameter set or a
|
||||
// processor-side per-instance setter.
|
||||
enum class ParamControl {
|
||||
kPlayMode = 0, // Gate | Trigger toggle
|
||||
kPitchEngine, // Varispeed | Preserve toggle
|
||||
kAttack, // AHDSR attack (Gate) / —
|
||||
kHold, // AHDSR hold (Gate)
|
||||
kDecay, // AHDSR decay (Gate)
|
||||
kSustain, // AHDSR sustain (Gate)
|
||||
kRelease, // AHDSR release (Gate)
|
||||
kTrigLength, // Trigger %-length
|
||||
kTrigFadeIn, // Trigger fade-in
|
||||
kTrigFadeOut, // Trigger fade-out
|
||||
kPitchEnvEnable, // AD pitch envelope on|off
|
||||
kPitchEnvAttack, // AD pitch attack
|
||||
kPitchEnvDecay, // AD pitch decay
|
||||
kPitchEnvDepth, // AD pitch depth in +/- semitones
|
||||
kKeyTrack, // key-tracking 0..200% (lives on InstrumentParams, not PlaySeconds)
|
||||
// Deck-only controls: processor-side per-instance params — routed to the processor
|
||||
// setters, never through applyParamControl.
|
||||
kVoiceCount, // polyphony bound (1..32) — a stepped knob in the VOICE group
|
||||
kVoiceMode, // Poly | Mono caption toggle (VOICE group)
|
||||
kMonoTrigger, // Retrig | Legato row toggle (VOICE group; live only in Mono)
|
||||
kMasterGain, // post-mixer master gain knob (-inf..+24 dB taper, MASTER group)
|
||||
kCount
|
||||
};
|
||||
// processor-side per-instance setter. The id space and the deck's group composition are
|
||||
// the pure deck_groups module's — this alias keeps the shell's spelling.
|
||||
using ParamControl = instrument::ui::DeckParam;
|
||||
|
||||
// The waveform markers on the waveform band: start-point + the sustain loop's two ends,
|
||||
// in draw + hit order.
|
||||
@@ -188,6 +167,10 @@ private:
|
||||
bool mouseDownDeck(const FaceLayout& fl, int x, int y);
|
||||
void mouseDownBrowse(int w, int h, int x, int y);
|
||||
|
||||
// Whether deck knob `id` belongs to a group whose enable toggle is off. The ONE predicate
|
||||
// behind both the Disabled paint and the inert grab, so they cannot disagree.
|
||||
bool deckKnobDisabled(int id) const;
|
||||
|
||||
// Live drag resolution, split on the same axis; each handles only its own DragKind
|
||||
// values and is called from onMouseMove's router.
|
||||
void dragChrome(const FaceLayout& fl, int x, int y); // kRootMarker
|
||||
@@ -349,12 +332,6 @@ private:
|
||||
// Persisted preview velocity as a 0..1 slider value (MIDI 1..127 -> [0,1]).
|
||||
double previewVelocity01() const;
|
||||
|
||||
// The deck groups: AMP ENVELOPE (Gate A/H/D/S/R; Trigger Fade In/Length %/Fade Out + two
|
||||
// reserved blanks so a mode flip never reflows neighbours) / PITCH (Key Track) / PITCH
|
||||
// ENV (P.Attack/P.Decay/P.Depth) / VOICE (Voices knob + Poly|Mono + Retrig|Legato) /
|
||||
// MASTER (Gain knob).
|
||||
std::vector<DeckGroupDesc> deckGroupDescs(const PlaySeconds& play) 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
|
||||
// via the -2 sentinel) read the processor's live value.
|
||||
|
||||
Reference in New Issue
Block a user