# DeepPluck Architecture

## V0.13.0 release-candidate boundary

V0.13.0 is a feature-frozen hardening and release-preparation layer over the
committed V0.12.75 product. It changes no DSP, parameter, state, preset,
randomization, Factory sound, MIDI behavior or GUI geometry. RC work is limited
to reproduced bug fixes, compatibility/stability validation, bounded
performance-regression checks, central version propagation, project-local
artifact/package verification and synchronized documentation.

The reconciled V0.13.0 Start HEAD is
fedb8ac7665cc43e956fd15900b1199596699dbf. The active Factory contract is the
user-approved 116-preset library in eight categories; the superseded four
factory:0..3 sounds remain internal compatibility snapshots only. The external
Factory final-audio gate is 696 renders per configuration (116 presets x three
sample rates x dry/wet), distinct from the 669+42 Tone corpus, representative
39-case CPU suite and registered CTests.

Release-candidate packaging is generated only below ignored
Artifacts/V0.13.0 by Tests/PrepareReleaseCandidatePackage.ps1. The package
allowlist contains the complete Release VST3 bundle and Release Standalone EXE.
Every packaged file is byte-compared with the validated Release build and listed
by relative path, size and SHA-256 in a sibling manifest. No installer, system
copy, signing, upload or push is part of V0.13.0.

The historical V0.11.5 D96/64 hard-deadline finding remains OPEN/deferred.
A current no-regression result does not convert it into a timing PASS. A final
technical resolution was not claimed. On 2026-09-05 the user explicitly accepted
the unchanged open finding as a known V1.0 release risk. This satisfies the risk
decision requirement without changing the technical status. Later on 2026-09-05
the user reported that the current synth runs without recognizable errors and
declared V0.13.0 complete. This is the practical milestone-level manual
acceptance; individual Cubase/device/DPI/listening checklist results were not
separately itemized. V0.13.0 is therefore READY FOR V1.0 RELEASE PREPARATION.

## Preserved V0.12.75 Standalone keyboard boundary; unchanged V0.12.5 UI and DSP

DeepPluck is a no-input, stereo-output JUCE 9 instrument with MIDI input and no MIDI output. The current version is 0.13.0. Its approved content baseline is V0.12.0 and its preserved immediate DSP/binary reference is the practically approved V0.11.5 working copy; earlier immutable V0.11.25 references also remain available. The V0.11.0 parameter freeze remains authoritative. Plugin manufacturer code `Tntx`, plugin code `DpV1`, bundle identifier `de.tonetrax.deeppluck`, formats, buses, MIDI contract, and all sixty-two parameter IDs/order/version hints remain unchanged. Randomization locks, Undo, Amount, browser state, preset metadata, GUI preferences, tab selection, the three GUI position proxies, visible keyboard octave and pressed keyboard notes remain runtime/UI state rather than extra host parameters.

`DeepPluckAudioProcessorEditor` branches only on the real JUCE `wrapperType`.
VST3, Undefined and every non-Standalone wrapper retain the exact 1536 x 900
editor, instantiate no keyboard panel, reserve no strip and never query the
global Standalone holder. A Standalone editor is 1536 x 980. All existing direct
children retain their VST3 bounds calculated inside an explicit (0, 0, 1536,
900) main rectangle; the new `DeepPluckStandaloneKeyboardPanel` occupies exactly
(0, 900, 1536, 80). The Welcome overlay is added above it and covers the full
editor, so hidden keys cannot receive clicks during first-run guidance. Opening
Welcome from SETTINGS first releases every keyboard-state note while the
collector is still connected, preventing a held gesture from being hidden.

The panel owns one `MidiKeyboardState`, one horizontal
`MidiKeyboardComponent`, two octave buttons and its listener lifetime. It uses
MIDI channel 1, range 0..127, 38-pixel keys and a non-persisted MIDI-36 (C2)
lowest visible key. Project-owned octave controls move by 12 and clamp 0..60;
JUCE's internal scroll controls have zero width and mouse-wheel scrolling is
ignored. The piano clears computer mappings, declines keyboard focus and does
not grab focus on click. This consciously defers QWERTZ without disturbing
parameter text entry or host shortcuts.

Only after confirming `wrapperType_Standalone`, a small GUI-side connector reads
`StandalonePluginHolder::getInstance()`, verifies that the holder owns the exact
processor attached to this editor and registers the holder player's existing
`MidiMessageCollector` as the keyboard-state listener. Tests inject a fake
listener through the same constructor boundary and use JUCE's real collector
without an audio device. External and virtual input therefore remain ordinary
events in one timestamped stream before the unchanged processor callback; a
same-channel/same-note second Note-On retains existing retrigger semantics and a
matching Note-Off ends that note. No source reference count or second MIDI merge
exists.

Cleanup first disables the panel, calls `MidiKeyboardState::allNotesOff(1)`
while the listener is still connected, then removes the listener. The operation
is idempotent and releases only virtually held notes. The existing
`releaseResources()` hard voice/FX reset remains the final audio-device/shutdown
safety layer. No keyboard, holder or GUI object is read by the audio thread; the
panel adds no polling timer, DSP, state, preset, preference or parameter work.

V0.12.5 is presentation-only. `WelcomeOverlayComponent` remains the single
first-run overlay and owns structured labels for product, subtitle, three short
orientation lines, Hover Help hint, exact credits, a `Start Playing` action, and
a `Don't show this again` toggle. `SettingsPageComponent` retains two sibling
panels: PREFERENCES owns only Hover Help, Welcome Screen, and Show Welcome Now;
ABOUT owns separately testable product/version/format/platform/JUCE/description/
credit labels plus one `HyperlinkButton` targeting exactly
`https://www.tonetrax-music.de`. The visible product is
`Just A Lil' PluckSynth`; host-visible identity remains `DeepPluck`.

`DeepPluckGuiPreferences` remains the sole process-shared preference owner. Its
three booleans are serialized atomically to
`User Application Data/ToneTrax/DeepPluck/GUI.properties`, never to APVTS or a
preset. A completed first run sets `welcomeSeen`; disabling the option sets
`welcomeEnabled` false; enabling or Show Welcome Now re-arms the shared state.
Show Welcome Now changes only the requesting overlay. Existing visible overlays
are not controlled by another editor's lifecycle. Missing, corrupt, and
wrong-root XML all produce the safe enabled/unseen first-run default.

The confirmed Windows x64 reference environment is Windows 11, Visual Studio 2026, MSVC 19.51, Windows SDK 10.0.26100.0, CMake 4.4.3, JUCE 9.0.1, and C++20. Debug and Release VST3/Standalone builds plus both CTest configurations are the automated build contract.

The user has accepted V0.11.25 with “0.11.25 test bestanden”. V0.11.5 preserves that sound and the frozen public contract. The representative baseline and real sampled hot paths are complete. The first filter-cache experiment was rejected (3.25% gain, below 5%) and completely removed. A separate settled-waveform-mix optimization has passed both registered efficiency thresholds and strict tested audio equivalence. Eleven measured block-deadline misses still fail the independent hard realtime gate; the later clean final matrix does not discard earlier violations. All 33 Debug/Release CTests and artifact/process-smoke checks pass. On 2026-09-03 the user practically approved the built V0.11.5 and requested roadmap continuation. This approval is recorded separately from the unresolved technical timing gate. Further diagnosis is deferred, earlier evidence is retained, and no automatic milestone commit is implied. V0.12.0 adds developer content tooling and approved-Factory infrastructure under the Sound Manifest; it does not reopen the deferred timing diagnosis or change DSP.

`DeepPluckOscillator` advances all five waveform-mix smoothers during an active
crossfade, including calls at zero frequency. When its conservative existing
ramp counter is zero, all five values have already reached exact one-hot targets
and repeated no-op smoother reads can be omitted. The counter uses round while
JUCE's smoother length uses floor, so it cannot finish prematurely. Pulse-width
smoothing, the persistent triangle recurrence, BLEP/sine expressions, phase,
crossfade arithmetic and accumulation order are unchanged. External frozen-binary
null tests and paired timing documented the gain; practical acceptance and the deferred deadline gate remain distinct.

## V0.12.0 preset content architecture

The Sound Manifest remains normative. `DeepPluckPresetCodec` extracts the
existing schema-1 reader/writer; missing known values still inherit defaults,
unknown fields remain tolerated and known nonnumeric values still fail.
Pretty developer output is UTF-8/LF for stable file hashes. The compact User
writer retains its original once-canonicalized raw-value semantics.

`DeepPluckPresetGenerator` owns a minimal parameter-only APVTS context and the
eight Rev1 category profiles. Explicit 64-bit seeds use fixed category hashing,
SplitMix64 mixing and mt19937_64/53-bit uniform draws, without constructing the
plugin's entropy-seeded Random engine. Output/Tune are fixed -3 dB/440 Hz.
All generation, schema validation, hashing and file access are offline only.
Path/identity/seed/hash checks reject changed, incomplete or overwritten runs.

