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:
2026-07-30 15:26:14 -04:00
parent c9c708a338
commit 67215509cb
25 changed files with 1354 additions and 191 deletions
+16
View File
@@ -160,6 +160,21 @@ struct PitchEnvSeconds {
double peakSemitones = 0.0; // signed depth at the peak
};
// The stored mirror of the engine's FilterParams (play_params.h, which owns what each field
// MEANS). Only the envelope differs between the two: the control positions and depths are
// rate-free already, so this block is a seconds/frames split of one field, not of the whole
// struct. The env default is a flat unity, so `enabled` is the only thing standing between a
// loaded blob and the pre-filter sound.
struct FilterSeconds {
bool enabled = false;
engine::filter::FilterSettings settings;
double modAmount = 0.0;
double velAmount = 0.0;
double keyTrack = 0.0;
AdsrSeconds env{0.0, 0.0, 0.0, 1.0, 0.0};
VelocityCurve velocityCurve = VelocityCurve::linear();
};
// The stored play bundle: wall-clock times in SECONDS, source-timeline quantities in
// frames/fractions (TriggerParams). Instrument-owned, serialized, editor-facing — distinct
// from the engine-facing PlayParams (frames).
@@ -169,6 +184,7 @@ struct PlaySeconds {
TriggerParams trigger; // Trigger: %-length + fades (source frames)
PitchEngine pitchEngine = kDefaultPitchEngine; // product default: Preserve
PitchEnvSeconds pitchEnv; // AD pitch modulation (seconds), off by default
FilterSeconds filter; // per-voice filter, off by default
};
// Resolve a stored seconds bundle to the engine's frame-domain PlayParams against a live