# DeepPluck V0.7.5 Oscillator Waveform Expansion — Implementation Report

Date: 2026-08-31

Starting branch: `master`

Starting HEAD: `f22d8ffd458250eda2e78b9728bc92119f82599e`

Implementation commit: `d7dbf47afed90d0d958b703670cd4c52d89e30ca`

Status: **automatable DoD complete; manual V0.7.5 verification pending**

## 1. Outcome

V0.7.5 gives each existing per-voice oscillator the same deliberately limited five-waveform engine: Saw, Triangle, Square, Sine, and Pulse. Both oscillators retain their independent phase, frequency, Pulse Width, live waveform state, and Unison copies while continuing through the established shared Filter and Amp Envelopes.

The implementation preserves the DeepPluck product identity, plugin and VST3 class IDs, the no-input/stereo-output/MIDI contract, the first 40 parameter IDs and order, 16 preallocated musical voices, Unison, global modulation, Filter and resonance compensation, Transient/Noise, voice stealing, factory-preset names/order, and previous preset values. It adds no waveform morphing, PWM modulation source, phase control, sync, FM, effect, randomisation, modulation-matrix feature, dependency, or final GUI redesign.

The complete automatable Definition of Done passed. Human GUI, listening, Cubase, state/automation workflow, scaling, and DAW CPU acceptance remain open and are not claimed.

## 2. Normative documents and sound decision

`Docs/Sound/DeepPluck_Sound_Manifest_v1.0.md`, the repository rules, current architecture, README, V0.7.0 implementation report, and the V0.7.5 master prompt were read before implementation. The Sound Manifest remained normative.

The five fixed choices broaden the tonal foundation without turning DeepPluck into a general-purpose synthesizer. Static level calibrations, the unchanged factory sounds, shared downstream envelopes, and explicit exclusion of extra modulation preserve the focused deep-house pluck identity.

## 3. Confirmed environment

- Windows 11 x64
- Visual Studio 2026 Developer Command Prompt 18.9.2
- MSVC 19.51.36256; toolset directory 14.51.36231
- Windows SDK 10.0.26100.0
- CMake 4.4.3
- JUCE 9.0.1
- C++20
- Generator `Visual Studio 18 2026`, architecture `x64`

The final fresh configuration selected `cl.exe` 19.51.36256 and Windows SDK 10.0.26100.0. Other versions remain untested.

## 4. Start gate and V0.7.0 baseline

The user had explicitly confirmed V0.7.0 as tested successfully. The unchanged starting source was freshly configured and built before implementation; all 20 existing CTest suites passed in Debug and Release.

Selected recorded V0.7.0 Release baselines:

| Scenario | Baseline |
|---|---:|
| CPU median, 16 notes, both oscillators, Unison 1/2/4 | 21.6914 / 24.8980 / 32.5530 ms |
| Maximum selected factory-preset peak | 0.473075 |
| Maximum V0.7 modulation stress peak | 0.262837 |

The starting oscillator was one PolyBLEP Saw class reused by the two oscillator banks in each fixed Unison slot. Oscillator 1 began at phase 0; Oscillator 2 began at phase 0.25. Frequency and live Unison controls preserved phase, both oscillators shared the Filter/Amp Envelopes, and `DeepPluckVoice` remained the musical-voice lifecycle owner.

## 5. Protected user data

The Cubase project had been changed by the user after the older size/hash snapshot embedded in the master prompt and before this task began. The actual task-start gate therefore recorded the newer project plus its auto-save. Those actual start values and every other protected file remained unchanged after implementation:

| Path | Bytes | SHA-256 |
|---|---:|---|
| `CubaseTest/DeepPluckTest.cpr` | 785571 | `A039DBBF9F60E8A8C61AC11C5CD63527CCD2EE5FF9935020CC0071B93AF36C5B` |
| `CubaseTest/Auto Saves/DeepPluckTest-01-(MIDI importieren SpurDeepPluck).bak` | 677938 | `F589C323777A8CBEC9C3C1EF334E443E05C2889D14C5C834D66C7209EAEF8380` |
| `Docs/Codex-Bild 31. Aug. 2026, 11_53_35.png` | 1487439 | `067F6550C004B2C47857A9F75496F493E3A3C3DBC424F44DD69BA56D5DCEDDB2` |
| `Docs/DeepPluck_V0.2.0a_Master_Prompt_Rev1.txt` | 71058 | `D9FBC1AC0F427E85FEBA3001018FB7A180AB3AA9BEFB23E9345FDCE215C6A9AC` |
| `Docs/DeepPluck_V0.2.0b_MasterPrompt_Rev1.txt` | 69848 | `7485ED397B1B2541D8F3062F141388B4A31F4496465031535E2A989B9E398B4D` |
| `Docs/DeepPluck_V0.5.0_Second_Oscillator_MasterPrompt_Rev1.txt` | 67755 | `FE239DD85257ADDF1C77562414EA44126CE71EB23FCAA38C9A0D495458B8115A` |
| `Docs/DeepPluck_V0.6.0_Unison_Stereo_Width_MasterPrompt_Rev1.txt` | 74230 | `F955FBF5E8EC1FF1AA3029ACD36195E8C282E65F7AC0CBDF382D651EAC0B51B7` |
| `Docs/DeepPluck_V0.6.5_Functional_GUI_Reorganization_MasterPrompt_Rev1.txt` | 47921 | `8EBFE9705CDA3593B45A4795D02D9C7BF9D0778C92AFDA4C49546B172DC4AA00` |
| `Docs/DeepPluck_V0.7.0_Modulation_MasterPrompt_Rev1.txt` | 63160 | `250F5EB3F8092FFD6E601CEB014AAA998B133A13D029E5B68F67EF8D9BBA4051` |
| `Docs/DeepPluck_V0.7.5_Oscillator_Waveform_Expansion_MasterPrompt_Rev1.txt` | 68380 | `F197C3C2FF2C7B661101E198D6DDECE8680556EBCBDB85CE44B9D62FD6952C67` |

No protected file was edited, moved, deleted, staged, or committed.

## 6. Complete 44-parameter contract

IDs 1–40 remain unchanged. The four V0.7.5 IDs are appended at positions 41–44 and use parameter version hint 1.