`Presets/Factory/catalog.json` explicitly lists final content; it is not a scan
of Documents or a glob of candidate directories. On 2026-09-04 the user amended
the original six-per-category target by selecting every latest `Keep` decision.
Build and runtime validation therefore require exactly 116 files with fixed
category counts 14/13/12/15/17/10/19/16, globally unique identities and names,
safe paths, numeric raw values and matching user-approval hashes. JUCE BinaryData
embeds only those explicit source files and the manifest. The Processor decodes
once during construction, never during audio processing.

New content IDs use `factory12:<stable-slug>`; the original `factory:0..3` continue
to load the original four snapshots. The active browser and relative navigation
show 116, not 116+4. Matching raw sound fingerprints restore
Factory display identity without requiring a source file in host state. Preferred
selection, per-instance Random locks/Amount and one-level Undo retain their load
boundary rules. Synthetic library injection is a friend only in the developer
test translation unit; no end-user override or audio callback branch is added.

`DeepPluckPresetAudition` reuses the real Processor/Editor with an injected,
project-local preset root and GUI preference file. The original-size editor is
scrollable in a small developer wrapper, with the standard JUCE audio/MIDI
selection and keyboard. Candidates are read-only Packs, fine tunes are isolated
User copies; append-only review/origin sidecars never enter a sound file or host
state. A review `Keep` was initially shortlist evidence; the user's later explicit
instruction to promote every `Keep` is the approval decision for those exact
hashes. Promotion was first validated byte-identically in a new artifact staging
directory and then copied into the explicit production Factory source tree.

The four infrastructure CTests plus the approved real-content test bring the
total to 38 per configuration. The real test verifies all 116 source files,
catalog entries, approval hashes, embedded values and compatibility behavior.
Preserved 0.11.5
PresetAudit binaries permit identical new sound snapshots on both DSP versions.
See `Tests/FactoryPresets.md` for the separate content and comparison gates.

## Preserved V0.11.5 CPU evidence boundary

`DeepPluckBenchmark` links the actual plugin shared code and invokes its complete callback, including snapshots, transport, MIDI, voice sum, FX, output and metering. It opens no editor/device and disables preset file IO. Every instance receives an explicit 62-value fixture before prepare and an identical empty-block preroll. All buffers/events/timing arrays are prepared outside the measured callback. Diagnostic state reads, statistics, hashing and files are confined to the console harness.

The non-exported `benchmark::Access` friend declarations in Processor, Voice and EffectsRack add no members or executable production work. Separate audio diagnostics record active/releasing/residual voices, latched unison, effect lifecycle and oscillator/LFO phase trajectories; timing mode does not call them. Static time windows and matched diagnostic phases are distinct, and mixed-boundary blocks always remain in whole-run totals.

The preserved legacy inventory contains 6,912 M/W/X cases, 588 A–E references and 420 transitions. The user amendment of 2026-09-03 replaces Cartesian CPU acceptance with 39 fixed representative cases: separate voice/unison/buffer/rate/FX scaling, reference sounds and selected stresses. Two-second initial windows and a separate half-second warmup have distinct IDs/provenance; existing long audio evidence is retained. Only Typical, Dense and the measured selected Worst are profiled. Bit-identical same-configuration audio remains the acceptance policy, with full-sample L/R/M/S and 1,024-frame window checks, not only output peaks. Profiling is a separate symbolised Release tree (`DEEPPLUCK_PROFILE_SYMBOLS`); its offline QPC phase markers are never compiled into the normal timing executable. No reduced DSP quality, changed seeds, idle LFO freeze, tail trimming or benchmark-only production fast path is permitted. See `Tests/CPUProfilingBenchmark.md` for the current manifest, statistics, provenance and gate protocol.

## Ownership and responsibilities

`DeepPluckAudioProcessor` owns, in lifetime order, the APVTS, bound parameter cache, current trivially-copyable block snapshot, `DeepPluckSynthesiser`, global `DeepPluckEffectsRack`, master-output smoother, control-thread `DeepPluckRandomization`, and `DeepPluckPresetManager`. Each atomic parameter is loaded once per block. The processor queries the playhead once per block, accepts only finite 20..400 BPM values, and passes the same cached tempo to the synthesiser and Delay. The final order is complete voice sum -> FX rack -> 20 ms Output Gain. Random/preset entry points are message/control-thread functions and are never reachable from voice/sample code.

`DeepPluckParameterState` is the shared control-side representation of the exact sixty-two APVTS values. It provides the authoritative append-only ID array, capture/default capture, parameter-aware canonicalization, normalized-tolerance matching, and host-notified application. Factory matching, Random Undo, User presets, and tests use this one representation instead of maintaining parallel parameter lists.

`DeepPluckRandomization` owns one per-instance `mt19937_64`, a runtime Amount, ten runtime locks, and one full sixty-value Undo snapshot. Production seeds combine system entropy and monotonic timing outside the audio path; tests can inject an exact 64-bit seed. The module matrix assigns every randomizable parameter exactly once and excludes Output Gain and Master Tune. The appended Effects Rack module owns all eighteen FX parameters. Continuous results use normalized-space interpolation; bool/choice changes use Amount as their Bernoulli probability. A no-op consumes no RNG and creates no Undo or display side effect.

`DeepPluckPresetManager` owns the configured preset root, current immutable catalog snapshot, default-value baseline, and one low-priority scanner thread. The real root is Documents/Tonetrax/Just a Lil' Pluck Synth/Presets; tests inject an ignored build-only root. Scan requests are coalesced, results replace the catalog under a short control-side lock, and the processor/audio callback never acquires it. All filesystem work, JSON parsing, safe-name handling, atomic replacement, recycle-bin deletion, and Explorer reveal remain outside audio. Scans reject symlinks/path escape, bound recursion to eight levels, process at most 2,000 files and 1 MiB per file, and isolate individual errors.

`DeepPluckSynthesiser` is a fixed-size JUCE derivative that provides sample-accurate MIDI dispatch, deterministic lock-free voice selection, and the single global `DeepPluckLfo`. It advances one LFO value per absolute audio sample into a fixed 64-sample stack-owned member buffer and gives the identical value sequence to every voice rendered for that interval. Exactly 16 `DeepPluckVoice` objects and one immutable `DeepPluckSound` are created outside the audio callback.

`DeepPluckVoice` owns one `DeepPluckUnison`, one `DeepPluckTransientNoise`, two channel-specific `DeepPluckFilter` instances, independent Amp and Filter instances of `DeepPluckEnvelope`, three 20 ms Amount smoothers, the existing parameter smoothers, velocity gain, and a fixed two-millisecond stereo residual fade for stolen notes. It reads the current block snapshot and shared modulation buffer without parameter IDs or atomics in its sample loop.

`DeepPluckUnison` owns fixed arrays of four primary and four secondary `DeepPluckOscillator` instances plus four detune-ratio and eight pan-gain smoothers. It latches a count from one to four at Note-On, initializes deterministic phase offsets, and produces one left/right tonal pair. No allocation, random phase, host lookup, collection growth, or dynamic voice creation occurs in this module.

`DeepPluckTransientNoise` owns a short sine-wavelet transient, two one-shot envelopes, a per-voice xorshift32 generator, DC blocker, two-pole noise low-pass, source calibration, and independent level/tone/bypass smoothers. It has no allocation, external assets, system entropy, sustained mode, or user-visible algorithm choice.

`DeepPluckLfo` owns only normalized phase, a 20 ms multiplicative rate smoother, waveform/restart selection, and a two-millisecond one-pole output dezipper. It has no APVTS, GUI, voice, dynamic storage, host query, or system-time dependency. Sine, Triangle, Saw, and Square are evaluated once globally per audio sample.

Each `DeepPluckOscillator` owns normalized phase, frequency, sample rate, phase increment, one fixed five-element waveform-weight bank, one Pulse Width smoother, and one Triangle integrator state. `setFrequency()`, waveform edits, and Pulse Width edits never reset phase. Invalid frequency produces silence. Note-on resets the primary to phase 0 and Oscillator 2 to phase 0.25, initializes one-hot waveform weights and Pulse Width without a partial old ramp, and reconstructs the Triangle state deterministically.

Each `DeepPluckFilter` owns a nonlinear four-pole ladder state, sample-rate-aware cutoff bounds, resonance feedback, a smoothed pre-filter drive shape, and bounded smoothed resonance-output compensation. The two channel instances receive identical cutoff, resonance, drive, and envelope control values but retain independent audio history.

`DeepPluckEnvelope` is a project-owned, JUCE-ADSR-independent state machine. It owns all stage, recurrence, live-update, and lifecycle behavior. Two independent instances per voice provide identical musical contracts without duplicating the state machine.

