S12: store wall-clock ADSR/pitch-env as seconds, resolve to frames at live rate
Kill kTier0Nominal*, adsrNeedsRateResolve, tier0Adsr, gateAdsr. Zones payload v5 carries seconds; v3 legacy reads convert at the frozen authoring rate; v4 (branch-only) dropped. Editor sliders now seconds. Engine takes frames resolved at keymap build.
This commit is contained in:
+22
-25
@@ -341,18 +341,15 @@ public:
|
||||
// Starts this voice on `note` at `velocity`, playing `sample` (a stable reference
|
||||
// the caller must keep alive for the voice's lifetime — the Keymap owns it), repitched
|
||||
// from `rootNote`. All five AHDSR fields (A/H/D/S/R) are read directly from
|
||||
// sample.play.adsr — the per-zone values stamped by buildTier0Keymap / buildZonedKeymap.
|
||||
// `gateAdsr` is the VoiceEngine's instrument-wide ADSR parameter, accepted for interface
|
||||
// compatibility but NOT used by start() (vestigial since the S12 review fix moved A/D/S/R
|
||||
// onto the per-zone SampleData). The S15 play MODE + Trigger params and the S16 pitch
|
||||
// ENGINE + pitch envelope are read from `sample.play`. The Preserve shifters MUST already
|
||||
// be pre-sized (presizePreserveShifters, off-thread) — start() only reset()s + warm()s
|
||||
// them (RT-safe, no allocation) since it runs on the audio thread inside process(). The warm
|
||||
// silence pass settles the OLA taps before the first output frame (no cold-start click).
|
||||
// Byte-identical to the pre-S15 engine when sample.play is default (Gate + Varispeed + no
|
||||
// pitch env).
|
||||
void start(int note, int velocity, const SampleData& sample, int rootNote,
|
||||
const AdsrParams& gateAdsr);
|
||||
// sample.play.adsr — the per-zone values (in FRAMES) resolved from the stored seconds by
|
||||
// buildTier0Keymap / buildZonedKeymap against the live sample rate. The S15 play MODE +
|
||||
// Trigger params and the S16 pitch ENGINE + pitch envelope are read from `sample.play`.
|
||||
// The Preserve shifters MUST already be pre-sized (presizePreserveShifters, off-thread) —
|
||||
// start() only reset()s + warm()s them (RT-safe, no allocation) since it runs on the audio
|
||||
// thread inside process(). The warm silence pass settles the OLA taps before the first
|
||||
// output frame (no cold-start click). Byte-identical to the pre-S15 engine when sample.play
|
||||
// is default (Gate + Varispeed + no pitch env).
|
||||
void start(int note, int velocity, const SampleData& sample, int rootNote);
|
||||
|
||||
// Gate off — begins the amplitude release. In GATE mode this enters the AHDSR release; in
|
||||
// TRIGGER mode it is a NO-OP (Trigger ignores note-off and plays through to its play length).
|
||||
@@ -456,18 +453,19 @@ class VoiceEngine {
|
||||
public:
|
||||
// Builds an engine with `maxVoices` voices (the polyphony bound) playing from
|
||||
// `keymap`. The keymap must outlive the engine (the engine holds a reference — it
|
||||
// reads zones and sample data through it, never copies PCM). `adsr` is the instrument-wide
|
||||
// Gate AHDSR timing (attack/decay/sustain/release); each zone's HOLD stage + play mode +
|
||||
// pitch engine ride on its SampleData::play. `preserveVoiceCap` (S16) bounds how many
|
||||
// Preserve-engine voices may sound at once (the shifter is materially heavier than
|
||||
// Varispeed) — a Preserve note-on beyond the cap is dropped rather than glitching; 0 means
|
||||
// "no separate Preserve cap" (bounded only by maxVoices). `preserveWindowFrames` is the OLA
|
||||
// window (in OUTPUT frames) every voice's Preserve pitch shifters are PRE-SIZED to at
|
||||
// construction (OFF the audio thread), so note-on (which runs in process()) never allocates;
|
||||
// 0 leaves them pass-through (a Varispeed-only instrument pays no ring cost). The processor
|
||||
// derives it from the host sample rate (kPreserveWindowMs). Defaulted so existing callers
|
||||
// (and the pure-core tests) are unaffected.
|
||||
VoiceEngine(std::size_t maxVoices, const Keymap& keymap, const AdsrParams& adsr,
|
||||
// reads zones and sample data through it, never copies PCM). Every AHDSR field (A/H/D/S/R)
|
||||
// + play mode + pitch engine rides on each zone's SampleData::play (in FRAMES, resolved
|
||||
// from the stored seconds at keymap build); the engine holds no instrument-wide ADSR.
|
||||
// `preserveVoiceCap` (S16) bounds how many Preserve-engine voices may sound at once (the
|
||||
// shifter is materially heavier than Varispeed) — a Preserve note-on beyond the cap is
|
||||
// dropped rather than glitching; 0 means "no separate Preserve cap" (bounded only by
|
||||
// maxVoices). `preserveWindowFrames` is the OLA window (in OUTPUT frames) every voice's
|
||||
// Preserve pitch shifters are PRE-SIZED to at construction (OFF the audio thread), so
|
||||
// note-on (which runs in process()) never allocates; 0 leaves them pass-through (a
|
||||
// Varispeed-only instrument pays no ring cost). The processor derives it from the host
|
||||
// sample rate (kPreserveWindowMs). Defaulted so existing callers (and the pure-core tests)
|
||||
// are unaffected.
|
||||
VoiceEngine(std::size_t maxVoices, const Keymap& keymap,
|
||||
std::size_t preserveVoiceCap = 0, std::int64_t preserveWindowFrames = 0);
|
||||
|
||||
// MIDI note-on. Resolves the note+velocity to a zone; if none matches (out of
|
||||
@@ -524,7 +522,6 @@ private:
|
||||
|
||||
std::vector<Voice> voices_;
|
||||
const Keymap& keymap_;
|
||||
AdsrParams adsr_;
|
||||
std::size_t preserveVoiceCap_ = 0; // S16: max simultaneous Preserve voices (0 = no separate cap)
|
||||
std::uint64_t nextStartOrder_ = 1; // monotonic; 0 reserved for "never started"
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user