| # | ID | Type/range | Fresh default |
|---:|---|---|---:|
| 1 | `outputGain` | float -60..+6 dB | -3 dB |
| 2 | `masterTune` | float 420..460 Hz | 440 Hz |
| 3 | `ampAttack` | float 0.001..5 s | 0.003 s |
| 4 | `ampDecay` | float 0.001..5 s | 0.320 s |
| 5 | `ampSustain` | float 0..1 | 0 |
| 6 | `ampRelease` | float 0.001..10 s | 0.180 s |
| 7 | `filterEnabled` | bool | on |
| 8 | `filterCutoff` | float 20..20000 Hz | 1800 Hz |
| 9 | `filterResonance` | float 0..1 | 0.18 |
| 10 | `filterDrive` | float 0..18 dB | 3 dB |
| 11 | `filterKeyTracking` | float 0..1 | 0.35 |
| 12 | `filterEnvAmount` | float -5..+5 oct | +2.50 oct |
| 13 | `filterAttack` | float 0.001..5 s | 0.002 s |
| 14 | `filterDecay` | float 0.001..5 s | 0.280 s |
| 15 | `filterSustain` | float 0..1 | 0 |
| 16 | `filterRelease` | float 0.001..10 s | 0.180 s |
| 17 | `transientEnabled` | bool | on |
| 18 | `transientLevel` | float -60..0 dB | -18 dB |
| 19 | `transientDecay` | float 0.001..0.100 s | 0.012 s |
| 20 | `transientTone` | float 0..1 | 0.55 |
| 21 | `noiseEnabled` | bool | on |
| 22 | `noiseLevel` | float -60..0 dB | -27 dB |
| 23 | `noiseDecay` | float 0.005..1.500 s | 0.120 s |
| 24 | `noiseTone` | float 0..1 | 0.35 |
| 25 | `osc2Enabled` | bool | on |
| 26 | `osc2Level` | float -60..0 dB | -15 dB |
| 27 | `osc2Octave` | int -1..+1 | 0 |
| 28 | `osc2Semitone` | int -12..+12 | 0 |
| 29 | `osc2FineTune` | float -50..+50 ct | -7 ct |
| 30 | `unisonVoices` | int 1..4 | 2 |
| 31 | `unisonDetune` | float 0..30 ct, step 0.1 | 4 ct |
| 32 | `stereoSpread` | float 0..1, step 0.001 | 0.35 |
| 33 | `lfoRate` | float 0.020..12 Hz, step 0.001 | 0.25 Hz |
| 34 | `lfoSync` | bool | false |
| 35 | `lfoSyncDivision` | choice 4/1..1/16, indices 0..6 | 1/4, index 4 |
| 36 | `lfoWaveform` | choice Sine/Triangle/Saw/Square | Sine, index 0 |
| 37 | `lfoRestartMode` | choice Free Running/Retrigger First Note | index 1 |
| 38 | `lfoFilterAmount` | float -2..+2 oct, step 0.01 | 0 |
| 39 | `lfoPitchAmount` | float -50..+50 ct, step 0.1 | 0 |
| 40 | `lfoSpreadAmount` | float -1..+1, step 0.001 | 0 |
| 41 | `osc1Waveform` | choice Saw/Triangle/Square/Sine/Pulse, indices 0..4 | Saw, index 0 |
| 42 | `osc1PulseWidth` | float 0.05..0.95, step 0.001 | 0.50 |
| 43 | `osc2Waveform` | choice Saw/Triangle/Square/Sine/Pulse, indices 0..4 | Saw, index 0 |
| 44 | `osc2PulseWidth` | float 0.05..0.95, step 0.001 | 0.50 |

All 44 raw parameter pointers are bound once outside the hot path. One bounded, validated, trivially-copyable snapshot is read per block; no APVTS string lookup occurs in voice or sample processing.

## 7. Oscillator formulas and calibration

For normalized phase `t`, increment `dt=frequency/sampleRate`, and PolyBLEP correction `B`:

```text
Saw = (2*t - 1) - B(t, dt)                       gain 1.00
Sine = sin(2*pi*t)                               gain 0.82
```

Square and Pulse share a dual-edge PolyBLEP core. A bipolar step receives the positive wrap correction and negative width-edge correction, removes its analytical DC term, and is peak-normalized before the fixed gain:

```text
raw = bipolarStep(t, w) + B(t, dt) - B(wrapped(t-w), dt)
dcRemoved = raw - (2*w - 1)
normalised = dcRemoved / (2*max(w, 1-w))
Square/Pulse = 0.60 * normalised
```

Square fixes `w=0.50`. Pulse uses the smoothed width after `guard=min(0.49,max(0.05,dt))` and `effectiveWidth=clamp(width,guard,1-guard)`. Pulse at 50% is sample-identical to Square.

Triangle is derived from the uncalibrated bandlimited 50% Square:

```text
triangleState = dt * bandlimitedSquare + (1-dt) * triangleState
Triangle = 4 * triangleState                       gain 1.00
```

At reset, `triangleState=0.25*(1-4*abs(t-0.5))`. The double state advances at every active oscillator sample even when Triangle is inaudible and is recovered deterministically if non-finite. No limiter, RMS normalizer, loudness tracker, oversampler, wavetable, or hidden makeup gain was introduced.

## 8. Live smoothing, phase, and voice lifecycle

Each oscillator permanently owns five linear `juce::SmoothedValue<float>` weights. Note-On/reset initializes an exact one-hot selection. A real live change only updates targets; all five values advance exactly once per sample for 5 ms and retain a unity sum within `1e-5`. Rapid changes can retarget an in-progress ramp without allocation or phase reset.

Each oscillator also owns one 20 ms linear Pulse Width smoother. It advances outside Pulse mode, has bounded 0.05..0.95 targets, and affects audio only in Pulse mode.