`DeepPluckEffectsRack` owns one `DeepPluckChorus`, `DeepPluckDelay`, and `DeepPluckReverb`, plus the preallocated routing scratch and a 15 ms equal-power Dry Bridge. The six orders are a fixed constexpr permutation table. Chorus owns two mono JUCE chorus instances and a wet scratch buffer; Delay owns two interpolated fixed-capacity ring buffers and feedback tone states; Reverb owns JUCE's stereo algorithm plus two fixed-capacity pre-delay buffers. Delay and Reverb track retained-tail energy, block new feed during bypass, and reset only after the -100 dBFS/multi-block quiet contract. A load-boundary atomic request resets all FX state at the next block without allocation.

`DeepPluckPresets` owns four immutable factory definitions plus message-thread apply/match helpers. Preset names are UI information; all sixty-two APVTS values are the persisted sound truth. The original forty-four values remain unchanged, and every Factory stores the neutral/default FX extension. A preferred User/Pack entry is represented by its stable catalog ID plus a complete value fingerprint; any mismatch displays Custom, while an explicitly saved User duplicate retains its User name.

`DeepPluckAudioProcessorEditor` owns the fixed 1536 x 900 page host and orchestrates a permanent `HeaderComponent`, compacted `OscillatorRowComponent`, `FinalPageNavigationComponent`, footer, Welcome overlay, and preconstructed MAIN, MODULATION, FX, and SETTINGS content. It has no DSP responsibility. Page selection changes only component visibility/enabled state, starts at MAIN for every new editor, and is not persisted or exposed to the host. The binding composition, colour hierarchy, mood, and visual language follow the tracked target mockup at `Docs/GUI/DeepPluck_V0.10_GUI_Target_Mockup.png`; the reduced vertical geometry removes no control or workflow.

`HeaderComponent` owns the grouped Factory/User/Pack/Imported selector, Previous/Next, Master Random, Undo, Amount, Save, Save As, Delete, Refresh, Open Folder, Master Tune and Output attachments, symbol-free two-label wordmark, status, centrally generated version label, and compact stereo output meter. The small bold-italic `JUST A LIL'` kicker and larger bold-italic `PluckSynth` title share the Amber identity colour but retain independent typography. The action row is aligned under the selector; Master Random and Undo form one vertical column beside the rotary Amount control, both provide preference-gated Hover Help, and the meter occupies the full header height. Each `ModulePanelComponent` owns one runtime Random/Lock pair for its assigned module. Oscillator Row owns 9 attachments, MAIN owns 25, MODULATION owns 8, and FX owns 17 normal attachments plus one `FxOrderController`. That controller remains the only binding to `fxOrder` and keeps three GUI-only position choices in a valid permutation. Combined with the header's two attachments, the editor owns exactly 62 host bindings. All pages remain constructed while hidden, so switching creates no attachments or workers. SETTINGS/About uses the human-facing `Tonetrax` spelling while the established JUCE company metadata remains `ToneTrax`.

The permanent Oscillator Row owns both waveform choices, Pulse Width controls, Oscillator 2 enable/level/tuning, static parameter-driven waveform displays, and the two oscillator Random/Lock pairs. MAIN contains Filter, Amp Envelope, Filter Envelope, Unison, Transient, and Noise with their existing controls and module pairs; parameter-driven ADSR displays own no DSP state. The Filter layout reserves the left side for On and the sole 112 x 112 Cutoff rotary, while Resonance/Drive and Key Track/Env Amount occupy an exact two-by-two grid. MODULATION contains only the established eight controls and one module pair; its LFO and Amounts panels each use the same 145-pixel row height as the three FX modules and deliberately leave the lower page area empty. FX begins with a 40-pixel rack action header containing the single rack Random/Lock pair and Reset Order, followed by compact Chorus, Delay, and Reverb rows. Shared label, control-body, and value bands align toggles, choices, and sliders throughout the editor. Every other rotary body is 64 x 64 pixels. FX uses fixed aligned Bypass and narrow Position columns followed by four common control axes; Delay places Free/Sync above Division in a narrow auxiliary column between Free Time and Feedback. The footer draws only credits, product, and version; the former nonfunctional `100%` text is absent. SETTINGS contains only GUI preferences and About/version information. Bypassed controls dim while routing and bypass controls remain usable. A lightweight message-thread timer updates preset matching, presentation, and meter ballistics. Editor construction and page switching never write sound values.

`calculateEnvelopeDisplayGeometry()` maps the existing logarithmic Attack, Decay, and Release domains to independent horizontal segments. The 0.001-second practical minimum has zero width, producing an exact vertical Attack rise or Decay/Release fall; longer values widen only the corresponding segment and Sustain remains horizontal. `createEnvelopePath()` consumes this pure geometry for both displays and never affects DSP or parameter state.

`DeepPluckLookAndFeel` owns the dark petrol/black surfaces, teal interaction emphasis, amber signal accents, rotary drawing, restrained button relief, menus, tooltips, and vector icons. Each rotary draws a bounded shadow, raised outer rim, inset radial-gradient face, fine inner rings, and a narrow upper-left specular arc before the unchanged Teal track/value and Amber pointer. The sole large Cutoff uses the same proportional rendering. `WorkflowIconKind` provides one explicit semantic mapping for workflow component IDs; `random.undo` is matched before the general `random.*` rule and draws a compact curved return arrow, while Random controls retain the dice. Waveform and envelope paths use a sharp 1.8-pixel Amber core plus bounded layered strokes whose outer falloff extends about five pixels; the glow changes presentation only. The meter retains its -60 to +6 dB normalization and uses Teal at the bottom, soft Cyan in the middle, and Amber at the top without traffic-light red bands. `DeepPluckGuiPreferences` is a process-shared JUCE file-backed service with an injectable test file, safe defaults, corrupt-file recovery, and serialized writes. Hover Help and first-run Welcome state never enter APVTS, presets, host state, or audio code. Tests install an injected backend under build-local test data and never touch the real user preference path.

## Signal flow

```text
MIDI events
    -> DeepPluckAudioProcessor: clear output + one parameter snapshot and tempo query per block
    -> DeepPluckSynthesiser: sample-accurate dispatch + one global LFO sample per audio sample
       -> shared bounded m[-1,+1] for every voice at the same absolute sample
       -> DeepPluckVoice
          DeepPluckUnison, 1..4 fixed internal slots
             Oscillator 1 selected anti-aliased waveform -> shared LFO cents ratio ----+
             Oscillator 2 selected anti-aliased waveform -> same ratio/level/bypass ---+ detune + LFO Spread + pan + N^-0.90
                                                                                         -> tonal left/right
          centred Transient wavelet -> one-shot/level/tone -----+ source sum left/right
          centred Noise -> DC block/tone/one-shot/level --------+
              -> independent dry left/right branches -----------+
              -> identical controls -> left/right 24 dB filters
                 -> bounded resonance compensation -------------+ 10 ms crossfade
                    cutoff = base + key tracking + Filter Envelope + LFO octaves
              -> one live Amp Envelope -> Velocity -> 0.08 Voice Gain
    -> true stereo voice sum
    -> global FX rack: Chorus / Delay / Reverb in one of six orders
    -> 20 ms smoothed global Output Gain
    -> lock-free L/R peak observation (read-only, no sample modification)
    -> stereo output
```

There is exactly one global output-gain stage after the complete voice sum and FX rack. The default is -3 dB. Immediately afterward, the audio thread computes finite L/R block maxima and publishes them with lock-free atomic maximum accumulation; the message thread consumes and clears those observations for display ballistics. Metering performs no allocation, lock, GUI access, or write to the output buffer. No hidden limiter, clipper, signal-dependent auto-gain, or global automatic output compensation is present. Fixed documented wet calibrations are 0.55 Chorus, 0.60 Delay, and 0.45 Reverb. Each equal-power Mix applies a shared deterministic nominal-power normalization derived only from its smoothed Mix value and calibration, capped at 1.75 (+4.86 dB). This prevents serial calibration loss while preserving headroom and exact Mix endpoints; it never analyses the signal or masks clipping/instability. The filter-local resonance curve remains an independent bounded compensation.

Both oscillators are duplicated only inside the fixed Unison slots. Their stereo tonal result is combined with exactly one centred Transient sample and one centred Noise sample per musical voice before the channel-specific filter dry/wet splits. Filter On therefore applies Drive, the four-pole low-pass, key tracking, and one shared Filter-Envelope value to the complete left/right source pair. Filter Off bypasses Drive/filter while preserving source-local shaping. One Amp Envelope, velocity, and fixed voice gain process both channels. Source completion or bypass never clears or extends a voice; the Amp Envelope remains lifecycle authority.

## Global effects rack

`DeepPluckEffectsRack` owns one prepared Chorus, Delay, and Reverb instance after the complete voice sum. It processes only preallocated buffers and never allocates, locks, queries parameters, or accesses the GUI in the audio callback. A successful state or preset load schedules a rack reset at the next block boundary. The processor reports a conservative 120-second tail.

| `fxOrder` | Serial order |
|---:|---|
| 0 | Chorus > Delay > Reverb |
| 1 | Chorus > Reverb > Delay |
| 2 | Delay > Chorus > Reverb |
| 3 | Delay > Reverb > Chorus |
| 4 | Reverb > Chorus > Delay |
| 5 | Reverb > Delay > Chorus |

