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:
@@ -44,5 +44,12 @@ reasampler_test(envelope_edit LINK envelope_edit)
|
||||
reasampler_pure_library(knob_deck SOURCES knob_deck.cpp LINK PUBLIC editor_geometry)
|
||||
reasampler_test(knob_deck LINK knob_deck)
|
||||
|
||||
# The deck's group COMPOSITION, split from its layout: knob_deck stays engine-free, while this
|
||||
# names the controls and so reads PlayMode (velocity_curve comes along with play_params.h).
|
||||
reasampler_pure_library(deck_groups
|
||||
SOURCES deck_groups.cpp
|
||||
LINK PUBLIC knob_deck velocity_curve peaks)
|
||||
reasampler_test(deck_groups LINK deck_groups)
|
||||
|
||||
reasampler_pure_library(curve_popup SOURCES curve_popup.cpp LINK PUBLIC editor_geometry)
|
||||
reasampler_test(curve_popup LINK curve_popup)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// deck_groups.cpp — see deck_groups.h. Pure data; no host types.
|
||||
|
||||
#include "core/instrument/ui/deck_groups.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
int id(DeckParam p) { return static_cast<int>(p); }
|
||||
double clamp(double v, double lo, double hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
||||
} // namespace
|
||||
|
||||
double deckBipolarFromNorm(double norm) { return clamp(norm, 0.0, 1.0) * 2.0 - 1.0; }
|
||||
double deckNormFromBipolar(double value) { return clamp(value, -1.0, 1.0) * 0.5 + 0.5; }
|
||||
|
||||
std::vector<DeckGroupDesc> sampleDeckGroups(PlayMode playMode) {
|
||||
std::vector<DeckGroupDesc> out;
|
||||
{
|
||||
DeckGroupDesc pitch;
|
||||
pitch.id = kGroupPitch;
|
||||
pitch.captionWidth = 38;
|
||||
pitch.captionToggle = {id(DeckParam::kPitchEngine), 48};
|
||||
pitch.cellIds = {id(DeckParam::kKeyTrack)};
|
||||
out.push_back(std::move(pitch));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc penv;
|
||||
penv.id = kGroupPitchEnv;
|
||||
penv.captionWidth = 58;
|
||||
penv.captionToggle = {id(DeckParam::kPitchEnvEnable), 32};
|
||||
penv.cellIds = {id(DeckParam::kPitchEnvAttack),
|
||||
id(DeckParam::kPitchEnvDecay),
|
||||
id(DeckParam::kPitchEnvDepth)};
|
||||
out.push_back(std::move(penv));
|
||||
}
|
||||
{
|
||||
// Tone shaping left-to-right, then the three modulation depths that all target cutoff.
|
||||
DeckGroupDesc filter;
|
||||
filter.id = kGroupFilter;
|
||||
filter.captionWidth = 46;
|
||||
filter.captionToggle = {id(DeckParam::kFilterEnable), 32};
|
||||
filter.cellIds = {id(DeckParam::kFilterMorph),
|
||||
id(DeckParam::kFilterCutoff),
|
||||
id(DeckParam::kFilterQ),
|
||||
id(DeckParam::kFilterDrive),
|
||||
id(DeckParam::kFilterModAmt),
|
||||
id(DeckParam::kFilterVel),
|
||||
id(DeckParam::kFilterKeyTrack)};
|
||||
filter.rowToggle = {id(DeckParam::kFilterLaw), 44};
|
||||
out.push_back(std::move(filter));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc fenv;
|
||||
fenv.id = kGroupFilterEnv;
|
||||
fenv.captionWidth = 66;
|
||||
fenv.cellIds = {id(DeckParam::kFilterEnvAttack),
|
||||
id(DeckParam::kFilterEnvHold),
|
||||
id(DeckParam::kFilterEnvDecay),
|
||||
id(DeckParam::kFilterEnvSustain),
|
||||
id(DeckParam::kFilterEnvRelease)};
|
||||
out.push_back(std::move(fenv));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc amp;
|
||||
amp.id = kGroupAmpEnv;
|
||||
amp.captionWidth = 78;
|
||||
amp.captionToggle = {id(DeckParam::kPlayMode), 44};
|
||||
if (playMode == PlayMode::Gate) {
|
||||
amp.cellIds = {id(DeckParam::kAttack), id(DeckParam::kHold),
|
||||
id(DeckParam::kDecay), id(DeckParam::kSustain),
|
||||
id(DeckParam::kRelease)};
|
||||
} else {
|
||||
// Trigger, time-ordered left-to-right (Fade In / Length % / Fade Out — matches
|
||||
// the drawn envelope), plus the two reserved blanks that hold the Gate width.
|
||||
amp.cellIds = {id(DeckParam::kTrigFadeIn), id(DeckParam::kTrigLength),
|
||||
id(DeckParam::kTrigFadeOut), -1, -1};
|
||||
}
|
||||
out.push_back(std::move(amp));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc voice;
|
||||
voice.id = kGroupVoice;
|
||||
voice.captionWidth = 38;
|
||||
voice.captionToggle = {id(DeckParam::kVoiceMode), 40};
|
||||
voice.cellIds = {id(DeckParam::kVoiceCount)};
|
||||
voice.rowToggle = {id(DeckParam::kMonoTrigger), 44};
|
||||
out.push_back(std::move(voice));
|
||||
}
|
||||
{
|
||||
DeckGroupDesc master;
|
||||
master.id = kGroupMaster;
|
||||
master.captionWidth = 46;
|
||||
master.cellIds = {id(DeckParam::kMasterGain)};
|
||||
out.push_back(std::move(master));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 AMP group's Gate/Trigger face)
|
||||
#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, // 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)
|
||||
// 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,
|
||||
kFilterEnvHold,
|
||||
kFilterEnvDecay,
|
||||
kFilterEnvSustain,
|
||||
kFilterEnvRelease,
|
||||
// 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 group's face; its width is
|
||||
// mode-independent (Trigger leaves two blank cells) so a mode flip never reflows the
|
||||
// neighbouring groups.
|
||||
std::vector<DeckGroupDesc> sampleDeckGroups(PlayMode playMode);
|
||||
|
||||
// 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
|
||||
Reference in New Issue
Block a user