diff --git a/README.md b/README.md index 251154f..e2a52c4 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@

C# front-end for AquaSynth patch scripts. It parses the AquaSynth patch DSL -into a serializable patch graph, emits Faust `.dsp`, and can ask an installed -Faust compiler to generate backend code such as C#, C++, C, or Rust. +into a serializable patch graph, emits Faust `.dsp`, and owns the path from +patch intent to generated DSP artifacts. This repo is the C# bridge for AquaSynth/Vortice-land. Rust remains the reference lab; this project is the tool that lets the engine script Faust without dragging @@ -53,7 +53,8 @@ external reference synth sources, and SysEx banks do not ship in - `PatchScript.Parse(script)` lowers terse script into `SynthPatch`. - `FaustEmitter.Emit(patch)` emits Faust source. - `FaustCompiler.ValidateAsync(source)` compile-checks with Faust when present. -- `FaustCompiler.CompileAsync(source, options)` writes generated backend code. +- `FaustCompiler.CompileAsync(source, options)` writes generated backend code + through an installed or resolved Faust compiler. - `BuiltInScripts.ReferenceScripts()` carries readable SFXR, BFXR-flavored, 808, FM bell, wobble bass, and advanced layered patches. They are stable references for testing and for judging whether the DSL can express useful @@ -61,7 +62,8 @@ external reference synth sources, and SysEx banks do not ship in - `SfxrParams`, `PatchScriptScoring`, `AudioAnalyzer`, and `Presets` carry the reusable AquaSynth-rs-side analysis, scoring, SFXR, and preset tools. -Faust `2.85.5` supports `-lang csharp`, so the intended hot path is: +The authoring/test lane can compile Faust-generated C# for easy .NET rendering +and parity checks: ```csharp var patch = PatchScript.Parse(""" @@ -78,6 +80,15 @@ await FaustCompiler.CompileAsync( new FaustCompileOptions(FaustTargetLanguage.CSharp, "Generated/Bass.cs")); ``` +The runtime lane is different. AquaSynth should own Faust toolchain selection, +compile cache identity, generated artifacts, parameter manifests, and dynamic +patch compilation outside the realtime path. Aquarium Engine should load and +host the finished artifact, bind controls, and schedule audio. Native Faust +targets are the preferred shipping path when the platform allows them; C# output +is convenient authoring machinery, not the ceiling. + +See [`docs/faust-toolchain-boundary.md`](docs/faust-toolchain-boundary.md). + ## Status The current slice covers the modular graph surface needed by the reference diff --git a/docs/audio-doctrine.md b/docs/audio-doctrine.md index 06ec937..42b8b2e 100644 --- a/docs/audio-doctrine.md +++ b/docs/audio-doctrine.md @@ -67,7 +67,13 @@ That separation is the model for this repo: - The DSL owns musical intent and graph construction. - The Faust emitter owns pure signal expression. +- AquaSynth owns the compile product contract: Faust toolchain selection, + target-language options, cache keys, generated artifact layout, parameter and + bus manifests, diagnostics, and provenance. - The engine owns scheduling, buffers, device I/O, threading, and presentation. +- The engine may request compilation or load a cached artifact, but dynamic + patch compilation belongs to AquaSynth and must complete outside the realtime + callback before the engine swaps in the finished DSP. - Patch parameters are runtime controls, not compile-time constants. A compiled DSP must be able to expose stable parameter paths so AquaSynth can vary a sound without recompiling the patch. @@ -77,6 +83,13 @@ Generated Faust should be stable, boring, and inspectable. Cleverness belongs in the DSL compiler only when it lowers to a graph a tired engineer can still read after midnight. +Authoring and parity tests may render through Faust-generated C# because that +keeps comparison buffers inside the .NET test harness. Shipping/runtime paths +should prefer Faust native targets when the platform permits it. Bundled Faust +belongs in an explicit AquaSynth toolchain lane, while consumer runtimes should +usually ship compiled DSP artifacts plus manifests instead of silently dragging a +compiler into the engine package. + ## Control And Modulation The Serum-style promise is not "one primitive that puts an oscillator on diff --git a/docs/faust-toolchain-boundary.md b/docs/faust-toolchain-boundary.md new file mode 100644 index 0000000..a2f119a --- /dev/null +++ b/docs/faust-toolchain-boundary.md @@ -0,0 +1,74 @@ +# Faust Toolchain Boundary + +AquaSynth owns the path from patch intent to loadable DSP product. Aquarium +Engine owns audio hosting. That split keeps the compiler brain near the patch +model and keeps the engine from becoming a synth compiler with a game loop +stapled to it. + +## Pipeline Map + +1. AquaSynth parses `.aqua` script into an explicit `SynthPatch`. +2. AquaSynth emits stable, inspectable Faust source. +3. AquaSynth resolves a pinned Faust toolchain or caller-provided Faust path. +4. AquaSynth compiles the patch into a target artifact. +5. AquaSynth emits a manifest for parameters, buses, MIDI behavior, sample-rate + assumptions, cache keys, diagnostics, and artifact provenance. +6. Aquarium loads the artifact, binds parameters, schedules notes and buffers, + and calls the DSP from its realtime path. + +Aquarium may request compilation, cache lookup, reload, or diagnostics. It +should not need to know how AquaSynth chooses Faust flags, target languages, +architecture files, generated source layout, or artifact identity. + +## Ownership + +AquaSynth owns: + +- patch graph contracts +- Faust source emission +- Faust toolchain selection and version pinning +- compile cache keys +- generated source/native artifact layout +- parameter and bus manifests +- diagnostics and compile provenance +- dynamic patch compilation outside the realtime callback + +Aquarium owns: + +- device I/O +- buffer scheduling +- audio-thread rules +- note/event delivery +- plugin or engine integration +- artifact loading policy for its platform +- presentation and editor UX + +## Runtime Rule + +Compilation is never an audio-thread operation. Dynamic patches compile in a +background worker or build step, then Aquarium swaps in a finished DSP artifact +through a realtime-safe handoff. + +Parameter changes are not recompiles. Recompile only when graph shape changes; +ordinary patch controls move through the compiled DSP parameter API. + +## Target Lanes + +- Authoring/tests may use Faust-generated C# because it is easy to render, + inspect, and compare inside the .NET test suite. +- Shipping builds should prefer Faust-generated native targets when the platform + permits it. +- A bundled Faust compiler belongs in an AquaSynth toolchain package or + installer lane, not as an accidental dependency of the core DSL package. +- Consumer runtimes should normally bundle compiled DSP artifacts plus manifests, + not the compiler itself, unless live user patch authoring is a product feature. + +## Invariants + +- AquaSynth remains the authority for what a patch means. +- Faust remains the pure DSP target, not the host runtime. +- Aquarium remains the realtime host, not the compiler. +- Generated artifacts are reproducible from script, AquaSynth version, Faust + version, target, compile options, and architecture inputs. +- Licenses and provenance travel with toolchains, reference inputs, generated + artifacts, and packaged output. diff --git a/state/evidence.jsonl b/state/evidence.jsonl index b4e5b74..d136885 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -94,3 +94,4 @@ {"date":"2026-05-16","topic":"zyn-coverage-map","lesson":"Zyn coverage must distinguish handled parity-backed behavior, counted pressure, and unknown subtypes. Broad parse success is inventory, not expressivity. The upstream GPL bank currently parses 1318/1318 files and yields 1459 enabled kit items / 63 layered instruments; PAD coverage is strongest, while ADD, SUB, active LFO routing, free envelopes, formant motion, effects, non-analog PAD filters, and several PAD OscilGen filter/waveshaper subtypes remain workout buckets.","evidence":"Added `ZynCoverageStatus`, `ZynCoverageBucket`, `ZynCoverageReport`, `ZynInstrumentSurvey.CoverageDirectory`, and `CoverageMarkdown`; `SurveysUpstreamGplInstrumentBankWhenAvailable` writes `artifacts/parity/zyn-upstream-bank-survey/coverage.md`. Focused ZynInstrumentTests passed 13/13."} {"date":"2026-05-16","topic":"zyn-pad-state-variable-filter","lesson":"Zyn PAD state-variable low-pass/high-pass filters are not a new Aqua authority. Category 2 type 0/1 maps cleanly onto existing `lpf`/`hpf` cutoff, Q, and order fields because Zyn `SVFilter` exposes low/high outputs and cascades `stages + 1`. Category 2 type 2/3 are band-pass/notch and must remain unknown until AquaSynth has a real band/notch filter owner.","evidence":"Mapped PAD filter `cat=2 type=0/1` in `ZynInstrumentReader`, moved those coverage buckets to handled, and added `RebuildMapsZynStateVariablePadLowPassToAquaFilterAuthority`. Focused ZynInstrumentTests passed 14/14."} {"date":"2026-05-16","topic":"native-bandpass-notch","lesson":"Band-pass and notch are ordinary AquaSynth filter authorities, not Zyn weirdness. `Filter` now owns `bpf`/`bpf_q`/`bpf_order` and `notch`/`notch_q`/`notch_order`; Zyn PAD analog and state-variable BPF/notch filters lower into those fields. Formants remain vowel/body color and PAD spectrum remains source-table construction.","evidence":"Added parser/model/Faust support for native BPF/notch, mapped Zyn PAD `cat=0 type=4/5` and `cat=2 type=2/3`, and added PatchScript plus ZynInstrument tests. Focused PatchScript/ZynInstrument tests passed 71/71."} +{"date":"2026-05-17","topic":"faust-toolchain-boundary","lesson":"AquaSynth should own patch-to-DSP compilation products, including Faust toolchain selection, target options, cache identity, manifests, diagnostics, provenance, and dynamic patch compilation outside the realtime callback. Aquarium Engine should request or load finished artifacts and host them, not own the meaning of patch compilation.","evidence":"User confirmed Aquarium currently owns dynamic patch compilation but that this belongs in AquaSynth so Aquarium can plug into it. Added `docs/faust-toolchain-boundary.md`, reframed README C# output as authoring/test lane, and updated doctrine/state to split Aqua compile authority from Aquarium realtime hosting."} diff --git a/state/map.yaml b/state/map.yaml index 72790b5..7aeeab9 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -15,6 +15,7 @@ surfaces: patch_library: patches tests: tests/AquaSynth.Dsl.Tests reference_roadmap: docs/reference-synth-roadmap.md + faust_toolchain_boundary: docs/faust-toolchain-boundary.md dx7_public_domain_fixtures: tests/AquaSynth.Dsl.Tests/Fixtures/Dx7/PublicDomain zyn_reference_source: external/zynaddsubfx zyn_upstream_instrument_bank: external/zynaddsubfx/instruments @@ -49,7 +50,8 @@ boundaries: - SFXR parameter mapping and atoms - analysis and comparison tools - script scoring - - Faust source generation, target-code generation, and candidate rendering + - Faust source generation, target-code generation, compile-product contracts, and candidate rendering + - dynamic patch compilation policy, cache identity, generated artifact manifests, and Faust toolchain selection dev_only: - public-domain DX7 SysEx fixtures - pinned GPL ZynAddSubFX source for parity tests diff --git a/state/memory.json b/state/memory.json index b95de43..d904de4 100644 --- a/state/memory.json +++ b/state/memory.json @@ -46,6 +46,9 @@ "standard ADSR envelope model", "note frequency/gate model", "Faust-managed playback/polyphony contract", + "Faust toolchain selection and compile-product contracts", + "dynamic patch compilation policy outside the realtime callback", + "generated DSP artifact manifests for engine hosting", "patch script parser", "SFXR atoms and parameter mapping", "analysis/comparison metrics", diff --git a/state/spine.yaml b/state/spine.yaml index 676b2bc..43fd4aa 100644 --- a/state/spine.yaml +++ b/state/spine.yaml @@ -45,6 +45,7 @@ live_invariants: - Notes own frequency, gate duration, and host/MIDI source; envelopes own ADSR level shape. Do not put note duration or SFXR punch back into the general envelope model. - Faust-managed MIDI polyphony is preferred: emit one voice graph with standard `freq`, `gain`, and `gate` controls plus `[midi:on][nvoices:n]` instead of inflating AquaSynth graphs for local allocation. - Generated Faust should remain inspectable and deterministic. + - AquaSynth owns the Faust toolchain/product boundary: target selection, compile options, cache keys, generated artifact layout, parameter/bus manifests, diagnostics, provenance, and dynamic patch compilation outside the realtime callback. Aquarium Engine hosts finished artifacts; it should not own patch-to-DSP compilation semantics. - AquaSynth Faust output may be rendered through Faust-generated C# for candidate audio comparison; reference parity still needs a real external render or captured licensed fixture. - Reference-patch correctness and synth parity tests live in AquaSynth, not AquaSynth. - Exposed patch parameters are runtime controls, not compile-time constants.