Oscillator 1 retains deterministic phase 0; Oscillator 2 retains phase 0.25. A new or stolen note initializes current/target waveform, Pulse Width, phase, and Triangle state so no previous crossfade leaks. Live waveform, Pulse Width, pitch, Unison detune/spread, Oscillator-2 level, and bypass changes preserve phase. Both oscillator banks remain ahead of the common per-voice Filter and Amp Envelopes and interact with the existing Transient, Noise, modulation, headroom, and voice-stealing paths.

## 9. State, legacy, automation, and presets

- Full V0.7.5 state roundtrip covers all 25 waveform pairs and non-default Pulse Width values.
- Representative V0.1.0-through-V0.7.0 states migrate missing fields to Saw/0.50 independently for each missing ID.
- Partial states retain every supplied new value and default only the missing field.
- Invalid waveform/Pulse Width snapshot values are bounded or replaced safely.
- Existing plugin IDs and the first 40 automation IDs remain unchanged; new IDs are append-only.
- Runtime phase, Triangle, and smoothing state are not persisted.

Factory-preset names, order, and the previous 40 values are unchanged. Each preset appends exactly `Saw / 0.50 / Saw / 0.50`. Deep Pluck still equals the fresh complete state. Editing any new parameter correctly produces `Custom`; applying a preset restores all 44 values.

## 10. GUI and attachment contract

The fixed 1180 × 700 editor and four-page navigation remain. The SOUND page now places Oscillator 1 and Oscillator 2 above Unison & Width. Each oscillator panel has the exact five-choice list and one Pulse Width control. Pulse Width stays attached but dims to alpha 0.45 outside Pulse. Oscillator-2 tonal controls also dim while Oscillator 2 is bypassed.

Attachment ownership is Header 2 + SOUND 12 + FILTER 10 + SHAPE 12 + MODULATION 8 = exactly 44. Component IDs are unique and equal their parameter IDs. Automated lifecycle/page switching retained parameters and audio, created no extra attachments, and found positive in-parent non-overlapping bounds.

## 11. Realtime-safety review

The audio path adds no allocation, lock, file/network/registry access, logging, GUI access, system time, entropy, or APVTS lookup. Waveform weights, Pulse Width smoother, Triangle state, phase, and cached frequency values are fixed members. Processing is deterministic and bounded. The processor remains the orchestrator, the editor owns no DSP, and no dependency or external asset was added.

## 12. Automated spectral and oscillator evidence

Spectral testing uses a coherent 1024-point direct DFT after four warm-up windows. Sample rates are 44.1, 48, and 96 kHz; fundamental bins are 3, 37, and 173, so every measured tone lands exactly on a DFT bin. A rectangular coherent window is used and zero neighbouring bins are ignored. Expected harmonics are separated from unwanted energy, unwanted energy is normalized by fundamental energy, and each anti-aliased waveform is compared with its naive counterpart. Pulse widths 0.25, 0.50, and 0.75 are included.

| Measurement | Maximum | Limit | Result |
|---|---:|---:|---|
| Saw unwanted/naive energy ratio | 0.0581483 | 0.65 | pass |
| Square unwanted/naive energy ratio | 0.0897064 | 0.65 | pass |
| Pulse unwanted/naive energy ratio | 0.0897064 | 0.65 | pass |
| Triangle unwanted/naive energy ratio | 0.378520 | 0.85 | pass |
| Sine coherent THD+N | -152.323 dB | <= -90 dB | pass |

Additional evidence:

