162 lines
8.1 KiB
C++
162 lines
8.1 KiB
C++
// deck_groups.h — WHICH groups the Sample face's knob deck carries and in what order, plus
|
|
// the control-id space they are built from. Pure data: knob_deck lays out whatever descriptors
|
|
// it is handed, and this module decides what those descriptors are, so the deck's signal-flow
|
|
// ordering is provable without a host.
|
|
|
|
#pragma once
|
|
|
|
#include <vector>
|
|
|
|
#include "core/instrument/engine/play_params.h" // PlayMode (the mode-dependent group faces)
|
|
#include "core/instrument/ui/knob_deck.h" // DeckGroupDesc
|
|
|
|
namespace reasampler::instrument::ui {
|
|
|
|
// Deck control ids. Opaque to knob_deck, resolved by the shell's hit-test and value binding.
|
|
// Runtime-only — nothing persists them, so the ordering here is free to change.
|
|
enum class DeckParam {
|
|
kPlayMode = 0, // Gate | Trigger toggle
|
|
kPitchEngine, // Varispeed | Preserve toggle
|
|
kAttack, // amp AHDSR attack (Gate)
|
|
kHold, // amp AHDSR hold (Gate)
|
|
kDecay, // amp AHDSR decay (Gate)
|
|
kSustain, // amp AHDSR sustain (Gate)
|
|
kRelease, // amp AHDSR release (Gate)
|
|
kTrigLength, // Trigger play span, % of the post-start length
|
|
kTrigAttack, // amp AHD attack (Trigger)
|
|
kTrigHold, // amp AHD hold, % of the span left after attack + decay
|
|
kTrigDecay, // amp AHD decay (Trigger)
|
|
kPitchEnvEnable, // AHD pitch envelope on|off
|
|
kPitchEnvAttack,
|
|
kPitchEnvHold, // % of the span left after attack + decay
|
|
kPitchEnvDecay,
|
|
kPitchEnvDepth, // AHD pitch depth in +/- semitones
|
|
kKeyTrack, // key-tracking 0..200% (lives on InstrumentParams, not PlaySeconds)
|
|
// Filter. The four control positions map through filter_params' own laws; the three
|
|
// depths are bipolar and centred at zero.
|
|
kFilterEnable, // filter on|off caption toggle
|
|
kFilterMorph, // morph position: high-pass .. low-pass
|
|
kFilterCutoff, // cutoff, log across the audio band
|
|
kFilterQ, // resonance
|
|
kFilterDrive, // in-loop drive depth
|
|
kFilterModAmt, // filter envelope -> cutoff, +/-100%
|
|
kFilterVel, // velocity -> cutoff, +/-100%
|
|
kFilterKeyTrack, // note -> cutoff, 0..200%
|
|
kFilterLaw, // morph law row toggle: HP-BP-LP | HP-notch-LP
|
|
kFilterEnvAttack, // filter AHDSR (Gate)
|
|
kFilterEnvHold,
|
|
kFilterEnvDecay,
|
|
kFilterEnvSustain,
|
|
kFilterEnvRelease,
|
|
kFilterTrigAttack, // filter AHD (Trigger)
|
|
kFilterTrigHold,
|
|
kFilterTrigDecay,
|
|
// Curve exponents. These never get a cell of their own — each is the INNER DIAL of the
|
|
// stage knob it shapes (see curveParamFor), which is why only sloped stages have one.
|
|
kAttackCurve,
|
|
kDecayCurve,
|
|
kReleaseCurve,
|
|
kTrigAttackCurve,
|
|
kTrigDecayCurve,
|
|
kPitchEnvAttackCurve,
|
|
kPitchEnvDecayCurve,
|
|
kFilterEnvAttackCurve,
|
|
kFilterEnvDecayCurve,
|
|
kFilterEnvReleaseCurve,
|
|
kFilterTrigAttackCurve,
|
|
kFilterTrigDecayCurve,
|
|
// Overlay selection radios — transient view state, not parameters.
|
|
kAmpEnvSelect,
|
|
kPitchEnvSelect,
|
|
kFilterEnvSelect,
|
|
// Deck-only controls: processor-side per-instance params — routed to the processor
|
|
// setters, never through the parameter set.
|
|
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
|
|
};
|
|
|
|
// Deck group ids. Unscoped so the shell's caption switch reads against the plain `id` int
|
|
// knob_deck carries.
|
|
enum DeckGroupId {
|
|
kGroupPitch = 0,
|
|
kGroupPitchEnv,
|
|
kGroupFilter,
|
|
kGroupFilterEnv,
|
|
kGroupAmpEnv,
|
|
kGroupVoice,
|
|
kGroupMaster,
|
|
};
|
|
|
|
// The deck's groups, left to right, in SIGNAL-FLOW order: pitch -> filter -> amp, then the
|
|
// two instance-wide groups. `playMode` picks the AMP and FILTER ENV groups' faces — AHDSR in
|
|
// Gate, AHD in Trigger — via knob_deck's blank-cell reservation (knob_deck.h) so a mode flip
|
|
// never reflows the neighbouring groups.
|
|
std::vector<DeckGroupDesc> sampleDeckGroups(PlayMode playMode);
|
|
|
|
// The curve-exponent control a stage knob's INNER DIAL edits, or kCount when the knob shapes
|
|
// no curve. THE one place the "every stage except Hold and Sustain is sloped" rule is written
|
|
// down: a knob with no entry here draws no inner dial and its inner region resolves as an
|
|
// ordinary knob grab.
|
|
DeckParam curveParamFor(DeckParam knob);
|
|
|
|
// Whether control `id` is delivered LIVE — straight to the voices that are already sounding —
|
|
// rather than through an instrument reload. The line is drawn at continuously-valued playback
|
|
// controls, so this is a routing decision at the editor's commit site rather than a property
|
|
// of any one knob; moving a control across the line is a change here and nowhere else.
|
|
//
|
|
// THE home for why each excluded control is excluded. Three continuous controls are outside
|
|
// the live set, plus every discrete toggle and the overlay radios:
|
|
// - the discrete toggles (play mode, pitch engine, filter enable/law, pitch-envelope enable)
|
|
// name a different sound rather than a different setting of one;
|
|
// - the three capture-anchored overrides (root, loop span, start frame) name positions in
|
|
// the decoded PCM;
|
|
// - kKeyTrack and kFilterVel feed values a voice latches at note-on by design (the pitch
|
|
// ratio and the velocity-curve result), so live delivery would retune or re-gain a note
|
|
// already struck;
|
|
// - kTrigLength resolves playEnd_, a fact about the note, not a setting of it;
|
|
// - the overlay radios select what the editor DRAWS and reach no parameter at all.
|
|
// Both amp shapes are live: the Trigger fade pair that used to reload folded into the AHD and
|
|
// inherited its routing, so a Trigger-mode instance now tracks its amplitude knobs too.
|
|
bool isLiveDeckParam(DeckParam id);
|
|
|
|
// The editor drag kinds that can commit live, in this pure module's own vocabulary (the
|
|
// shell's DragKind maps onto it) so the WHOLE routing decision — not just the predicate — is
|
|
// testable without a host.
|
|
enum class LiveDragKind { kOther, kDeckKnob, kEnvNode };
|
|
|
|
// Whether a drag of `kind` commits live. A deck knob is live per isLiveDeckParam (negative ids
|
|
// are the shell's processor-side sentinels and out-of-range ids are not controls, so neither
|
|
// reaches the enum); an envelope-node drag is live in either mode, since every stage value it
|
|
// can reach — AHDSR or AHD, on any of the three envelopes — is itself live.
|
|
bool liveCommitFor(LiveDragKind kind, int paramId);
|
|
|
|
// Which envelope the waveform overlay draws and edits. Exclusive across the three envelope
|
|
// decks, and kNone is a valid resting state — the editor opens there. Transient view state:
|
|
// never persisted, never a parameter.
|
|
enum class OverlayEnv { kNone, kAmp, kPitch, kFilter };
|
|
|
|
// The envelope a deck's overlay-select radio picks; kNone for any other control id.
|
|
OverlayEnv overlayEnvForRadio(int radioId);
|
|
|
|
// The selection a click on `radioId` produces from `current`. Two rules, provable here rather
|
|
// than in the shell: picking another deck's radio switches to it (exclusivity), and clicking
|
|
// the ACTIVE one clears back to kNone — "no envelope shown" is a state the user can get back
|
|
// to, not an error. A non-radio id leaves the selection alone.
|
|
OverlayEnv nextOverlaySelection(OverlayEnv current, int radioId);
|
|
|
|
// Whether the overlay for `env` is INERT: its deck group's enable toggle is off, so its knobs
|
|
// are drawn-but-dead and a node drag on the same params must be too — otherwise a drag reaches
|
|
// a param a knob couldn't (envelope_edit.h). Amp has no enable toggle and is never inert.
|
|
bool overlayEnvInert(OverlayEnv env, bool pitchEnvEnabled, bool filterEnabled);
|
|
|
|
// The deck's BIPOLAR knob law: 0.5 of the knob's travel is zero depth, the ends are -1 and
|
|
// +1. Exact inverses, and exact at the centre detent (0.5 -> 0 -> 0.5), so a knob parked at
|
|
// centre can never persist a hair of modulation. Out-of-range norm clamps to the endpoints.
|
|
double deckBipolarFromNorm(double norm);
|
|
double deckNormFromBipolar(double value);
|
|
|
|
} // namespace reasampler::instrument::ui
|