Initialize synth memory doctrine
This commit is contained in:
parent
3246672bbe
commit
4cd6b5952d
6 changed files with 265 additions and 0 deletions
55
AGENTS.md
Normal file
55
AGENTS.md
Normal file
|
|
@ -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.
|
||||
120
docs/audio-doctrine.md
Normal file
120
docs/audio-doctrine.md
Normal file
|
|
@ -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.
|
||||
<https://files.portaudio.com/docs/v19-doxydocs/writing_a_callback.html>
|
||||
- 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.
|
||||
<https://pd.iem.sh/objects/block~/>
|
||||
- Faust architecture docs: a Faust program is pure DSP mapping inputs to
|
||||
outputs; architecture files connect that DSP to drivers and controllers.
|
||||
<https://faustdoc.grame.fr/manual/architectures/>
|
||||
- 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.
|
||||
<https://faustlibraries.grame.fr/libs/signals/>
|
||||
3
state/evidence.jsonl
Normal file
3
state/evidence.jsonl
Normal file
|
|
@ -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"}
|
||||
39
state/map.yaml
Normal file
39
state/map.yaml
Normal file
|
|
@ -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
|
||||
36
state/memory.json
Normal file
36
state/memory.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
12
state/scratch.md
Normal file
12
state/scratch.md
Normal file
|
|
@ -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.
|
||||
Loading…
Reference in a new issue