diff --git a/AGENTS.md b/AGENTS.md index 9c242b3..00266b1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,7 @@ compiler behavior. ## Persistent State - `state/map.yaml` is the canonical repo map. +- `state/spine.yaml` is the rehydration spine for multi-agent synth library work. - `state/memory.json` stores durable project taste, doctrine, and decisions. - `state/scratch.md` is disposable current-slice working context. - `state/evidence.jsonl` stores short lessons that should change future work. diff --git a/README.md b/README.md index 8077a5b..c9ddf35 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,7 @@ and installed Faust validation. Migration coverage is tracked in [`docs/migration-checklist.md`](docs/migration-checklist.md), because leaving important things behind in the old repo would be a very efficient way to become our own haunted house. + +Reference-driven DSL growth is tracked in +[`docs/reference-synth-roadmap.md`](docs/reference-synth-roadmap.md). Agent +handoff state lives in [`state/spine.yaml`](state/spine.yaml). diff --git a/docs/reference-synth-roadmap.md b/docs/reference-synth-roadmap.md new file mode 100644 index 0000000..615d92c --- /dev/null +++ b/docs/reference-synth-roadmap.md @@ -0,0 +1,227 @@ +# Reference Synth Roadmap + +AquariumSynthCSharp needs external synthesis references for the same reason the +SFXR path was useful: a real target prevents the DSL from becoming a pile of +locally plausible knobs. The goal is not to clone every synth. The goal is to +use known engines and patch formats as pressure tests, then rebuild selected +sounds through Aquarium's own graph until the missing abstractions are obvious. + +## Objective + +Build a reference-driven synth library workflow: + +- Parse or describe external synth patches as structured reference models. +- Render references when legally and technically practical. +- Rebuild selected references in Aquarium patch DSL. +- Compare audio with analysis metrics and listening notes. +- Promote only the abstractions that make repeated reference rebuilds simpler. + +The DSL should grow because a reference target demanded a missing invariant, not +because a field name seemed maybe useful at 2 AM. + +## Live Invariants + +- Aquarium owns its patch graph. External synths are reference targets, not +runtime dependencies. +- Reference importers must preserve source provenance, license status, and the +source format hash. +- Tests may compare against rendered fixtures, extracted parameter topology, or +known structural expectations. Weak "it parsed" tests do not prove expressivity. +- Do not add a generic graph escape hatch until a specific family of references +forces it. +- When a reference target exceeds the current voice-centric DSL, document the +missing graph feature instead of smuggling it through unrelated parameters. + +## Target Families + +### Tier 1: DX7/Dexed + +Use this first. DX7 SysEx is compact, structured, and musically meaningful: +six operators, thirty-two algorithms, per-operator envelopes, ratios, feedback, +LFO modulation, pitch envelopes, key scaling, and output levels. + +Dexed is a GPL-3.0 DX7-compatible open-source synth that is closely modeled on +the Yamaha DX7 and accepts SysEx cartridge data. Its `msfa` synth engine remains +Apache-2.0, which may matter if we use code as a reference rather than only +format notes. + +Pressure on Aquarium DSL: + +- Operator graph representation. +- Multi-stage envelopes beyond ADSR. +- Algorithm presets as topology, not per-voice decoration. +- Feedback as a first-class edge. +- Ratio/fixed-frequency operator modes. +- Feature and audio parity tests against rendered references. + +First deliverable: + +- Add a `Dx7SysEx` parser for single voice and cartridge payloads. +- Convert one or two public-domain or project-authored DX7-style voices into + `ReferencePatch` records. +- Build an Aquarium DSL approximation using explicit FM templates. +- Add mel/envelope/spectral comparison against rendered Dexed or internal + reference output once the render path is chosen. + +Source: [Dexed](https://github.com/asb2m10/dexed) + +### Tier 2: ZynAddSubFX + +ZynAddSubFX is a GPL-2.0-or-later synth with additive, subtractive, and PAD +synthesis engines. It ships a large instrument library and exposes formant +filters, state-variable filters, free envelope shapes, layered kits, modulation, +and effects routing. + +Pressure on Aquarium DSL: + +- Additive harmonic banks. +- Non-ADSR envelope curves. +- Layered instruments and kits. +- Formant and state-variable filter intent. +- PAD-style spectral/wavetable generation as a distinct source type. +- Part effects versus voice-local effects. + +First deliverable: + +- Parse a small set of Zyn instrument XML files into a neutral reference model. +- Classify features used by each instrument before trying to synthesize them. +- Rebuild one additive lead, one PAD texture, and one formant/vocal patch. + +Sources: + +- [ZynAddSubFX](https://github.com/zynaddsubfx/zynaddsubfx) +- [ZynAddSubFX feature list](https://github.com/zynaddsubfx/zynaddsubfx) + +### Tier 3: Faust Examples And Libraries + +Faust remains the best reference for general DSP graph topology. Its official +repo contains categorized examples, and the standard libraries include +oscillators, filters, delays, reverbs, physical models, DX7, virtual analog +effects, wave-digital models, spatialization, and more. + +Pressure on Aquarium DSL: + +- General signal graph expression. +- Multichannel routing. +- Delay networks and feedback delay networks. +- Reverb topologies. +- Physical modeling: waveguides, modal resonators, mass interaction, exciters. +- Wave-digital circuit trees. + +First deliverable: + +- Add an `ExternalFaustReference` catalog entry type that stores source path, + license/provenance, graph family, and expected compile behavior. +- Select small fixtures from official examples first: comb string, additive + drum, stereo reverb, physical model, WDF filter. +- Use Faust references to decide whether Aquarium needs a lower-level graph DSL + beneath the current instrument patch surface. + +Sources: + +- [Faust examples](https://github.com/grame-cncm/faust) +- [Faust library docs](https://faustlibraries.grame.fr/) +- [Faust physical models](https://faustlibraries.grame.fr/libs/physmodels/) +- [Faust reverbs](https://faustlibraries.grame.fr/libs/reverbs/) +- [Faust wave-digital models](https://faustlibraries.grame.fr/libs/wdmodels/) + +### Tier 4: Surge XT + +Surge XT is a modern GPL-3.0 hybrid synth with a large real-world patch corpus. +It is a strong long-term target, but it is too broad for the first import pass. + +Pressure on Aquarium DSL: + +- Multi-scene patch structure. +- Multiple oscillator algorithms. +- Wavetables. +- Deep modulation routing. +- Filter/fx chains. +- Macro controls and performance mapping. + +First deliverable: + +- Inspect factory patch format and licensing. +- Build a feature extractor before attempting translation. +- Choose one simple subtractive patch, one wavetable patch, and one modulation + patch as future targets. + +Source: [Surge XT](https://github.com/surge-synthesizer/surge) + +### Tier 5: Cardinal/VCV-Style Modular Patches + +Cardinal is a GPL-3.0 self-contained open-source modular synth based on VCV Rack +code. It is the right target once Aquarium needs to prove arbitrary modular graph +expression. + +Pressure on Aquarium DSL: + +- Module graph representation. +- Cables as first-class edges. +- Control voltage semantics. +- Audio-rate versus control-rate modulation. +- Feedback and clocking. +- Polyphonic lanes. + +First deliverable: + +- Do not start here. +- When the lower-level graph layer exists, parse a small Cardinal patch and + classify modules, ports, cables, and rates. + +Source: [Cardinal](https://github.com/DISTRHO/Cardinal) + +## Data Model Slice + +Add reference models before importers grow teeth: + +```csharp +public sealed record ReferencePatch( + string Id, + string Family, + string Name, + ReferenceSource Source, + IReadOnlyList Features, + string? AquariumScript); + +public sealed record ReferenceSource( + string Kind, + string Uri, + string License, + string Hash, + string Notes); +``` + +This keeps source authority visible. A reference can be "known target, not yet +translated" without pretending it is an Aquarium patch. + +## Work Order + +1. Create reference model contracts and tests. +2. Add DX7 SysEx parser and feature extraction. +3. Add two DX7-derived Aquarium DSL rebuilds. +4. Add audio fixture workflow for rendered references. +5. Add Zyn XML feature extraction. +6. Add Faust reference catalog entries and compile checks. +7. Reassess DSL shape: keep voice DSL, split lower-level graph DSL, or add + explicit operator/routing sublanguage. +8. Only then touch Surge or Cardinal. + +## Rejected Shortcuts + +- Do not vendor giant preset banks before license and provenance are explicit. +- Do not translate everything into the current `voice` command by adding dozens + of fields. +- Do not compare only parse success. That proves the parser is lenient, not that + the DSL can express the sound. +- Do not use Faust as a hidden escape hatch for Aquarium patches. If a reference + requires raw Faust, the DSL is missing an abstraction or the reference belongs + in a lower-level graph layer. + +## Success Criteria + +- A new agent can pick one target family and know where to start. +- Every reference fixture says what feature pressure it applies. +- The DSL roadmap is driven by reference failures, not taste fumes. +- Aquarium can keep using readable reference patches while the synth library + grows toward serious DSP graph expression. diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 6a3eb10..741f752 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -4,3 +4,4 @@ {"kind":"sfxr_parity","lesson":"SFXR normalized filter values are not linear cutoff frequencies. In Faust export, small SFXR high-pass values must remain very low; mapping hpf directly to thousands of Hz removes the body of pickup, jump, powerup, and blip presets while still leaving them audible enough for weak tests to pass.","source":"Aquarium engine native Faust parity test against C# SFXR reference renderer"} {"kind":"dsl_reference_corpus","lesson":"BFXR, SFXR, 808, FM, and wobble presets are reference patches for testing and DSL design, not terseness trophies. Built-ins should be readable enough to teach the patch language, and the parser should support multi-line commands instead of forcing all expressive patches into one-line golf.","source":"user clarified preset purpose for patch DSL iteration"} {"kind":"advanced_reference_patches","lesson":"The synth reference catalog needs complex examples that combine defaults, templates, multiple voices, modulation buses, FM, formants, noise layers, repeat, and soft clipping. These patches expose DSL readability and composition pressure better than short one-shot SFX presets.","source":"user asked for sophisticated patch references"} +{"kind":"reference_synth_roadmap","lesson":"AquariumSynthCSharp should use external synth and DSP corpora as reference targets, not just internal demo patches. Start with DX7/Dexed SysEx for structured FM, then ZynAddSubFX, Faust examples/libraries, Surge XT, and Cardinal as progressively broader pressure tests. Keep provenance and license state explicit before vendoring fixtures.","source":"user clarified any synth can be broken down and rebuilt via Aquarium DSL"} diff --git a/state/map.yaml b/state/map.yaml index 8c1f248..a4cec04 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -9,9 +9,11 @@ surfaces: memory: state/memory.json scratch: state/scratch.md evidence: state/evidence.jsonl + spine: state/spine.yaml solution: AquariumSynthCSharp.slnx library: src/AquariumSynth.Dsl tests: tests/AquariumSynth.Dsl.Tests + reference_roadmap: docs/reference-synth-roadmap.md modules: model: src/AquariumSynth.Dsl/Model.cs parser: src/AquariumSynth.Dsl/PatchScript.cs diff --git a/state/memory.json b/state/memory.json index 1073ab2..a5631e3 100644 --- a/state/memory.json +++ b/state/memory.json @@ -26,11 +26,24 @@ ], "doctrine_refs": [ "docs/audio-doctrine.md", - "docs/migration-checklist.md" + "docs/migration-checklist.md", + "docs/reference-synth-roadmap.md", + "state/spine.yaml" ], + "reference_strategy": { + "purpose": "Use external synths and DSP corpora as pressure tests for Aquarium's patch DSL and Faust emitter.", + "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." + }, "last_verified": { "command": "dotnet test", - "result": "13 passed", - "commit": "3246672" + "result": "15 passed", + "commit": "pending reference roadmap handoff" } } diff --git a/state/scratch.md b/state/scratch.md index b921fdc..4b2d617 100644 --- a/state/scratch.md +++ b/state/scratch.md @@ -1,12 +1,16 @@ # Scratch -Current standing order: keep migrating reusable synth tooling into C# while -leaving only the Rust renderer/runtime and experimental compiler branch behind. +Current standing order: grow AquariumSynthCSharp through external reference +targets, not through speculative field sprawl. `state/spine.yaml` and +`docs/reference-synth-roadmap.md` are the handoff surfaces for agents working on +the synth library while Aquarium work continues elsewhere. -The next likely slice is serialization and host integration: +The next likely slice is DX7/Dexed: -- Define JSON contracts for `SynthPatch` and related records. -- Add a CLI for `.aqs` script -> `.dsp` / generated C#. -- Add compile cache keyed by script/options/source hash. -- Keep doctrine aligned with realtime constraints before adding runtime-facing - APIs. +- Add neutral reference model contracts with provenance/license/hash fields. +- Implement a DX7 SysEx parser skeleton for single voices and cartridge payloads. +- Extract operator topology, envelopes, ratios, feedback, levels, LFO, and pitch + envelope into feature records before trying to translate. +- Rebuild one or two small DX7-style sounds in Aquarium DSL. +- Keep tests focused on structure first, then add rendered audio comparison once + the render path is explicit. diff --git a/state/spine.yaml b/state/spine.yaml new file mode 100644 index 0000000..4b4cc65 --- /dev/null +++ b/state/spine.yaml @@ -0,0 +1,91 @@ +project: AquariumSynthCSharp +spine_version: 1 +updated: 2026-05-12T22:55:00+01:00 + +prime_directive: > + Build coherent synth machinery. References exist to reveal missing structure, + not to justify piling parameters onto the current patch DSL. + +current_objective: > + Turn AquariumSynthCSharp into a reference-driven patch authoring and Faust + generation library. Use external synths and DSP corpora as pressure tests so + Aquarium's DSL grows only when a real target proves the abstraction belongs. + +current_mechanism: + parser: src/AquariumSynth.Dsl/PatchScript.cs + model: src/AquariumSynth.Dsl/Model.cs + faust_emitter: src/AquariumSynth.Dsl/FaustExport.cs + analysis: src/AquariumSynth.Dsl/AudioAnalysis.cs + built_ins: src/AquariumSynth.Dsl/BuiltInScripts.cs + tests: tests/AquariumSynth.Dsl.Tests/PatchScriptTests.cs + doctrine: docs/audio-doctrine.md + roadmap: docs/reference-synth-roadmap.md + +live_invariants: + - Aquarium owns its patch graph; external synths are references, not runtime dependencies. + - Patch scripts lower into explicit graph records before Faust emission. + - Generated Faust should remain inspectable and deterministic. + - Reference fixtures must preserve provenance, license status, source hash, and feature pressure. + - Audio metrics are evidence, not final judgment. + - Do not add a generic graph escape hatch until specific references force it. + - Do not hide missing graph structure behind unrelated voice fields. + +reference_targets: + dx7_dexed: + priority: 1 + purpose: "Structured FM/operator graph pressure test." + source: "https://github.com/asb2m10/dexed" + first_slice: + - "Parse DX7 SysEx single voice and cartridge payloads." + - "Extract six-operator topology, envelopes, ratios, feedback, levels, LFO, pitch envelope." + - "Rebuild two small DX7-style references in Aquarium DSL." + zynaddsubfx: + priority: 2 + purpose: "Additive, subtractive, PAD, formant, kit, and layered-instrument pressure test." + source: "https://github.com/zynaddsubfx/zynaddsubfx" + first_slice: + - "Parse selected instrument XML files into neutral reference records." + - "Classify additive, PAD, formant, and effect usage before translation." + faust: + priority: 3 + purpose: "General DSP graph topology pressure test." + sources: + - "https://github.com/grame-cncm/faust" + - "https://faustlibraries.grame.fr/" + first_slice: + - "Add ExternalFaustReference catalog entries." + - "Compile selected official examples as external references." + - "Use failures to decide whether Aquarium needs a lower-level graph DSL." + surge_xt: + priority: 4 + purpose: "Modern hybrid synth and modulation matrix pressure test." + source: "https://github.com/surge-synthesizer/surge" + first_slice: + - "Inspect factory patch format and license/provenance." + - "Build feature extractor before translation." + cardinal: + priority: 5 + purpose: "Modular graph and cable/rate semantics pressure test." + source: "https://github.com/DISTRHO/Cardinal" + first_slice: + - "Defer until a lower-level graph layer exists." + +next_agent_start_here: + recommended_first_task: > + Implement reference model contracts and a DX7 SysEx parser skeleton with + feature extraction tests. Do not start by importing a giant bank. + must_read: + - AGENTS.md + - docs/audio-doctrine.md + - docs/reference-synth-roadmap.md + - state/spine.yaml + - state/memory.json + - state/evidence.jsonl + verification: + - "dotnet test AquariumSynthCSharp.slnx --no-restore" + +cut_line: + - "Cut any importer that cannot state source provenance and license." + - "Cut weak tests that only prove parser leniency." + - "Cut voice-field sprawl when a target needs topology." + - "Split a lower-level graph DSL if Faust/Cardinal-style references make the voice DSL lie."