- Frozen V0.7.0 Saw output: absolute sample error `<=1e-7`; cyclic phase error `<=1e-12`.
- Square versus 50% Pulse: sample-identical within `1e-7`.
- All five waveforms: finite, peak-bounded, and 1000 Hz crossing count within one crossing over one second (`<=0.1%`).
- Pulse stationary mean: `<=0.002` at widths 0.05/0.25/0.50/0.75/0.95; peak `<=0.61`.
- Square mean `<=0.001`; Sine mean `<=1e-5`.
- Triangle late-window RMS ratio stays within 5%; resets at phases 0/0.125/0.5/0.875 are sample-exact deterministic.
- All 20 directed waveform switches complete the 5 ms ramp, retain phase within `1e-12`, keep weight sum within `1e-5`, and satisfy the calibrated adjacent-jump bound.
- Rapid overlapping switch chains remain finite.
- Pulse Width reaches 0.50 halfway and 0.95 after exactly 960 samples at 48 kHz; transition mean remains `<=0.02`.
- Pulse Width transition/reference maximum adjacent jump: 0.708270 / 0.467369, within the specified calibrated bound.
- Pulse Width changes are sample-neutral in Saw, Triangle, Square, and Sine.

## 13. Integration, determinism, and headroom

The integration suite covers all 25 Oscillator-1/Oscillator-2 pairs, Unison 1 and 4, independent oscillator choices, Filter/Amp Envelopes, Transient, Noise, LFO targets, legacy/partial states, voice stealing, repeat determinism, and block partitioning.

The maximum V0.7.5 stress uses 16 simultaneous MIDI voices, four Unison slots, both oscillators, all 25 waveform pairs including Pulse at a bound and Square/Triangle, maximum meaningful Oscillator-2 level, high resonance with compensation, 18 dB Drive, Transient, Noise, active Filter/Pitch/Spread modulation, long Release, and voice stealing. All samples remained finite; maximum absolute peak was `0.364083`, below 0.95. No limiter or hidden clipping masks the result.

Existing headroom regression also passed at 44.1/48/96 kHz. The highest selected factory case remained Tight Pluck, 44.1 kHz, 16 notes, peak `0.473075`.

## 14. Release performance

The V0.7.0 Release baseline and final V0.7.5 measurements use the same deterministic load, warm-up, iterations, and median method.

| Scenario | Median/result |
|---|---:|
| V0.7.0 Unison-4 Saw/Saw baseline | 32.5530 ms |
| V0.7.5 16 voices, both oscillators, Unison 1/2/4 | 22.1440 / 26.5324 / 36.4321 ms |
| V0.7.5 Saw/Saw / V0.7.0 baseline | 1.11916, limit 1.20 |
| Worst pair, Sine/Sine | 50.3126 ms |
| Worst pair / Saw/Saw | 1.38100, limit 2.00 |
| Unison-4 / Unison-1 | 1.64524, limit 8.00 |
| Amount zero / maximum active modulation | 36.4321 / 40.8136 ms |
| Active modulation / zero | 1.12026, limit 2.00 |

An intermediate incremental Release link exposed reuse of inconsistent old objects and failed the absolute CPU gate. A configuration-specific clean rebuild recompiled every source and produced the stable passing measurements above. No performance limit, test criterion, or DSP behavior was weakened.

## 15. Final configure, build, and tests

Final configuration command:

```powershell
cmake --fresh -S . -B build -G "Visual Studio 18 2026" -A x64
```

The generated JUCE Windows resource file did not automatically refresh when only version metadata changed. `CMakeLists.txt` now removes only the two known build-local generated `.rc` files during Windows configure, allowing JUCE to regenerate them from current target metadata. This corrected stale Standalone FileVersion/ProductVersion 0.6.5 to 0.7.5 without modifying `JUCE/` or product IDs.

| Configuration | Full clean build | CTest | Test duration | Compiler errors | New compiler warnings |
|---|---|---|---:|---:|---:|
| Debug | pass | 22/22 pass | 20.00 s | 0 | 0 |
| Release | pass | 22/22 pass | 6.61 s | 0 | 0 |

The suites are `parameters`, `oscillator`, `envelope`, `filter`, `filter_envelope`, `transient_noise`, `transient_noise_integration`, `oscillator2`, `oscillator2_integration`, `unison`, `unison_integration`, `headroom`, `performance`, `synthesiser`, `sample_accuracy`, `matrix`, `gui`, `smoothing_stability`, `modulation`, `modulation_integration`, `waveform`, and `waveform_integration`.

## 16. Standalone smoke