Order changes use a 15 ms dry bridge before the newly selected serial path becomes active. The bridge has exact dry and wet endpoints and prevents old effect state from being interpreted in a different topology. The GUI's three position controls are projections of this single six-choice parameter rather than additional sound state.

Chorus uses two independent JUCE chorus cores with fixed 6.5 ms and 9.5 ms centre delays, zero feedback, full-wet cores, and a smoothed mid/side width stage. Delay uses preallocated stereo circular buffers with linear interpolation and a feedback-loop low-pass tone control. Free time is 10..2000 ms; Sync time is derived once per block from the cached host tempo:

```text
delayMilliseconds = 60000 / bpm * beats
choices = { 1/4, 1/8 D, 1/8, 1/8 T, 1/16 D, 1/16, 1/16 T }
beats = { 1, 3/4, 1/2, 1/3, 3/8, 1/4, 1/6 }
```

Missing or invalid tempo falls back deterministically to 120 BPM. Delay feedback is bounded to 0.75 and the line capacity covers three seconds plus interpolation reserve. Reverb uses JUCE's stereo reverb behind a pre-delay line with no dry component inside the core. Its fixed internal wet level is one third, width is one, freeze is off, and the external equal-power Mix owns the dry/wet relationship.

Rate, depth, width, feedback, tone, size, pre-delay, damping, Mix, feed, and bypass transitions are smoothed between 15 and 40 ms according to their role. Bypass feeds silence into the relevant effect while existing Delay and Reverb tails decay; it does not retain an endless frozen tail. Chorus clears when its bypass transition completes because it has no meaningful long tail. Delay and Reverb become inactive only after a bounded quiet hold following the latest possible audible output, then clear their internal state. Mix 0 is exact dry and Mix 1 is exact zero-dry with a bounded normalized wet endpoint. All three effects use deterministic reset state and bounded sample processing.

## Fixed oscillator waveform engine

For normalized phase `t` and `dt = frequency / sampleRate`, the rising saw is:

```text
sample = (2 * t - 1) - polyBLEP(t, dt)
```

The correction smooths the negative wrap discontinuity at the beginning and end of the cycle. It is the frozen V0.7.0 Saw reference and retains unity gain.

Sine is `0.82 * sin(2*pi*t)`. Square and Pulse use the same dual-edge core. For width `w`, the raw bipolar step receives a positive BLEP at phase wrap and a negative BLEP at the width edge, then removes `2*w-1`, divides by `2*max(w,1-w)`, and applies fixed gain 0.60. Square always uses `w=0.5`; Pulse uses its smoothed width after the high-frequency safety bound `guard=min(0.49,max(0.05,dt))` and `effectiveWidth=clamp(width,guard,1-guard)`. Pulse at 50% is consequently sample-identical to Square.

Triangle integrates the bandlimited 50% Square before its 0.60 output calibration:

```text
state = dt * square + (1 - dt) * state
triangle = 4 * state
```

At reset, `state=0.25*(1-4*abs(t-0.5))`, which makes every start phase deterministic and avoids inherited voice-steal state. Triangle and Saw use unity output calibration.

Every oscillator permanently owns five linear `SmoothedValue<float>` weights. A live selection makes its target weight one and all others zero; all five advance once per sample over 5 ms and their weighted sum forms the output. The fixed bank prevents allocation and keeps rapid changes continuous without phase reset. One independent linear Pulse Width smoother advances over 20 ms even outside Pulse mode, while only the Pulse formula consumes it. Processing remains bounded, allocation-free, table-free, and non-oversampled. PolyBLEP is a lightweight band-limiting approximation, not a claim of complete alias removal.

## Per-voice Unison and stereo width

The internal count `N` is latched at Note-On and clamped to 1..4. Count changes therefore affect only new or stolen notes; Detune and Spread remain live on held notes. Setting `N = 1` selects the exact legacy phases, detune, and dual-mono pan gains.

For maximum detune `D` in cents, slot `i` uses:

```text
ratio_i = 2^((D * detuneFactor[N][i]) / 1200)
```

| N | Detune factors | Pan positions | Phase offsets |
|---:|---|---|---|
| 1 | `0` | `0` | `0` |
| 2 | `-0.5, +0.5` | `-1, +1` | `-1/32, +1/32` |
| 3 | `-1, 0, +1` | `-1, 0, +1` | `-1/12, 0, +1/12` |
| 4 | `-1, -0.6, +0.6, +1` | `-1, -1/3, +1/3, +1` | `-1/8, -1/24, +1/24, +1/8` |

The detune-factor arithmetic mean is zero. Pan orientation is multiplied by `+1` or `-1` from `(voiceIndex + midiNote) % 2`, balancing simultaneous notes without randomness. Spread scales the signed pan position. Gains are calculated when targets change, then linearly smoothed for 20 ms:

```text
p = panPosition[N][i] * stereoSpread * orientation
leftGain = sqrt(max(0, 1 - p))
rightGain = sqrt(max(0, 1 + p))
```

Detune ratios use a 20 ms multiplicative smoother. Repeated automation continues from the current smoother values and never resets oscillator phase. Slot phases at Note-On are `wrap01(phaseOffset)` for Oscillator 1 and `wrap01(0.25 + phaseOffset)` for Oscillator 2. Both oscillators share the same slot ratio, preserving their Octave/Semitone/Fine-Tune interval. Mean absolute detune and phase span increase from two through four slots, making each count a deliberate density step. The tonal sum uses the fixed normalization `N^-0.90`; maximum coherent growth is therefore bounded to `N^0.10`, or 1.149 at four slots. No signal detector, limiter, clipper, or dynamic auto-gain is present.

## Global LFO and focused modulation

The LFO phase `p` is normalized to `[0,1)` and advanced once per processed sample. Free Rate is bounded to 0.020..12 Hz. In Sync mode, the cached host tempo and immutable division table determine:

```text
beatsPerCycle = [16, 8, 4, 2, 1, 0.5, 0.25]
rateHz = tempoBpm / (60 * beatsPerCycle[division])
p = p + rateHz / sampleRate
p = p - floor(p)
```

The valid 20..400 BPM sync range produces up to 26.6667 Hz at 400 BPM and 1/16; this exact musical sync result is retained even though the user-facing Free Rate maximum is 12 Hz. Invalid host tempo leaves the last valid value unchanged, starting from 120 BPM. Rate changes ramp multiplicatively over 20 ms. Waveform outputs at the current phase are:

```text
Sine     = sin(2*pi*p)
Triangle = 1 - 4*abs(p - 0.5)
Saw      = 2*p - 1
Square   = p < 0.5 ? +1 : -1
```

All are bounded to `[-1,+1]`. A two-millisecond one-pole dezipper limits parameter/waveform transition steps. Free Running never resets. Retrigger First Note resets to phase zero only when Note-On finds no active voice, including no release tail; overlapping notes and voice steals preserve phase.

The three 20 ms Amount smoothers never alter or write their APVTS base values. For shared LFO sample `m`:

```text
effectiveCutoffHz = clamp(baseCutoffWithKeyAndEnvelope * 2^(m * filterAmountOctaves))
pitchRatio = 2^(m * pitchAmountCents / 1200)
signedMovement = clamp(m * spreadAmount, -1, 1)
availableRoom = signedMovement >= 0 ? 1 - baseSpread : baseSpread
effectiveSpread = clamp(baseSpread + signedMovement * availableRoom, 0, 1)
```

The pitch ratio multiplies both oscillator base frequencies before the existing per-slot Unison detune. The endpoint-aware Spread mapping remains active at base Spread 0 and 1: full movement can reach the opposite endpoint, while intermediate Amount values move proportionally within the available direction-specific room. Spread targets are recalculated at a fixed maximum interval of 16 samples and then use the existing 20 ms pan-gain smoothers. Static zero Amounts use exact neutral branches. There is deliberately no oscillator-mix target, per-voice LFO, modulation matrix, effect, or feedback routing.

## Per-voice second oscillator

Oscillator 2 reuses the same fixed five-waveform engine. Its target pitch is:

```text
offsetSemitones = 12 * octave + semitone + fineTuneCents / 100
frequency2 = frequency1 * 2^(offsetSemitones / 12)
```

Octave is an integer from -1 to +1, Semitone an integer from -12 to +12, and Fine Tune is -50 to +50 cents. Live pitch changes use a 20 ms multiplicative ramp, so their motion is proportional in pitch space. Note-On synchronises current and target frequency immediately and resets phase to 0.25. Parameter changes and bypass never reset it.

Level is converted from dB once per rendered segment and targets a 20 ms linear-gain ramp. `osc2Enabled` targets a separate 5 ms linear crossfade. Every active Unison slot continues processing Oscillator 2 while bypassed, preserving deterministic phase and Pulse Width continuity for a later enable. A hard stop resets it to its known quarter-cycle-relative phase, and a new or stolen note applies the normal Note-On waveform/width initialization. Both oscillators remain before the shared Filter and Amp Envelopes. No sync, FM, free-running global phase, per-oscillator envelope, waveform morphing, or PWM modulation was added.

