108 lines
18 KiB
JSON
108 lines
18 KiB
JSON
{
|
|
"project": "AquaSynth",
|
|
"mission": "Own AquaSynth patch authoring, analysis, preset mapping, and Faust generation in C# for the AquaSynth/Vortice engine path.",
|
|
"current_boundary": {
|
|
"leave_in_rust": [
|
|
"Rust sample renderer",
|
|
"PatchPlayer runtime",
|
|
"realtime AudioUnit bridge",
|
|
"experimental Rust DSL compiler branch"
|
|
],
|
|
"own_here": [
|
|
"patch graph model",
|
|
"operator graph model",
|
|
"renderable delayed operator feedback",
|
|
"patch parameter contracts",
|
|
"reference patch contracts",
|
|
"test-only public-domain DX7 fixtures",
|
|
"test-only dexed-py reference renderer",
|
|
"test-only project-authored Dexed patch renderer",
|
|
"ignored listening WAV parity artifacts",
|
|
"structured development .aqua patch library",
|
|
"ZynAddSubFX .xiz feature extraction with project-authored fixtures",
|
|
"Zyn additive harmonic-bank DSL surface",
|
|
"Zyn PAD spectral-cloud DSL surface with explicit Zyn profile/bandwidth/position table semantics",
|
|
"explicit low-pass Q filter damping",
|
|
"staged rate/level envelopes for normal voices",
|
|
"thresholded DX7 rendered parity test",
|
|
"reference rebuild catalog",
|
|
"DX7 SysEx structural parsing",
|
|
"DX7 algorithm topology metadata",
|
|
"DX7 effective operator-level approximation",
|
|
"DX7 measured carrier output amplitude curve",
|
|
"DX7 isolated modulation-index route scale",
|
|
"DX7 summed/cascaded modulation route scale",
|
|
"DX7 calibrated feedback amount table",
|
|
"DX7 ratio detune lowering",
|
|
"DX7 ROM COM output compensation cut",
|
|
"DX7 operator key/rate scaling at effective played note",
|
|
"DX7 topology-aware route lowering",
|
|
"DX7 envelope trace helper",
|
|
"DX7 block-interpolated envelope applied-gain trace helper",
|
|
"DX7 applied-envelope curved lowering",
|
|
"staged operator envelope DSL",
|
|
"curved staged operator envelope segments",
|
|
"DX7 envelope-to-ADSR approximation",
|
|
"standard ADSR envelope model",
|
|
"note frequency/gate model",
|
|
"Faust-managed playback/polyphony contract",
|
|
"patch script parser",
|
|
"SFXR atoms and parameter mapping",
|
|
"analysis/comparison metrics",
|
|
"Faust-generated C# candidate rendering",
|
|
"script scoring",
|
|
"preset catalogs",
|
|
"Faust emission and compiler wrapper"
|
|
]
|
|
},
|
|
"taste": [
|
|
"Patch scripts should be terse but lower into explicit, inspectable graph data.",
|
|
"Compiler abstractions should remove repeated musical structure, not hide DSP behavior.",
|
|
"Generated Faust should be boring enough to diff and debug.",
|
|
"Realtime-facing work must assume allocation, locks, file I/O, console I/O, and OS calls are hostile."
|
|
],
|
|
"doctrine_refs": [
|
|
"docs/audio-doctrine.md",
|
|
"docs/dx7-calibration-plan.md",
|
|
"docs/migration-checklist.md",
|
|
"docs/reference-synth-roadmap.md",
|
|
"state/spine.yaml"
|
|
],
|
|
"reference_strategy": {
|
|
"purpose": "Use external synths and DSP corpora as parity pressure for AquaSynth's patch DSL and Faust emitter: the DSL must reproduce behavior in terse, readable AquaSynth syntax, not clone foreign internals.",
|
|
"priority_order": [
|
|
"DX7/Dexed SysEx for FM operator topology",
|
|
"ZynAddSubFX instruments for additive, subtractive, PAD, formant, and layered patches",
|
|
"Faust examples/libraries for general DSP graph topology",
|
|
"Surge XT for modern hybrid synth modulation",
|
|
"Cardinal/VCV-style patches for modular cable/rate semantics"
|
|
],
|
|
"rule": "Add DSL abstractions only when reference rebuilds prove the missing invariant. Do not consume references that cannot be backed by parity tests; parsing alone is inventory, not evidence of expressivity."
|
|
},
|
|
"dx7_reference_path": {
|
|
"status": "DX7 SysEx parser extracts voice edit buffers, packed voices, packed 32-voice banks, six operators, envelopes, algorithm, feedback, oscillator sync, LFO, transpose, voice name, checksum validation, ReferencePatch features, algorithm topology from the reverse-engineered OPS algorithm ROM table, envelope-to-ADSR approximations, measured carrier output amplitude, isolated two-op modulation route scale, summed/cascaded route scale, calibrated feedback amount table, ratio detune lowering, voice transpose lowering, fixed-frequency operator lowering, ROM COM output compensation cut, operator key/rate scaling at the effective played note, topology-aware route lowering, staged operator envelopes with per-segment curves, raw and block-interpolated DX7 envelope tracing, applied-envelope curved lowering with optional sustain floor, two AquaSynth DSL rebuild attempts, first-class OperatorGraph support with graph-level vibrato, public-domain SysEx fixture coverage, dexed-py reference rendering, project-authored Dexed patch rendering, AquaSynth candidate rendering through Faust-generated C#, one thresholded rendered parity test for analog1.syx voice 13, a hard PRC SYNTH1 parity probe with listening WAV artifacts and log-mel <= 0.145, project-authored algorithm-8 summed-stack/cascade/summed-pair parity, rendered community parity for Mooger, Piano Bass, RES SYNTH1, and ANLGSYN 1 with a sustained high-band gate, a MELLOWSOLO pressure artifact test, and renderable delayed operator feedback.",
|
|
"lesson": "Algorithm 32's additive six-carrier shape maps cleanly to six AquaSynth voices. Algorithm 8 justified OperatorGraph: operators, modulation edges, carriers, feedback, and graph-level pitch motion belong to a graph authority, not Voice fields. The first rendered parity rung is behavioral and sine-like, not exact DX7 operator execution. A harder algorithm-8 target exposed that feedback must be delayed recursion in Faust, not a direct cyclic self-reference. Static DX7 output-level approximation helped import/rebuild math but was mathematically wrong for carrier amplitude. A single-carrier Dexed sweep shows carrier amplitude follows 2^((outputLevel - 99) / 8). Isolated two-op DX7 FM maps full-level modulation to AquaSynth route index 6.275; summed/cascaded algorithm-8 branches now use 6.0 after PRC listening, a project-authored combined stack probe, and split cascade/summed-pair probes showed lower values were under-bright. Feedback lowers through a fitted table instead of PRC-local constants; ANLGSYN 1 showed max feedback value 7 needs a hotter AquaSynth amount 2.2 to recover buzzy high bands, while value 5 remains 0.19 for PRC. ANLGSYN 1 then exposed a sustained-buzz gap: output soft clip helped but did not stop mid-note drift; max-feedback source operators need an applied-envelope sustain floor so the feedback loop stays driven after the attack. ANLGSYN 1's remaining beat is pitch LFO: AMD is nonzero but all operator AMS values are 0, while PMD/PMS lower cleanly to operator-graph vibrato with faded delay. Ratio-mode detune belongs in operator frequency lowering. DX7 voice transpose belongs in graph base frequency: transpose 24 is neutral, transpose 12 lowers MIDI note 60 to 130.8128 Hz. DX7 fixed-frequency operators are absolute Hz, not note ratios; ANLGSYN 1 proved fixed op1/op3 must lower to ratios around 0.007615/0.009227 against a 130.8128 Hz graph base to preserve low buzzing modulation. ROM COM carrier compensation was cut: user listening found no-COM renders harmonically closer and mainly too quiet, so `OperatorOutputCompensation` returns unity and graph gain owns loudness recovery. Operator key-scaling and rate-scaling must lower against the effective played MIDI note after transpose; hardcoded MIDI 60 made RES SYNTH1 too soft on attack and Piano Bass less harmonically accurate. Project-authored Dexed patch rendering now gives lawful calibration targets. Reusing the operator output curve for EG levels was tested and cut because it failed hard-target log-mel; later output-level-dependent and naive traced-release applied-envelope experiments were also cut because they worsened broader parity. Direct envelope tracing shows current linear `env=rl` cannot express DX7 EG spike/drop behavior. Dexed-style audio samples EG once per 64-sample block and interpolates applied gain, so raw EG parity alone is not audio parity. Staged operator envelopes now support linear/exponential segments and supranormal levels. `ApproximateAppliedRateLevelEnvelope` uses that surface and now picks the first near-peak before gate so slower rising modulators hit early instead of swelling late; it can also preserve a floor for max-feedback source sustain. Community gains with no COM and played-note scaling: Mooger 0.75, Piano Bass 0.72, RES SYNTH1 0.75. Algorithm-2 non-carrier applied envelopes currently cap the first peak at 0.92 to prevent ANLGSYN 1/RES SYNTH1 peak blowout while preserving Piano Bass. MELLOWSOLO is a pressure artifact test, not passing parity; its render window is 1.25s so the candidate release is audible instead of cut at the artifact boundary. Mooger needs a more aggressive attack contour, and RES SYNTH1's phase-like attack issue is likely relative operator attack/peak shape because oscillator sync is enabled. PRC remains passing on log-mel/envelope/RMS, with score floor lowered to 0.74 after rate scaling changed the aggregate score.",
|
|
"next": "Use the now-validated algorithm-8 route scale, transpose lowering, and fixed-frequency lowering as fixed inputs while isolating remaining community-voice timbre mismatch, then expand to broader public-domain banks."
|
|
},
|
|
"zyn_reference_path": {
|
|
"status": "Initial ZynInstrumentReader parses plain XML and gzip-compressed .xiz-shaped XML from project-authored fixtures into ZynInstrument records and ReferencePatch features. First Zyn-style AquaSynth rebuilds exist for additive lead, PAD texture, and vocal/formant layer. `ZynInstrumentSurvey` can rank .xiz directories by feature pressure. Named `layer` declarations provide the first Zyn-driven DSL abstraction; additive `harmonics`, PAD `spectrum`, and staged `env=rl` voice envelopes now attach to those layers. Zyn PAD `spectrum` banks can now carry explicit `zyn_mode`, `zyn_bandwidth`, `zyn_bwscale`, `zyn_profile`, and `zyn_position` table-generation fields, and `PadSynthWaveform` lowers those through Zyn-style profile/bandwidth math. The pinned Zyn source now builds as a test-only MSYS2/MinGW oracle through `tools/zyn-reference/build-zyn-reference.ps1`, which patches an ignored artifact source copy, proves `PadNoteTest`, and builds `ZynPadReference`.",
|
|
"lesson": "Start Zyn as structural classification, then move into explicit scoped rebuilds. Upstream Zyn presets live in a nested GPL instrument-bank submodule and may be used as test-only parity/pressure feedstock once provenance is recorded. The ZynAddSubFX engine source is pinned as GPL test/development material under `external/zynaddsubfx` at 3ab608c432996ba4d582176572c0b0f82328c825; use it as a parity oracle only, never as AquaSynth runtime or NuGet package content. Zyn build fixes belong in `tools/zyn-reference/patches/` and apply to ignored artifact copies, not the clean pinned submodule. The PAD oracle now renders both generated PAD sample tables and note audio through Zyn `PADnoteParameters`/`PADnote`; optional parity artifacts live under `artifacts/parity/zyn-pad-reference` and `artifacts/parity/zyn-pad-fixtures`. The first project-authored PAD comparison proved the harness and showed AquaSynth `spectrum` was not a close Zyn PAD emulation. User listening identified Zyn as pure wineglass and AquaSynth as angry bees. Cutting unearned air/noise/drive/soft-clip from the rebuild, adding interpolated wavetable reads, and raising AquaSynth spectral tables to Zyn's 131072-frame PAD size improved the report. A later pitch/drone pass split PAD table root from playback note frequency: `spectrum root=...` owns table/base frequency and `freq=...` owns the played note. Using Zyn's reported sample basefreq 77.7813 while playing 261.6256, removing gratuitous spread/vibrato, collapsing two same-note PAD voices into one coherent spectral table, and trimming gain improved the report to log-mel 0.35669, envelope 0.347572, RMS ratio 1.001704, centroid ratio 1.004072, score 0.6211. The first PAD fixture survey shows `pad-pure` and the old `pad-texture` render identically through Zyn, so the filter/category XML in `pad-texture` is not live pressure for this oracle path. `pad-harmonic` changes the source table and note output through explicit OSCIL harmonics, making it the next real PAD reference rung. The remaining gap is source-table/profile and envelope semantics, not more decorative patch layers. The report now also emits source-table WAVs for listening. The hardened reader parses the upstream GPL instrument bank at commit c5c912131b31df5fdf372d2f06a25aaf2375837f: 1318/1318 .xiz files; optional tests write a ranked report and render five upstream PAD listening fixtures under ignored artifacts. `RebuildFirstPadAsAquaSynthScript` now generates first-pass per-preset PAD `.aqua` candidates from the first enabled PAD kit item, OSCIL harmonic magnitudes, oracle-reported table base frequency, basic volume, and simple rate/level envelope scaffolding while reporting missing bandwidth/profile/filter semantics. The worst target is `olivers-100/0032-Drum Kit.xiz`, with 16 enabled kit items, mixed ADD/SUB/PAD engines, 106 envelopes, 25 free envelopes, 73 LFOs, 37 filters, and 3 formant filters. The first rebuilds show AquaSynth can sketch small Zyn fixtures with existing voices/macros/formants/noise, and the upstream survey correctly pushed named kit/layer routing first. `layer` owns name, engine tag, optional key range metadata, default gain, and effect-send labels while lowering to ordinary voices. `harmonics` owns authored ratio/gain partial banks under those layers. `spectrum` owns PAD-style root/spread/partial clouds under named layers and generated table base frequency; playback `freq` remains note authority. Staged `env=rl` now applies to voices as well as operators, preserving Note as gate authority and envelope data as level-shape authority. AquaSynth still does not claim Zyn ADDsynth phase/bandwidth details, full Zyn PAD harmonic-profile/randomness/pitch-zone behavior, arbitrary free-envelope point curves, or richer formant morphing.",
|
|
"next": "Continue Zyn PAD parity from the sampled OscilGen stage-order, multi-kit PAD, Zyn-curve static global-filter, low-pass/high-pass order, PAD frequency-detune, low-pass/high-pass filter-envelope, logarithmic Zyn envelope-time, generated Zyn PAD output-calibration, low-pass note tracking, explicit low-pass Q, Q+order cascade, one-based OscilGen oscillator-filter index, base-function-7 selected-table-root, base-function phase modulation, and adaptive-harmonics paths. Ghost Ensemble now expands its power-base multi-harmonic OscilGen source, applies Zyn filter note tracking from `freq_track`, emits `lpf_q` from the Zyn analog filter `q`, and preserves low-pass slope by cascading resonant Faust stages according to `lpf_order`; latest Ghost log-mel is 0.369402, envelope distance is 0.363685, RMS ratio is 1.054875, centroid ratio is 1.006808, and score is 0.57229. DoublePadBass is accepted by ear and latest survey reports log-mel 0.247674, envelope distance 0.208351, RMS ratio 0.888011, score 0.650319. Soft Pad is accepted by ear. Organ Choir now maps kit0 HP2/stage/filter-envelope motion and kit1's selected table root, `base_function_modulation=1`, and `adaptive_harmonics=1`; latest Organ log-mel is 0.471483, envelope distance 0.302978, RMS ratio 0.878, centroid ratio 1.011328, and score 0.541933. Its kit1 PAD table energy now matches the wind-body shape much more closely: 400-800 Hz 0.2713 vs Zyn 0.2780 and 8-16 kHz 0.1471 vs Zyn 0.1355. ZynPadReference has test-only `harmonics` and `selected` modes; selected roots are diagnostic/source-specific, not a global default, because global selected roots regressed accepted DoublePadBass. sin2x remains loud/concentrated after the one-based filter-index fix, so do not claim full Zyn PAD parity until broader OscilGen filtering/waveshaping/adaptive-harmonic behavior and note/global filter LFO ownership are mapped or deliberately excluded."
|
|
},
|
|
"note_and_envelope_model": {
|
|
"rule": "Notes own frequency, gate duration, and host/MIDI source. Envelopes own ADSR level shape.",
|
|
"sfxr_mapping": "SFXR sustain is note gate duration. SFXR punch maps to lower ADSR sustain level plus voice gain compensation so the implicit attack peak stays at 1 and the held level sits below it.",
|
|
"faust_surface": "One-shot patches use generated ADSR over patch age and note gate duration. MIDI mono/poly patches emit Faust's standard freq/gain/gate controls and [midi:on][nvoices:n] metadata so Faust architectures own note allocation."
|
|
},
|
|
"runtime_parameters": {
|
|
"rule": "Patch parameters are runtime controls, not compile-time constants.",
|
|
"faust_surface": "Faust UI controls expose parameter zones that hosts can update with setParamValue/getParamValue-style APIs.",
|
|
"aquasynth_requirement": "AquaSynth DSL patches declare stable exposed parameters with path/default/min/max/step metadata so callers can vary sound without recompiling.",
|
|
"binding_rule": "Parameter binding uses field-site references such as `lpf=@/macro/brightness` and `/opgraphs/0/operators/2/level`; the field owns meaning and the parameter owns the runtime value. Avoid target-routing semantics unless a new graph authority is deliberately mapped."
|
|
},
|
|
"last_verified": {
|
|
"command": "dotnet build AquaSynth.slnx --no-restore; dotnet test tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-build -v minimal; dotnet pack src\\AquaSynth.Dsl\\AquaSynth.Dsl.csproj -c Release --no-restore",
|
|
"result": "Renamed local repo, namespaces, package metadata, docs, state, and remote wiring to AquaSynth/AquaSynth-rs; test assembly passed 108 and pack produced AquaSynth.Dsl.0.1.0-aquasynth.1.nupkg",
|
|
"commit": "pending AquaSynth rename wiring"
|
|
}
|
|
}
|