Both final project-local Standalones were launched as real GUI processes. Each produced a nonzero native window handle titled `DeepPluck`, remained stable, accepted a normal close request, and exited with code 0 without a forced kill:

- Debug Standalone: pass
- Release Standalone: pass

This is a technical process/window smoke only, not a visual or listening acceptance.

## 17. Artifacts, versions, sizes, and SHA-256

| Artifact file | Bytes | SHA-256 |
|---|---:|---|
| Debug Standalone | 27312128 | `F2F289E71BC50B560AA10B1C11EFD000C6F2AA44D72F860BCF40B80195F489DB` |
| Debug VST3 binary | 24924672 | `9EEE760A74630A790AB71AF9B3EA27107D9A5A0670B20CD4CC37C67D2448627C` |
| Debug `moduleinfo.json` | 1086 | `E42CA30EF209944C12BFB43805D72306F36C642408794DC3D428586F5486FD45` |
| Release Standalone | 9052160 | `8E5D684FC22647358E4956970E033A582C5ECC90CC10772E95CBAE7093B59E71` |
| Release VST3 binary | 7870464 | `288F1BC88BBB8FCD131DA089846C3ED99D5E554B77EA9DA4A97EA18E2000FDC3` |
| Release `moduleinfo.json` | 1086 | `E42CA30EF209944C12BFB43805D72306F36C642408794DC3D428586F5486FD45` |

The listed files exist under both `build/DeepPluck_artefacts/<Config>` and `Artifacts/<Config>`. Every build/local pair has identical size and SHA-256, including both files in each VST3 bundle.

- Debug VST3 metadata: 0.7.5
- Release VST3 metadata: 0.7.5
- Debug Standalone FileVersion/ProductVersion: 0.7.5 / 0.7.5
- Release Standalone FileVersion/ProductVersion: 0.7.5 / 0.7.5

Manufacturer ToneTrax, codes `Tntx`/`DpV1`, bundle ID `de.tonetrax.deeppluck`, component CID `ABCDEF019182FAEB546E747844705631`, controller CID `ABCDEF011234ABCD546E747844705631`, formats, categories, and buses are unchanged.

`COPY_PLUGIN_AFTER_BUILD` remains `FALSE`. The pre-existing user-installed system VST3 was read only and remained byte/time identical:

| System file | Bytes | UTC timestamp | SHA-256 |
|---|---:|---|---|
| `Contents/Resources/moduleinfo.json` | 1086 | 2026-08-31T11:47:21.2878451Z | `EEFEF965FFC65E529A094F538C1B314DAB3B6A4C6C0072B3F0D9C3CADA569615` |
| `Contents/x86_64-win/DeepPluck.vst3` | 7857664 | 2026-08-31T11:47:21.3138447Z | `3D82FDD3CC538B371185291572883C53373B7561BE1334E1C17E2D572FFDEC33` |

No system installation occurred.

## 18. Documentation and repository safety

Changed documentation/build contract:

- `README.md`: V0.7.5 behavior, 44 parameters, waveform/Pulse Width semantics, state/preset compatibility, SOUND GUI, tests, manual host checklist, and limitations.
- `ARCHITECTURE.md`: waveform formulas/state, smoothing, signal flow, ownership, 44-parameter/state/preset/GUI/test contracts.
- `AGENTS.md`: confirmed reference milestone advanced to V0.7.5.
- `CMakeLists.txt`: version/description, resource-metadata regeneration, and two new suites.
- This report: reproducible implementation and acceptance evidence.

Repository checks:

- `git diff --check`: pass.
- Diff under `JUCE/`: empty.
- `build/` and `Artifacts/` payloads: ignored, not staged, not tracked.
- Protected untracked files: hash-equal to the actual task-start gate and uncommitted.
- Explicit staging paths were used; `git add .` was not used.
- Implementation commit: `d7dbf47afed90d0d958b703670cd4c52d89e30ca` — `feat: add oscillator waveform expansion`.
- This report is committed separately as `docs: add V0.7.5 implementation report`; its hash is supplied in the final delivery because a commit cannot contain its own hash.
- No history rewrite and no push.