## Per-voice 24 dB filter

The filter is a nonlinear four-stage ladder low-pass. For cutoff `fc` and sample rate `fs`:

```text
pole = exp(-2 * pi * fc / fs)
g = 1 - pole
forward = 0.76923076923 * g
history = 0.23076923076 * g
feedback = 3.8 * resonance * tanh(stage4)
a = drivenInput - feedback
b = history * oldA + pole * oldB + forward * a
c = history * oldB + pole * oldC + forward * b
d = history * oldC + pole * oldD + forward * c
e = history * oldD + pole * oldE + forward * d
compensation = 1 + 1.5 * resonance^2
output = 1.2 * e * smoothed(compensation)
```

Four cascaded poles provide the nominal 24 dB/octave stopband. The automated deterministic measurement remains 24.2165 dB/octave. Soft bounded feedback prevents runaway at maximum resonance. Resonance compensation is unity at zero, rises quadratically, and is capped at 2.5 (+7.96 dB) at maximum resonance. It ramps over 20 ms and is applied after the ladder, so it neither changes feedback stability nor drives the nonlinear input stage. It is intentionally not a limiter or signal-dependent controller: non-finite output still triggers the existing explicit filter reset. The compensated isolated maximum-resonance/maximum-drive sweep peaked at 3.08514 and remained finite. Cutoff is bounded to `20 Hz .. min(20 kHz, 0.45 * sampleRate)`.

Pre-filter drive is an odd, DC-symmetric rational soft saturation. From the host value in dB:

```text
linearGain = 10^(driveDb / 20)
shape = 0.35 * (linearGain - 1)
drivenInput = input * (1 + shape) / (1 + shape * abs(input))
```

At 0 dB, `shape` is zero and the stage is exactly linear. The shape ramps over 20 ms. This keeps the control warm and bounded without a hard clip, limiter, oversampling subsystem, or separate effect.

## Cutoff modulation, smoothing, and bypass

Cutoff is calculated per sample in octave space:

```text
baseOctaves = log2(smoothedBaseCutoffHz)
keyOffset = ((midiNote - 60) / 12) * smoothedKeyTracking
envelopeOffset = smoothedEnvAmountOctaves * filterEnvelopeValue
lfoOffset = sharedLfoValue * smoothedLfoFilterAmountOctaves
cutoffHz = 2^(baseOctaves + keyOffset + envelopeOffset + lfoOffset)
```

MIDI note 60 is neutral. Key tracking 1.0 moves cutoff one octave per keyboard octave. Envelope Amount is bipolar from -5 to +5 octaves; LFO Filter Amount is bipolar from -2 to +2 octaves. Base cutoff, resonance, key tracking, envelope amount, and LFO Amount ramp over 20 ms; base cutoff is smoothed in log2 space. Drive and resonance output compensation use their own 20 ms ramps. The existing tune and output ramps remain 20 ms.

`filterEnabled` targets an internal linear 10 ms crossfade between the continuously rendered dry Saw and filter path. Repeated toggles continue from the current mix. The filter remains warm while bypassed, so enabling cannot reveal a stale state. Bypass includes drive, filter, and filter-envelope modulation but never changes voice or Amp-Envelope lifecycle.

## Per-voice transient and noise engine

The Transient is a bipolar sine wavelet. It starts at phase zero and therefore produces no hard delta at Note-On. A fixed 0.25 ms linear micro attack reaches the wavelet peak before a one-shot exponential decay. `transientTone` maps logarithmically from 650 Hz to 8 kHz:

```text
frequency = 650 * 2^(tone * log2(8000 / 650))
transient = sin(phase) * oneShot * (0.45 * 10^(levelDb / 20)) * bypassMix
```

The fixed 0.45 calibration keeps the 0 dB endpoint useful but bounded. Frequency and gain ramp over 10 ms; bypass ramps over 5 ms. Note-On synchronises current/target values, resets phase to zero, and triggers the one-shot. Enabling during an existing note only reveals the current remainder and never retriggers it.

Noise begins as per-voice xorshift32 white noise mapped to `[-1, 1]`. Each voice receives a compile-time-stable base seed mixed from its voice index. Note-On additionally mixes MIDI note and a bounded note counter. Equal prepared voice index/note/counter sequences reproduce exactly, while simultaneous voice indices are decorrelated. No global mutable generator, random device, system time, or OS entropy is used.

The raw source passes through a fixed 60 Hz DC blocker and two cascaded one-pole low-pass stages:

```text
r = exp(-2*pi*60/sampleRate)
highPassed = input - previousInput + r * previousHighPassed
a = 1 - exp(-2*pi*cutoff/sampleRate)
low1 += a * (highPassed - low1)
low2 += a * (low1 - low2)
noise = low2 * oneShot * (0.30 * 10^(levelDb / 20)) * bypassMix
```

`noiseTone` maps logarithmically from 500 Hz to `min(16 kHz, 0.45 * sampleRate)`. The coefficient, rather than its frequency, ramps over 20 ms, so no transcendental work occurs per sample. Noise gain also ramps over 20 ms and bypass over 5 ms. Filter states reset at Note-On/Hard Stop, but a disabled active layer continues deterministic internal processing so later enabling is coherent.

Both one-shots use a fixed micro attack (Transient 0.25 ms, Noise 0.50 ms) followed by a decay whose host time means peak to -60 dB. For current value `v` and `N = round(decaySeconds * sampleRate)` remaining samples:

```text
coefficient = exp(log(0.001 / v) / N)
v[n+1] = v[n] * coefficient
```

At `N` samples or `v <= 0.001`, the source becomes exactly zero/idle. Changing Decay while active recalculates from the exact current level with the new duration; it does not jump, rise, restart, or return to micro attack. Transient Decay is 1..100 ms and Noise Decay 5..1500 ms.

## DeepPluckEnvelope recurrence

Stages are `idle`, `attack`, `decay`, `sustain`, and `release`. A stage from current value `start` to `end` uses:

```text
coefficient = exp(-log((1 + ratio) / ratio) / sampleCount)
target = end +/- ratio * abs(end - start)
value = target + coefficient * (value - target)
```

The sign places the recurrence target beyond the requested endpoint. After the exact stage sample count, the value is snapped to the endpoint and the state advances. Attack uses target ratio 0.30. V0.11.25 selects an immutable construction-time contour: Amp Decay/Release uses 0.0002 (previously 0.0001), while the Filter instance explicitly retains 0.0001. Sustain changes retain ratio 0.0001 over 15 ms in both instances. There is no new parameter or per-sample contour branch. This is the user-accepted V0.11.25 contour and must remain unchanged by CPU work.

Each new stage records the minimum/maximum of its actual start and destination. The recurrence is bounded to that interval before its scheduled endpoint, preventing floating-point undershoot and the subsequent tiny upward snap. Upward live Sustain changes remain valid; the sample counter, exact endpoints, current-level update rule and voice lifecycle remain unchanged. The pre-existing finite fallback is retained, not expanded into clipping or automatic gain control. The frozen raw time values and normalized/text mapping have no migration delta.

`setParameters()` receives the current block snapshot before each rendered voice segment. When the duration or destination of the active stage changes, the stage is reconfigured from its current level; no prior sample is recalculated and no stage restarts. Attack changes only affect active Attack, Decay changes active Decay, Sustain changes active Decay/Sustain, and Release changes active Release. The new Release duration is the remaining time from the current level to zero.

`noteOff()` immediately enters Release from Attack, Decay, or Sustain. It never completes Attack or Decay first. Release starts from the current value. If a zero-sustain envelope is already effectively silent, it becomes idle without inventing hidden energy.

Amp and Filter envelopes receive their own live parameter sets before every sample-accurate voice segment. Note-on resets and triggers both. Normal note-off releases both at the same exact MIDI offset. A completed Filter Envelope does not clear the voice; the Amp Envelope remains the voice-lifecycle authority. Hard stop resets oscillator, filter, both envelopes, smoothers' next-note state, and voice ownership deterministically.

## Voice stealing and lifecycle

Voice selection order is:

1. free compatible voice;
2. quietest released compatible voice;
3. quietest remaining compatible voice.

Equal-level ties choose the oldest candidate, keeping selection deterministic. Effective level is the current envelope level multiplied by velocity gain. An unavoidable hard steal retains the last left and right output samples and fades both to zero over two milliseconds while the new note starts immediately with the current Unison count. This requires no extra voice, allocation, lock, or delayed note-on. Hard All Notes Off discards residual fades and clears every voice.

## Randomization, preset files, and host-state boundary

The fixed randomization matrix is:

| Module | APVTS indices | Count |
|---|---|---:|
| Oscillator 1 | 40, 41 | 2 |
| Oscillator 2 | 24..28, 42, 43 | 7 |
| Amp Envelope | 2..5 | 4 |
| Filter | 6..10 | 5 |
| Filter Envelope | 11..15 | 5 |
| Transient | 16..19 | 4 |
| Noise | 20..23 | 4 |
| Unison / Stereo | 29..31 | 3 |
| Modulation | 32..39 | 8 |
| Effects Rack | 44..61 | 18 |

