From 4cd6b5952d35f4e600d239cb233be47c67b9e9d2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 8 May 2026 10:40:15 +0100 Subject: [PATCH] Initialize synth memory doctrine --- AGENTS.md | 55 +++++++++++++++++++ docs/audio-doctrine.md | 120 +++++++++++++++++++++++++++++++++++++++++ state/evidence.jsonl | 3 ++ state/map.yaml | 39 ++++++++++++++ state/memory.json | 36 +++++++++++++ state/scratch.md | 12 +++++ 6 files changed, 265 insertions(+) create mode 100644 AGENTS.md create mode 100644 docs/audio-doctrine.md create mode 100644 state/evidence.jsonl create mode 100644 state/map.yaml create mode 100644 state/memory.json create mode 100644 state/scratch.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9c242b3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,55 @@ +# AquariumSynthCSharp Instructions + +## Purpose + +AquariumSynthCSharp is the C# authoring and compiler front-end for Aquarium +synth patches. It owns patch graph models, terse patch scripting, SFXR-derived +patch mapping, analysis/scoring tools, preset catalogs, and Faust source/code +generation for the Aquarium/Vortice engine path. + +The Rust crate may remain a reference lab and audio renderer. This repo should +not depend on the Rust branch for reusable authoring, analysis, preset, or Faust +compiler behavior. + +## Persistent State + +- `state/map.yaml` is the canonical repo map. +- `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. +- `docs/audio-doctrine.md` is the live doctrine for DSP, realtime audio, and + patch-language design. + +Update these when the synth learns something durable. Delete stale guidance +instead of stacking little commemorative plaques in the hallway. + +## Operating Doctrine + +- Keep the audio work in this repo self-contained. External Faust is a compiler + target, not an excuse to hide local contracts. +- Authoring code may allocate, parse, search, score, and explain. Generated DSP + and runtime-facing code must assume realtime rules: no unbounded waits, no + filesystem, no console I/O, no locks, no surprise allocation. +- Treat patch scripts as a dataflow language: terse surface, explicit graph, + analyzable AST, deterministic lowering. +- Separate control-rate intent from audio-rate signal work. LFOs, envelopes, + UI controls, and buses need clear rate semantics even when they compile into + one Faust expression. +- Favor block-structured reasoning. Buffer-oriented APIs should make block + size, sample rate, latency, and channel count visible where they matter. +- Keep Faust output boring and inspectable. The DSL may be expressive; emitted + Faust should be stable, readable enough to debug, and easy to diff. +- Analysis metrics are tools, not judges. Spectral and envelope comparisons + catch regressions; listening, target references, and gameplay context still + matter. + +## Verification + +Use focused checks: + +```powershell +dotnet test +``` + +For compiler-affecting changes, make sure tests cover parse -> patch -> Faust +source. If Faust is installed, validation tests should continue to pass. diff --git a/docs/audio-doctrine.md b/docs/audio-doctrine.md new file mode 100644 index 0000000..310dd99 --- /dev/null +++ b/docs/audio-doctrine.md @@ -0,0 +1,120 @@ +# Aquarium Synth Audio Doctrine + +## Mission + +AquariumSynthCSharp is the authoring and compiler surface for Aquarium synth +patches. It should let us sketch sound quickly, compile to Faust, inspect the +graph, compare results, and hand stable contracts to the C#/Vortice side. + +The point is not to rebuild a whole audio engine in C# because we got excited +near a compiler. The point is to make patch intent portable, analyzable, and +pleasantly dangerous. + +## Realtime Law + +Realtime audio has one primitive moral fact: the callback deadline wins. + +Runtime-facing code must avoid operations with unbounded or scheduler-dependent +latency: + +- dynamic allocation or deallocation +- filesystem, network, console, or logging I/O +- locks, mutexes, blocking waits, sleeps, or context switches +- host/API calls not explicitly documented as callback-safe +- graph compilation, parsing, reflection, or cache misses + +Authoring tools can do expensive work. Audio callbacks cannot. Build patches, +compile Faust, allocate buffers, score references, and serialize contracts +outside the audio thread. Feed the realtime side precompiled DSP, fixed buffers, +plain parameters, and lock-free or double-buffered control updates. + +## Dataflow Shape + +Patch scripts are a textual dataflow language. Their job is to describe a graph: +voices, controls, modulators, envelopes, filters, and output routing. + +Pure Data is useful discipline here: + +- A patch is a graph of objects and connections, not a bag of imperative steps. +- Audio-rate work is block-based, and block size is part of the execution model. +- Subpatches can change block size, overlap, resampling, and on/off DSP state; + those ideas map to explicit compile/runtime boundaries, not hidden globals. +- Control events and signal streams are different animals. They can meet, but + the crossing should be named. + +For this repo that means: + +- Keep `SynthPatch` explicit and serializable. +- Keep parser sugar as sugar. Defaults, templates, SFXR atoms, and buses should + all lower into visible patch graph data. +- Keep block size, sample rate, channel count, and latency visible in APIs that + consume or produce audio buffers. +- Prefer deterministic lowering. Same script plus same options must produce the + same patch and Faust source. + +## Faust Boundary + +Faust describes pure DSP: inputs to outputs. Architecture files and host code +connect that pure processor to drivers, UI, MIDI, sensors, plugins, and the +outside world. + +That separation is the model for this repo: + +- The DSL owns musical intent and graph construction. +- The Faust emitter owns pure signal expression. +- The engine owns scheduling, buffers, device I/O, threading, and presentation. +- Host parameters should be smoothed where they cross into signal-rate behavior. + +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. + +## Control And Modulation + +The Serum-style promise is not "one primitive that puts an oscillator on +everything." The promise is that any meaningful parameter can be a modulation +target in an ergonomic, visible way. + +Rules: + +- Every modulation target must have a stable semantic name. +- Global controls and voice-local modulators should share target names. +- Modulators need waveform, rate, depth, phase, and bias. +- Buses are authoring conveniences; compiled patches should expose individual + target lanes. +- UI/control-rate movement should be smoothed before it becomes audible zipper + noise. + +## Metrics + +Analysis exists to catch regressions and support search, not to crown winners. + +Useful metrics: + +- envelope distance for shape and timing +- log-mel spectrogram distance for perceptual-ish spectral shape +- peak/RMS ratios for loudness sanity +- zero-crossing and centroid ratios for noise/brightness drift +- script readability and terseness scores for DSL golf work + +Do not confuse high metric agreement with musical success. The game context, +the reference sound, and the user’s ear still outrank the spreadsheet. Annoying, +but civilization has survived worse. + +## Source Distillations + +- PortAudio callback guidance: callbacks are delicate realtime contexts; avoid + unbounded calls such as allocation, I/O, context switching, mutex operations, + and unsafe API calls. + +- Pure Data `block~`/`switch~`: DSP is block-structured; subpatches can set + block size, overlap, resampling, and switched DSP state. Pd’s default block + size is 64 samples. + +- Faust architecture docs: a Faust program is pure DSP mapping inputs to + outputs; architecture files connect that DSP to drivers and controllers. + +- Faust signals library: buses, block termination, interpolation, repeat, and + smoothing are first-class signal-composition tools; smooth control crossings + instead of letting UI steps become audio artifacts. + diff --git a/state/evidence.jsonl b/state/evidence.jsonl new file mode 100644 index 0000000..ec6a6ad --- /dev/null +++ b/state/evidence.jsonl @@ -0,0 +1,3 @@ +{"kind":"migration","lesson":"The C# repo must own reusable authoring, analysis, preset, scoring, and Faust-generation behavior; only the Rust renderer/runtime and experimental compiler branch are intentionally left behind.","source":"user direction and migration pass through commit 3246672"} +{"kind":"realtime","lesson":"Runtime-facing audio code must avoid unbounded operations: allocation, locks, I/O, console output, OS calls, and context switches do not belong in audio callbacks.","source":"PortAudio callback documentation"} +{"kind":"dsp_graph","lesson":"Treat patch scripts as deterministic dataflow graphs with explicit rate and block semantics; terse syntax is acceptable only when lowering remains inspectable.","source":"Pure Data block/subpatch model and Faust block-diagram model"} diff --git a/state/map.yaml b/state/map.yaml new file mode 100644 index 0000000..8c1f248 --- /dev/null +++ b/state/map.yaml @@ -0,0 +1,39 @@ +project: AquariumSynthCSharp +mission: > + C# authoring, analysis, and Faust generation layer for Aquarium synth patches, + targeting the Aquarium/Vortice engine path without dragging the Rust renderer + into runtime dependencies. +surfaces: + instructions: AGENTS.md + doctrine: docs/audio-doctrine.md + memory: state/memory.json + scratch: state/scratch.md + evidence: state/evidence.jsonl + solution: AquariumSynthCSharp.slnx + library: src/AquariumSynth.Dsl + tests: tests/AquariumSynth.Dsl.Tests +modules: + model: src/AquariumSynth.Dsl/Model.cs + parser: src/AquariumSynth.Dsl/PatchScript.cs + faust: src/AquariumSynth.Dsl/FaustExport.cs + sfxr: src/AquariumSynth.Dsl/SfxrParams.cs + analysis: src/AquariumSynth.Dsl/AudioAnalysis.cs + scoring: src/AquariumSynth.Dsl/ScriptMetrics.cs + presets: src/AquariumSynth.Dsl/Presets.cs + built_in_scripts: src/AquariumSynth.Dsl/BuiltInScripts.cs +boundaries: + leave_in_rust: + - sample renderer + - PatchPlayer runtime + - realtime AudioUnit bridge + - experimental Rust DSL compiler branch + own_in_csharp: + - patch graph contracts + - patch script surface + - SFXR parameter mapping and atoms + - analysis and comparison tools + - script scoring + - Faust source and target-code generation +checks: + default: dotnet test + faust_validation: automatic in tests when Faust is installed diff --git a/state/memory.json b/state/memory.json new file mode 100644 index 0000000..1073ab2 --- /dev/null +++ b/state/memory.json @@ -0,0 +1,36 @@ +{ + "project": "AquariumSynthCSharp", + "mission": "Own Aquarium Synth patch authoring, analysis, preset mapping, and Faust generation in C# for the Aquarium/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", + "patch script parser", + "SFXR atoms and parameter mapping", + "analysis/comparison metrics", + "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/migration-checklist.md" + ], + "last_verified": { + "command": "dotnet test", + "result": "13 passed", + "commit": "3246672" + } +} diff --git a/state/scratch.md b/state/scratch.md new file mode 100644 index 0000000..b921fdc --- /dev/null +++ b/state/scratch.md @@ -0,0 +1,12 @@ +# Scratch + +Current standing order: keep migrating reusable synth tooling into C# while +leaving only the Rust renderer/runtime and experimental compiler branch behind. + +The next likely slice is serialization and host integration: + +- 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.