## 19. Automatable Definition of Done

| Requirement | Result |
|---|---|
| Sound Manifest read and followed | pass |
| V0.7.0 user confirmation/start gate/baseline | pass |
| VS2026 x64/C++20/JUCE 9 reference environment | pass |
| Version 0.7.5 in project and all artifacts | pass |
| Plugin/class/code/bus identity unchanged | pass |
| IDs 1–40 unchanged; IDs 41–44 exact append-only | pass |
| Exactly 44 parameters and 44 GUI attachments | pass |
| Five specified waveforms on both oscillators | pass |
| Exact algorithms, calibration, smoothing, phase/lifecycle | pass |
| Legacy/partial state, presets, Custom, automation IDs | pass |
| Unison/Filter/envelopes/Transient/Noise/modulation/stealing integration | pass |
| Realtime safety and determinism | pass |
| Spectral/DC/frequency/switch/Pulse Width tests | pass |
| Headroom and 25-pair extreme matrix | pass |
| Release performance budgets | pass |
| Debug build and tests | pass, 22/22 |
| Release build and tests | pass, 22/22 |
| VST3/Standalone/local copy/version/smoke checks | pass |
| Compiler errors/new compiler warnings | 0/0 |
| No JUCE change, system install, protected-file change, or push | confirmed |
| Separate implementation/report commits | implementation complete; report commit follows |

## 20. Manual verification still required

`MANUAL VERIFICATION REQUIRED - GUI AND LISTENING TESTS`

- Switch Oscillator 1 and Oscillator 2 independently through Saw, Triangle, Square, Sine, and Pulse.
- Play at least Saw+Saw, Saw+Sine, Saw+Triangle, Square+Saw, Pulse+Triangle, Triangle+Square, Sine+Saw, Sine+Pulse, and Pulse+Pulse.
- Judge Sine as a quiet Oscillator-2 body/sub layer for clean, soft, controlled low end.
- Move Pulse Width slowly and quickly over 5–95%; check effect, clicks, level jumps, and dropouts.
- Compare Pulse at 50% directly with Square.
- Switch all waveforms on held notes and long releases; check high notes for foldback/harshness and low notes for DC/drift.
- Check short-pluck Transient precision and long-release Triangle/Pulse stability.
- Check maximum Unison/Detune/Spread for stereo stability and mono compatibility.
- Activate Filter/Pitch/Spread modulation and confirm unchanged musical behavior.
- At high Resonance and Drive, confirm compensation remains stable and does not mask clipping.
- Audition all four factory presets for their V0.7.0 character.
- Inspect all four pages at Windows scaling 100%, 125%, and 150%, including Pulse Width and Oscillator-2 dimming.
- Switch GUI pages while notes are playing.

`MANUAL VERIFICATION REQUIRED - CUBASE, STATE, AUTOMATION AND CPU`

- Scan as the same DeepPluck instrument; confirm no duplicate identity.
- Confirm the first 40 automation parameters are unchanged and the four new IDs follow append-only.
- Load V0.7.0 and older projects; confirm Saw/Saw, 50/50%, and the old sound.
- Save, close, and reload a new project with different oscillator waveforms and Pulse Widths; confirm parameter and sound recall.
- Write/read automation for `osc1Waveform`, `osc2Waveform`, `osc1PulseWidth`, and `osc2PulseWidth`, including periods when Pulse is inactive.
- Exercise automation on held notes and across host block boundaries.
- Test voice stealing, release tails, multiple instances, realtime playback, and offline bounce.
- Observe Cubase peak and host CPU for Saw/Saw, a Sine layer, Square/Pulse, and maximum Unison.
- Judge mono compatibility and stereo center.

## 21. Commit summary

- Implementation: `d7dbf47afed90d0d958b703670cd4c52d89e30ca` — `feat: add oscillator waveform expansion`.
- Report: separate commit `docs: add V0.7.5 implementation report`; hash supplied in final delivery.
- Nothing was pushed.

## 22. Final status

**automatable DoD complete; manual V0.7.5 verification pending**