The union contains exactly 60 unique IDs. Indices 0 and 1 (`outputGain`, `masterTune`) are never randomized or restored by Random Undo. Locks default to unlocked for every new instance and are unaffected by preset/host loads. The Effects Rack lock is a total no-op for all eighteen FX values. One successful Random replaces the prior Undo snapshot. Preset/Factory loads and host-state restore invalidate it; Save, Refresh, Amount, and Lock changes do not.

The complete V0.11.0 Random audit preserves every V0.10.4 range, weight, choice, and strategy except the two Attack mixtures. `ampAttack` uses logarithmic ranges `[0.001,0.015]`, `[0.015,0.080]`, `[0.080,0.500]`, `[0.500,2.000]` with weights `70/23/6/1`. `filterAttack` uses `[0.001,0.020]`, `[0.020,0.100]`, `[0.100,0.800]`, `[0.800,2.000]` with weights `75/20/4/1`. Statistical tests cover all declared branches, including the one-percent long tails. All other categorical, uniform, logarithmic, zero-mass, and 70%-stay/30%-move FX-order distributions are frozen at their real V0.10.4 definitions.

Each `.deeppluckpreset` is UTF-8 JSON schema 1:

```json
{
  "format": "JustALilPluckPreset",
  "schemaVersion": 1,
  "name": "Preset name",
  "author": "Author",
  "pluginVersion": "0.11.0",
  "parameters": { "all62StableIds": "numeric values" },
  "metadata": { "description": "optional" }
}
```

`pluginVersion` records the writer and is not a schema-1 load gate. Missing known parameters receive their current APVTS defaults, unknown parameters are ignored, and all known values are canonicalized through their actual JUCE parameter ranges/types before any state application. A malformed file, unsupported format/schema, nonnumeric known value, nonfinite value, oversized file, or escaped path is rejected transactionally. Direct subfolders are read-only packs; optional `pack.json` uses `JustALilPluckPack`, schema 1, and `packName`, with folder-name fallback. Only files beneath `User/` are editable.

Host state remains the full APVTS tree only. It contains neither preset stable ID nor external path, so project recall is independent of renamed, moved, or deleted preset files. On host restore, the preferred external identity is cleared and normal full-value Factory matching may still recognize a Factory sound.

## Parameters, presets, automation, and state

The following table is the frozen public V0.11.0 manifest. Types are Float (`F`), Integer (`I`), Bool (`B`), and Choice (`C`). Every version hint is `1`; `continuous` means a JUCE interval of zero. A midpoint is the raw value produced by normalized 0.5. Choice labels appear in immutable index order.

| # | ID | Host name | Type / hint | Raw range; interval | Raw default | Mapping midpoint / choices |
|---:|---|---|---|---|---:|---|
| 0 | `outputGain` | Output | F / 1 | -60..6; 0.1 | -3 | linear -27 |
| 1 | `masterTune` | Master Tune | F / 1 | 420..460; 0.1 | 440 | linear 440 |
| 2 | `ampAttack` | Attack | F / 1 | 0.001..2 s; continuous | 0.003 | 0.04472136 s |
| 3 | `ampDecay` | Decay | F / 1 | 0.001..5 s; continuous | 0.320 | 0.07071068 s |
| 4 | `ampSustain` | Sustain | F / 1 | 0..1; 0.001 | 0 | linear 0.5 |
| 5 | `ampRelease` | Release | F / 1 | 0.001..10 s; continuous | 0.180 | 0.1 s |
| 6 | `filterEnabled` | Filter | B / 1 | 0..1; 1 | 1 | false, true |
| 7 | `filterCutoff` | Filter Cutoff | F / 1 | 20..20000 Hz; continuous | 1800 | 632.45553 Hz |
| 8 | `filterResonance` | Filter Resonance | F / 1 | 0..1; 0.001 | 0.18 | linear 0.5 |
| 9 | `filterDrive` | Filter Drive | F / 1 | 0..18 dB; 0.1 | 3 | linear 9 |
| 10 | `filterKeyTracking` | Filter Key Track | F / 1 | 0..1; 0.001 | 0.35 | linear 0.5 |
| 11 | `filterEnvAmount` | Filter Env Amount | F / 1 | -5..5 oct; 0.01 | 2.50 | linear 0 |
| 12 | `filterAttack` | Filter Attack | F / 1 | 0.001..2 s; continuous | 0.002 | 0.04472136 s |
| 13 | `filterDecay` | Filter Decay | F / 1 | 0.001..5 s; continuous | 0.280 | 0.07071068 s |
| 14 | `filterSustain` | Filter Sustain | F / 1 | 0..1; 0.001 | 0 | linear 0.5 |
| 15 | `filterRelease` | Filter Release | F / 1 | 0.001..10 s; continuous | 0.180 | 0.1 s |
| 16 | `transientEnabled` | Transient | B / 1 | 0..1; 1 | 1 | false, true |
| 17 | `transientLevel` | Transient Level | F / 1 | -60..0 dB; 0.1 | -18 | linear -30 |
| 18 | `transientDecay` | Transient Decay | F / 1 | 0.001..0.100 s; continuous | 0.012 | 0.010 s |
| 19 | `transientTone` | Transient Tone | F / 1 | 0..1; 0.001 | 0.55 | linear 0.5 |
| 20 | `noiseEnabled` | Noise | B / 1 | 0..1; 1 | 1 | false, true |
| 21 | `noiseLevel` | Noise Level | F / 1 | -60..0 dB; 0.1 | -27 | linear -30 |
| 22 | `noiseDecay` | Noise Decay | F / 1 | 0.005..1.500 s; continuous | 0.120 | 0.08660254 s |
| 23 | `noiseTone` | Noise Tone | F / 1 | 0..1; 0.001 | 0.35 | linear 0.5 |
| 24 | `osc2Enabled` | Oscillator 2 | B / 1 | 0..1; 1 | 1 | false, true |
| 25 | `osc2Level` | Osc 2 Level | F / 1 | -60..0 dB; 0.1 | -15 | linear -30 |
| 26 | `osc2Octave` | Osc 2 Octave | I / 1 | -1..1; 1 | 0 | -1, 0, 1 |
| 27 | `osc2Semitone` | Osc 2 Semitone | I / 1 | -12..12; 1 | 0 | integers in order |
| 28 | `osc2FineTune` | Osc 2 Fine Tune | F / 1 | -50..50 ct; 0.1 | -7 | linear 0 |
| 29 | `unisonVoices` | Unison Voices | I / 1 | 1..4; 1 | 2 | 1, 2, 3, 4 |
| 30 | `unisonDetune` | Unison Detune | F / 1 | 0..30 ct; 0.1 | 4 | 5 ct |
| 31 | `stereoSpread` | Stereo Spread | F / 1 | 0..1; 0.001 | 0.35 | linear 0.5 |
| 32 | `lfoRate` | LFO Rate | F / 1 | 0.020..12 Hz; 0.001 | 0.25 | 0.5 Hz |
| 33 | `lfoSync` | LFO Sync | B / 1 | 0..1; 1 | 0 | Free, Sync |
| 34 | `lfoSyncDivision` | LFO Sync Division | C / 1 | 0..6; 1 | 4 | 4/1, 2/1, 1/1, 1/2, 1/4, 1/8, 1/16 |
| 35 | `lfoWaveform` | LFO Waveform | C / 1 | 0..3; 1 | 0 | Sine, Triangle, Saw, Square |
| 36 | `lfoRestartMode` | LFO Restart | C / 1 | 0..1; 1 | 1 | Free Running, Retrigger First Note |
| 37 | `lfoFilterAmount` | LFO Filter Amount | F / 1 | -2..2 oct; 0.01 | 0 | linear 0 |
| 38 | `lfoPitchAmount` | LFO Pitch Amount | F / 1 | -50..50 ct; 0.1 | 0 | linear 0 |
| 39 | `lfoSpreadAmount` | LFO Spread Amount | F / 1 | -1..1; 0.001 | 0 | linear 0 |
| 40 | `osc1Waveform` | Osc 1 Waveform | C / 1 | 0..4; 1 | 0 | Saw, Triangle, Square, Sine, Pulse |
| 41 | `osc1PulseWidth` | Osc 1 Pulse Width | F / 1 | 0.05..0.95; 0.001 | 0.50 | linear 0.50 |
| 42 | `osc2Waveform` | Osc 2 Waveform | C / 1 | 0..4; 1 | 0 | Saw, Triangle, Square, Sine, Pulse |
| 43 | `osc2PulseWidth` | Osc 2 Pulse Width | F / 1 | 0.05..0.95; 0.001 | 0.50 | linear 0.50 |
| 44 | `chorusBypass` | Chorus Bypass | B / 1 | 0..1; 1 | 1 | active, bypassed |
| 45 | `chorusRate` | Chorus Rate | F / 1 | 0.05..4 Hz; continuous | 0.35 | 1.0 Hz |
| 46 | `chorusDepth` | Chorus Depth | F / 1 | 0..1; 0.001 | 0.25 | linear 0.5 |
| 47 | `chorusWidth` | Chorus Width | F / 1 | 0..1; 0.001 | 0.65 | linear 0.5 |
| 48 | `chorusMix` | Chorus Mix | F / 1 | 0..1; 0.001 | 0.20 | linear 0.5 |
| 49 | `delayBypass` | Delay Bypass | B / 1 | 0..1; 1 | 1 | active, bypassed |
| 50 | `delaySync` | Delay Sync | B / 1 | 0..1; 1 | 1 | Free, Sync |
| 51 | `delayTime` | Delay Time | F / 1 | 0.010..2 s; continuous | 0.375 | 0.14142136 s |
| 52 | `delaySyncDivision` | Delay Sync Division | C / 1 | 0..6; 1 | 1 | 1/4, 1/8 D, 1/8, 1/8 T, 1/16 D, 1/16, 1/16 T |
| 53 | `delayFeedback` | Delay Feedback | F / 1 | 0..0.75; 0.001 | 0.32 | linear 0.375 |
| 54 | `delayTone` | Delay Tone | F / 1 | 800..18000 Hz; continuous | 6000 | 3794.73319 Hz |
| 55 | `delayMix` | Delay Mix | F / 1 | 0..1; 0.001 | 0.22 | linear 0.5 |
| 56 | `reverbBypass` | Reverb Bypass | B / 1 | 0..1; 1 | 1 | active, bypassed |
| 57 | `reverbSize` | Reverb Size | F / 1 | 0.10..0.92; 0.001 | 0.55 | linear 0.51 |
| 58 | `reverbPreDelay` | Reverb Pre-Delay | F / 1 | 0..0.120 s; 0.0001 | 0.018 | 0.040 s |
| 59 | `reverbDamping` | Reverb Damping | F / 1 | 0..1; 0.001 | 0.55 | linear 0.5 |
| 60 | `reverbMix` | Reverb Mix | F / 1 | 0..1; 0.001 | 0.18 | linear 0.5 |
| 61 | `fxOrder` | FX Order | C / 1 | 0..5; 1 | 0 | C>D>R, C>R>D, D>C>R, D>R>C, R>C>D, R>D>C |

The formatter/parser is a shared control-side service used by both JUCE parameter attributes and every visible APVTS slider. Envelope Attack/Decay/Release and Transient/Noise Decay display milliseconds below one second (`3 ms`, `280 ms`) and compact seconds above it (`1.25 s`). Delay Time uses seconds with up to three meaningful decimals. Cutoff and Delay Tone display whole Hz below 1 kHz and exact two-decimal kHz above it (`850 Hz`, `1.80 kHz`, `6.00 kHz`). Master Tune uses one-decimal Hz; LFO/Chorus Rate use two-decimal Hz. Level/Drive use one-decimal dB. Normalized amplitude, tone, width, mix, resonance, feedback, and pulse-width values display percentage with one optional decimal; positive bipolar LFO Spread includes `+`. Filter/LFO Filter amounts use signed exact two-decimal `oct`; oscillator/LFO pitch amounts use signed compact `ct`; Unison Detune uses one-decimal `ct`; octave/semitone/voice parameters use compact integers. Reverb Pre-Delay always displays compact milliseconds. Parsers accept point/comma and the matching ms/s, Hz/kHz, percent, dB, oct, ct, and st units; unitless time input is raw seconds. Formatting never runs in audio code and never quantizes stored raw values.

This manifest is the compatibility freeze. Future work must not silently alter index, ID, host name, type, version hint, range, interval/choice order, default, normalized mapping, raw unit/meaning, or text semantics. Any approved exception requires an explicit compatibility milestone, migration, automated full-manifest delta, and host/preset/automation documentation.

The historical four compatibility snapshots are Deep Pluck, Soft Pluck, Tight
Pluck, and Long Pluck. They are no longer active browser Factory entries; only
their stable factory:0..3 identities and original sounds remain loadable for old
states. Deep Pluck exactly equals the fresh APVTS defaults. Each snapshot stores
all sixty-two values; its original forty-four values are unchanged and the
appended FX values use the declared defaults with every effect bypassed. Current
active Factory application uses the approved 116-entry catalog. Preset
application uses normalized host-notified changes on the message thread.
Parameter matching shows `Custom` for every non-matching combination, including
edits to any FX value or order.

| Preset | Oscillator 2 On/Level/Octave/Semitone/Fine | Unison Voices/Detune/Spread | Transient On/Level/Decay/Tone | Noise On/Level/Decay/Tone |
|---|---|---|---|---|
| Deep Pluck | on, -15 dB, 0, 0, -7 ct | 2, 4 ct, 35% | on, -18 dB, 0.012 s, 0.55 | on, -27 dB, 0.120 s, 0.35 |
| Soft Pluck | on, -21 dB, -1, 0, +5 ct | 3, 6 ct, 50% | on, -24 dB, 0.020 s, 0.35 | on, -25 dB, 0.220 s, 0.25 |
| Tight Pluck | on, -21 dB, +1, 0, 0 ct | 1, 0 ct, 0% | on, -13 dB, 0.006 s, 0.72 | on, -32 dB, 0.055 s, 0.48 |
| Long Pluck | on, -18 dB, 0, 0, -11 ct | 4, 9 ct, 75% | on, -21 dB, 0.016 s, 0.48 | on, -24 dB, 0.320 s, 0.30 |

| Preset | Rate/Mode/Division/Waveform/Restart | Filter/Pitch/Spread Amount |
|---|---|---|
| Deep Pluck | 0.25 Hz, Free, 1/4, Sine, Retrigger First Note | 0 oct, 0 ct, 0% |
| Soft Pluck | 0.12 Hz, Free, 1/4, Sine, Free Running | +0.12 oct, 0 ct, 0% |
| Tight Pluck | 0.25 Hz, Free, 1/4, Sine, Retrigger First Note | 0 oct, 0 ct, 0% |
| Long Pluck | 0.08 Hz, Free, 1/4, Sine, Free Running | 0 oct, 0 ct, +20% |

State save writes the full 62-value APVTS tree. Restore overlays recognized properties onto the current tree. All existing V0.1-through-V0.9 migration rules remain active. Every missing V0.9.0 ID receives its declared default independently of the current live state; therefore old states load with Chorus, Delay, and Reverb bypassed and `fxOrder = 0`. Supplied partial FX values survive. V0.11.0 explicitly canonicalizes restored `ampAttack` and `filterAttack`: finite values below 0.001 become 0.001, values in 0.001..2.0 remain raw-identical, values above 2.0 become 2.0, and nonfinite values use the respective 0.003/0.002 default. Preset-file canonicalization uses the same current ranges for finite legacy values while its existing transactional parser rejects nonnumeric/nonfinite known data. A successful Factory/User/Pack/host load requests a deterministic FX reset at the next audio block so stale tails cannot leak across sounds. Oscillator phases, smoother progress, FX delay lines/tails, RNG, Amount, locks, Undo, catalog, and external preset identity are intentionally not persisted.

Host automation is normalized. Consequently, old `ampAttack`/`filterAttack` automation points keep their stored normalized number but map through the new 0.001..2.0 range; they cannot also preserve every former 0.001..5.0 raw time. Raw host-state and preset values up to 2 seconds are preserved exactly. This documented limitation affects only those two Attack automation mappings; the other sixty parameter mappings remain frozen.

## Realtime policy

The audio path performs no heap allocation, collection growth, locking, file/network access, logging, string creation, ValueTree work, GUI calls, parameter-ID lookup, entropy acquisition, random target generation, preset matching, or catalog access. Each atomic is loaded once per block and host position is queried once per block. Random parameter writes happen only from explicit control-thread actions and reach audio through the existing next-block snapshot and smoothers. Preset filesystem work runs in the dedicated worker or explicit message-thread file action. The global LFO, Unison arrays, waveform banks, stereo filter states, Chorus scratch, Delay rings, Reverb pre-delay, routing scratch, smoothers, and tail detectors are fully prepared before playback. The Delay feedback path is hard-bounded to 0.75; Reverb Freeze is always off; nonfinite feedback/filter samples are neutralized rather than concealed by a limiter.

## Central version flow

`project(DeepPluck VERSION 0.13.0)` is the single authoritative current version. Immutable references retain their original versions. The plugin, five console targets (Tests, ToneAudit, Benchmark, PresetAudit, PresetGenerator) and the separate Developer Audition GUI all explicitly inherit PROJECT_VERSION. Configure refreshes generated Windows resources for all seven targets. `DeepPluck_PreBuildVersionCheck` validates CMakeCache, every JUCE target, Debug/Release version strings/codes and resource tuples before compilation. The `artifact_version` CTest checks both plugin binaries, both local copies, every VST3 class version and frozen identity, native x64 PE headers, physical FileVersion/ProductVersion tuples and all helper executables. Complete VST3 bundle membership/hashes must match local copies. Synthetic missing/stale/class-version probes do not modify real binaries.

## Automated regression contract

V0.12.75 adds `DeepPluck.standalone_keyboard`, bringing the current configuration
to forty CTests. The suite uses actual VST3, Undefined and Standalone wrapper
identities and covers exact geometry/main-bound preservation, keyboard range and
labels, keyboard-state gestures, 12-step octave clamp, focus policy, Welcome
blocking, state/parameter neutrality, real collector coexistence and same-note
ordering, cleanup, unused-path bit identity, finite 17th-note stealing and hard
release reset. `DeepPluck.evidence_paths` runs both final-evidence wrappers in
validation-only mode, accepts bounded V0.12.0/V0.12.5/V0.12.75/V0.13.0 subpaths, rejects
an external sibling, and creates
nothing at the rejected target. The GUI suite additionally verifies the exact
Welcome/ABOUT text and URL, generated version, focus order, bounds, First Run,
Start Playing, Don't show this again, re-arm/show-now, immediate Hover Help,
valid atomic persistence under rapid updates, wrong-root fallback, shared
multi-instance behavior, and host-state neutrality. Tests use only build-local
preference files and never click the external website.

The CPU milestone adds four harness CTests for a total of 33. A missing external R25/S reference is a failing `audio_equivalence` test, never permission to create a candidate reference. The original 29-test baseline passed before harness changes. Final tests, complete paired Release performance coverage, strict old/candidate audio comparison and two independent nine-pair efficiency confirmations remain separate required gates. No automatic milestone commit is allowed until the automatable gates actually pass.

V0.11.25 adds the offline `tone_voicing` suite, making twenty-nine total. It links real production objects, checks exact envelope timing/monotonicity, exports 270 oscillator cases, 1,260 filter probes, 669 musical/stress cases plus 42 sample-partition cases and 32 real synth/rack/output chains. Original V0.11.0 objects and headers are retained separately for actual A rendering and bidirectional state readers; no old DSP formula is reconstructed. `Tests/CompareToneVoicing.ps1` verifies the frozen contract, unchanged components and predeclared delta gates, then creates full-clip constant-attenuation stereo/mono listening copies. See `Tests/ToneVoicingAudit.md` for provenance and rerun requirements.

The unchanged twenty-eight CTest suites cover all prior contracts plus:

- the complete frozen 62-entry manifest by host index, ID, name, concrete JUCE type, version hint, range, interval, default, choice order, midpoint/skew anchor, dense monotonic normalized mapping, and roundtrip behavior; exact Host and visible-GUI value texts/parsers; explicit finite/nonfinite Attack migration; and the two audited Attack Random mixtures;
- exact 60-ID/ten-module randomization coverage, isolation, exclusions, locks, normalized Amount interpolation, categorical probability, deterministic RNG/no-op ordering, one-step Undo/display boundaries, 10,000 bounded actions, every declared FX weighted branch within tolerance, and rendered randomized/high-risk states;
- User preset schema/save/load/Save As, all sixty-two values, UTF-8 and atomic replacement, old forty-four-value compatibility, filename/path protection, transactional rejection, Factory/Pack protection, stable scanning, and bounded file/depth/size limits;
- preferred User/Factory/Custom navigation, host-state independence, worker/audio separation, repeated editor lifecycle, ten unique Random/Lock pairs, three valid FX position proxies, and exactly sixty-two bindings;
- current-config artifact version and byte-identity validation for Debug and Release;
- direct Chorus/Delay/Reverb tests, exact Mix endpoints, bounded nominal-power Mix normalization, moderate all-FX RMS/peak comparison, bypass feed/tails, maximum Delay/Reverb convergence, mono compatibility, all six orders, Dry Bridge, rapid automation, load-reset safety, and 44.1/48/96 kHz by 1/17/32/64/127/512/1024 block matrices;
- full processor FX integration, one shared tempo query, legacy/factory bit identity, all-order 16-voice headroom, random/extreme peak bounds, and paired all-off plus active/tail CPU scenarios;

- sixty-two stable append-only parameters with the first forty-four unchanged, FX defaults/choices, presets, V0.1-through-V0.8 legacy migration, partial V0.9 migration, and full V0.9 roundtrip;
- oscillator safety/pitch/phase plus PolyBLEP alias-energy reduction;
- Oscillator 2 pitch ranges, anti-aliasing, phase start/continuity, smoothing, bypass identity, shared Filter/Amp routing, deterministic block partitioning, voice stealing, and multi-rate headroom;
- exact progressive Unison detune/pan/phase tables, monotonic detune-density and phase-span proxies, bounded `N^-0.90` normalization, 20 ms smoothing, count latching, phase continuity, mono return, stereo width, deterministic block partitioning, centred Transient/Noise, stereo filters, and stereo residual stealing;
- fresh-default and four-preset headroom at 44.1/48/96 kHz with 1/4/8/16-note chords, plus left/right/Mid/Side/mono metrics for Unison counts 1..4;
- deterministic warm-up/median CPU measurements for counts 1/2/4, one/sixteen musical voices, one/two oscillators, all 25 waveform pairs, and maximum active/off modulation, including the V0.7.0 Saw baseline, 1.20 Saw regression ceiling, 2.0 worst-waveform/Saw ceiling, 8.0 Unison scaling ceiling, and 2.0 modulation ceiling;
- envelope bounds/timing plus immediate Attack/Decay/Sustain-to-Release and all live ADSR changes;
- filter impulse/DC/sine stability, measured 24 dB slope, 20 ms bounded resonance compensation, passband RMS across cutoff/Drive/sample-rate combinations, and compensated maximum stress;
- per-voice Filter Envelope, bipolar modulation, key tracking, log smoothing, dry bypass identity, and live Release changes;
- isolated Transient/Noise bounds, -60 dB timing, tone brightness, DC, dB mapping, deterministic/decorrelated PRNG, live Decays, and 5 ms bypasses;
- source integration before the main filter and Amp Envelope, exact legacy-off identity, sample offsets, live bypass, and Amp-owned lifecycle;
- exact voice ownership, musical stealing, retained fade, velocity, headroom, and lifecycle;
- sample-accurate MIDI events;
- 44.1/48/96 kHz crossed with block sizes 1/17/32/64/127/512/1024;
- tune/output/envelope smoothing, 16-voice default peak, and stress stability;
- exact LFO formulas, phase/rate bounds, division/tempo table, Free/Sync behavior, output dezipper, determinism, and phase-preserving live changes;
- global sample sharing, first-note restart semantics, release/steal behavior, exact Filter/Pitch/endpoint-aware Spread mappings, audible stereo-energy movement from zero base Spread, Amount-zero and Filter-bypass neutrality, and maximum active-modulation headroom;
- frozen V0.7.0 Saw samples and phase, five bounded waveform algorithms, coherent 1024-sample DFT measurements at 44.1/48/96 kHz and low/mid/high bins, DC/peak stability, 50% Pulse/Square identity, deterministic Triangle reset, all 20 directed 5 ms crossfades, rapid switch chains, 20 ms Pulse Width changes, and Pulse Width neutrality outside Pulse;
- all 25 oscillator combinations at Unison 1 and 4, independent widths, state roundtrip, partial migration, voice stealing, deterministic output, filter/envelope/transient/noise/modulation interaction, and the 16-voice/4-slot maximum headroom matrix;
- the exact 62-binding GUI matrix, permanent oscillator row, four-tab page hierarchy, fixed 1536 x 900 geometry, stacked Master Random/Undo beside rotary Amount, equal compact MODULATION/FX row heights with reserved lower space, non-overlapping top FX action header, footer without the obsolete scale text, symbol-free two-line bold-italic header wordmark, exact `Tonetrax` About credit, vector-styled controls, explicit Undo-versus-Random icon mapping, rendered rotary highlight/shade contrast, shared 64-pixel rotary size plus the sole 112-pixel Cutoff exception, Filter two-by-two grid, strict FX axes and stacked Delay auxiliary controls, narrow FX Position column, full-height meter with -60/0/+6 dB normalization checks, Hover Help coverage, minimum-time ADSR slopes, pure waveform/ADSR geometry, GUI preference roundtrip/corrupt recovery, first-run Welcome workflow, final-signal lock-free stereo metering, order-proxy mapping/Reset, generated page snapshots, unchanged preset/Custom behaviour, repeated editor lifecycle, and sample-identical audio while the visible page changes.

Manual listening and Cubase validation remain required for musical acceptance, GUI layout, host automation modes, project recall, offline bounce, and real audio-device behavior.

## Deferred architecture

A general modulation matrix, additional effect types, free-scaling GUI architecture and computer-key/QWERTZ piano control remain separate future scopes. V0.13.0 intentionally adds no sound parameter, new Factory value, modulation target, effect type, installer, automatic updater, MIDI output, or resizable editor.
