Rename project to AquaSynth

This commit is contained in:
Meta Vens 2026-05-16 05:49:44 +01:00
commit 64d0a6bf4f
47 changed files with 516 additions and 515 deletions

View file

@ -1,14 +1,14 @@
# AquariumSynthCSharp Instructions
# AquaSynth Instructions
## Purpose
AquariumSynthCSharp is the C# authoring and compiler front-end for Aquarium
AquaSynth is the C# authoring and compiler front-end for AquaSynth
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.
generation for the AquaSynth/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
The AquaSynth-rs branch may remain a reference lab and audio renderer. This repo should
not depend on AquaSynth-rs for reusable authoring, analysis, preset, or Faust
compiler behavior.
## Persistent State

8
AquaSynth.slnx Normal file
View file

@ -0,0 +1,8 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/AquaSynth.Dsl/AquaSynth.Dsl.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/AquaSynth.Dsl.Tests/AquaSynth.Dsl.Tests.csproj" />
</Folder>
</Solution>

View file

@ -1,8 +0,0 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/AquariumSynth.Dsl/AquariumSynth.Dsl.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/AquariumSynth.Dsl.Tests/AquariumSynth.Dsl.Tests.csproj" />
</Folder>
</Solution>

View file

@ -1,12 +1,12 @@
# Aquarium Synth CSharp
# AquaSynth
C# front-end for Aquarium Synth patch scripts. It parses the Aquarium patch DSL
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.
This repo is the C# bridge for Aquarium/Vortice-land. Rust remains the reference
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
the Rust crate into the room by the ankle.
the AquaSynth-rs crate into the room by the ankle.
## Run
@ -15,7 +15,7 @@ dotnet test
```
DX7 reference-render tests use `dexed-py` when a Python with `dexed` and
`numpy` is available. Set `AQUARIUM_DX7_PYTHON` to that interpreter to force a
`numpy` is available. Set `AQUASYNTH_DX7_PYTHON` to that interpreter to force a
specific runtime; otherwise the tests skip the render path and still parse the
vendored public-domain fixture.
@ -26,23 +26,23 @@ ZynAddSubFX is pinned as a GPL test/development reference under
git submodule update --init --recursive
```
That source is a parity oracle only. Aquarium does not ship Zyn code, link it
from `AquariumSynth.Dsl`, or treat it as runtime machinery.
That source is a parity oracle only. AquaSynth does not ship Zyn code, link it
from `AquaSynth.Dsl`, or treat it as runtime machinery.
## Package Boundary
Aquarium consumes this library as a pinned `AquariumSynth.Dsl` NuGet package,
not as a live project reference. Breaking synth-library work should happen here
freely, then Aquarium should intentionally update only after a new package
version is packed and tested.
Downstream consumers should pin this library as an `AquaSynth.Dsl` NuGet
package, not use it as a live project reference. Breaking synth-library work
should happen here freely, then consumers should intentionally update only after
a new package version is packed and tested.
```powershell
dotnet pack src\AquariumSynth.Dsl\AquariumSynth.Dsl.csproj -c Release
dotnet pack src\AquaSynth.Dsl\AquaSynth.Dsl.csproj -c Release
```
The test suite verifies that development fixtures, Python render helpers,
external reference synth sources, and SysEx banks do not ship in
`AquariumSynth.Dsl.nupkg`.
`AquaSynth.Dsl.nupkg`.
## Shape
@ -55,7 +55,7 @@ external reference synth sources, and SysEx banks do not ship in
references for testing and for judging whether the DSL can express useful
sound designs cleanly.
- `SfxrParams`, `PatchScriptScoring`, `AudioAnalyzer`, and `Presets` carry the
reusable Rust-side analysis, scoring, SFXR, and preset tools.
reusable AquaSynth-rs-side analysis, scoring, SFXR, and preset tools.
Faust `2.85.5` supports `-lang csharp`, so the intended hot path is:

View file

@ -1,8 +1,8 @@
# Aquarium Synth Audio Doctrine
# AquaSynth Audio Doctrine
## Mission
AquariumSynthCSharp is the authoring and compiler surface for Aquarium synth
AquaSynth is the authoring and compiler surface for AquaSynth
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.
@ -64,7 +64,7 @@ That separation is the model for this repo:
- The Faust emitter owns pure signal expression.
- The engine owns scheduling, buffers, device I/O, threading, and presentation.
- Patch parameters are runtime controls, not compile-time constants. A compiled
DSP must be able to expose stable parameter paths so Aquarium can vary a sound
DSP must be able to expose stable parameter paths so AquaSynth can vary a sound
without recompiling the patch.
- Host parameters should be smoothed where they cross into signal-rate behavior.
@ -110,7 +110,7 @@ authorities.
as a general envelope field.
- Host/MIDI note mode should expose stable note frequency and note gate controls
that the engine can wire to MIDI note-on/note-off behavior.
- Faust-managed polyphony is the preferred MIDI path. Aquarium should describe a
- Faust-managed polyphony is the preferred MIDI path. AquaSynth should describe a
single voice graph and emit Faust's standard `freq`, `gain`, and `gate`
controls with `[midi:on][nvoices:n]` options, leaving MIDI decoding and voice
allocation to Faust architectures unless a concrete target proves that
@ -122,7 +122,7 @@ authorities.
## Metrics
Analysis exists to catch regressions and support search, not to crown winners.
Aquarium Faust output can be rendered through Faust-generated C# in authoring
AquaSynth Faust output can be rendered through Faust-generated C# in authoring
tests, which gives us candidate audio buffers without depending on the old Rust
renderer. That is only half of parity: reference targets still need a real
external render or a captured, lawful fixture before the comparison means

View file

@ -2,7 +2,7 @@
## Objective
Make DX7 reference rebuilds prove Aquarium can emulate the machine, not merely
Make DX7 reference rebuilds prove AquaSynth can emulate the machine, not merely
draw a similar operator graph. The hard `PRC SYNTH1` target showed the current
DSL surface is expressive enough to describe the topology, but the importer
math is still fake in the places that matter perceptually.
@ -10,7 +10,7 @@ math is still fake in the places that matter perceptually.
## Current Mechanism
- `dexed-py` renders lawful public-domain or project-authored DX7 references.
- Aquarium renders candidate patches through Faust-generated C#.
- AquaSynth renders candidate patches through Faust-generated C#.
- Tests compare rendered audio with log-mel, envelope, duration, RMS,
zero-crossing, centroid, and listening WAV artifacts.
- `OperatorGraph` owns topology: operators, routes, carriers, feedback, and
@ -33,11 +33,11 @@ math is still fake in the places that matter perceptually.
- Render project-authored DX7 algorithm-32 patches with one audible carrier.
- Sweep operator output levels.
- Fit output level to linear carrier amplitude.
- Verify Aquarium single-carrier renders match relative RMS.
- Verify AquaSynth single-carrier renders match relative RMS.
2. **Two-operator modulation index**
- Render a carrier plus one modulator across modulator output levels.
- Fit DX7 output level to Aquarium route/index scaling using log-mel and
- Fit DX7 output level to AquaSynth route/index scaling using log-mel and
harmonic energy, not RMS.
- Keep carrier amplitude fixed while calibrating modulation index.
@ -46,7 +46,7 @@ math is still fake in the places that matter perceptually.
- Fit feedback amount separately from route/index scaling.
4. **Ratio detune**
- Lower DX7 ratio-mode detune into Aquarium operator ratios.
- Lower DX7 ratio-mode detune into AquaSynth operator ratios.
- Keep detune as frequency behavior, not a modulation or chorus feature.
5. **Algorithm output compensation**
@ -70,14 +70,14 @@ math is still fake in the places that matter perceptually.
unlike the current linear `env=rl` approximation.
- Dexed's rendered audio does not hear that raw state directly. The operator
path samples EG once per 64-sample block and linearly interpolates the
applied gain across the block, so a DX7-capable Aquarium envelope must
applied gain across the block, so a DX7-capable AquaSynth envelope must
model both the rate/level state machine and its block-interpolated output.
- Aquarium staged operator envelopes now support per-segment curves, e.g.
- AquaSynth staged operator envelopes now support per-segment curves, e.g.
`env=rl rates=.00145,.0508,.5268,.35 levels=2,.297,.0156,0 curves=lin,exp,exp,exp`.
This is a general staged-contour feature, not a DX7-specific syntax escape.
- `Dx7SysEx.ApproximateAppliedRateLevelEnvelope` lowers a DX7 EG into a
curved staged contour by tracing Dexed-style block-interpolated applied
gain and normalizing it against Aquarium's separate operator-output level.
gain and normalizing it against AquaSynth's separate operator-output level.
On `PRC SYNTH1`, this improves log-mel from `0.24476814` to `0.17186502`,
envelope distance from `0.43700194` to `0.13057204`, and zero-crossing
ratio from `0.7931764` to `0.7815517` while keeping RMS at unity.
@ -117,11 +117,11 @@ math is still fake in the places that matter perceptually.
envelope distance at `<= 0.14`, zero-crossing ratio in `0.8..1.11`, and
score at `>= 0.6`.
- `Piano Bass` exposed a real pitch bug: the candidate ignored the DX7 voice
transpose byte, so `transpose=12` rendered one octave too high. Aquarium
transpose byte, so `transpose=12` rendered one octave too high. AquaSynth
now uses `Dx7SysEx.NoteFrequencyHz(midiNote, transpose)`, where `24` is
neutral.
- `ANLGSYN 1` exposed a second real pitch/frequency bug: DX7 fixed-frequency
operators were being lowered as fake note ratios. Aquarium now maps fixed
operators were being lowered as fake note ratios. AquaSynth now maps fixed
operators through the Dexed log-frequency formula and divides the absolute
Hz by the graph base frequency. This restores the low fixed-frequency
carrier/modulation buzz for op1/op3 instead of flattening the voice into
@ -129,13 +129,13 @@ math is still fake in the places that matter perceptually.
- Community listening then exposed two note-dependent lowering gaps:
operator key-scaling was computed against hardcoded MIDI note 60, and DX7
operator rate scaling was parsed but not applied to the block-interpolated
envelope trace. Aquarium now lowers both against the effective played
envelope trace. AquaSynth now lowers both against the effective played
MIDI note after voice transpose. This makes `RES SYNTH1` attack much
closer and improves `Piano Bass` log-mel, with graph gain rebalanced to
`0.72` for `Piano Bass`.
- `ANLGSYN 1` also exposed that the previous max-feedback value was too tame
for audible community patches. DX7 feedback value `7` now lowers to
Aquarium feedback `2.2`, while feedback value `5` remains at `0.19` for
AquaSynth feedback `2.2`, while feedback value `5` remains at `0.19` for
the hard PRC target. The hotter value restores much more of the 1.2-5 kHz
buzzing-harmonic band, with envelope/RMS still imperfect.
- Listening then caught that the max-feedback candidate still lost punch
@ -145,7 +145,7 @@ math is still fake in the places that matter perceptually.
gates `ANLGSYN 1` with a sustained 2.5-5 kHz band-energy check so the
old drifting version cannot pass by global log-mel score alone.
- Later listening caught `ANLGSYN 1` and `RES SYNTH1` blowing out into a
drone at the modulator peak. Until Aquarium has a more exact DX7 EG/output
drone at the modulator peak. Until AquaSynth has a more exact DX7 EG/output
model, algorithm-2 non-carrier applied envelopes cap only the first staged
peak at `0.92`. This leaves `Piano Bass` unchanged, improves `RES SYNTH1`,
and keeps `ANLGSYN 1` judged primarily by log-mel plus sustained high-band

View file

@ -1,7 +1,7 @@
# Migration Checklist
This repo is now the authoring-side home for Aquarium Synth patch scripts and
Faust generation. The Rust crate remains useful as a reference lab, but the
This repo is now the authoring-side home for AquaSynth patch scripts and
Faust generation. The AquaSynth-rs crate remains useful as a reference lab, but the
current C# surface should not depend on somebody remembering what got built
there.
@ -21,7 +21,7 @@ there.
classic 808, FM bell, wobble bass, and advanced layered patches.
- Abstract SFXR reference script using defaults and templates.
- SFXR parameter presets, mutation, shorthand atoms, and SFXR-to-patch mapping.
- Aquarium preset patches: pluck, heartbeat, voice, and SFXR named bridge.
- AquaSynth preset patches: pluck, heartbeat, voice, and SFXR named bridge.
- Patch script scoring: terse, readability, and balanced metrics.
- Audio analysis and comparison: envelope, log-mel spectrogram, spectral
features, distance metrics, and score.

View file

@ -1,10 +1,10 @@
# Reference Synth Roadmap
AquariumSynthCSharp needs external synthesis references for the same reason the
AquaSynth 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's internals. 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
selected sounds through AquaSynth's own graph until the missing abstractions are
obvious.
## Objective
@ -13,18 +13,18 @@ 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.
- Rebuild selected references in AquaSynth patch DSL.
- Compare audio with analysis metrics and listening notes.
- Promote only the abstractions that make repeated reference rebuilds simpler.
- Reject reference targets we cannot turn into parity tests. Parsing a format is
inventory; proving Aquarium can reproduce the behavior is the point.
inventory; proving AquaSynth can reproduce the behavior is the point.
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
- AquaSynth owns its patch graph. External synths are reference targets, not
runtime dependencies.
- Expressive parameters are part of the patch contract. Callers must be able to
change exposed values at runtime through the hosted DSP parameter API instead
@ -32,10 +32,10 @@ runtime dependencies.
- Reference importers must preserve source provenance, license status, and the
source format hash.
- Tests must compare against rendered fixtures, extracted parameter topology, or
known structural expectations that prove Aquarium can express the same behavior.
known structural expectations that prove AquaSynth can express the same behavior.
Weak "it parsed" tests do not prove expressivity, and references without a
credible parity test path should not be consumed.
- Aquarium Faust output can be rendered through Faust-generated C# for candidate
- AquaSynth Faust output can be rendered through Faust-generated C# for candidate
audio comparison. Reference audio still needs a real source fixture or renderer;
do not compare a rebuild only against itself and call that parity.
- DX7 reference audio uses `dexed-py` when available, with public-domain SysEx
@ -43,7 +43,7 @@ credible parity test path should not be consumed.
package content.
- ZynAddSubFX source is pinned under `external/zynaddsubfx` as GPL
test/development material. It may be compiled or wrapped only for parity
tests; Aquarium must not ship, link, or runtime-depend on it.
tests; AquaSynth must not ship, link, or runtime-depend on it.
- 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
@ -62,7 +62,7 @@ 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:
Pressure on AquaSynth DSL:
- Operator graph representation.
- Multi-stage envelopes beyond ADSR.
@ -76,9 +76,9 @@ 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.
- Build an AquaSynth DSL approximation using explicit FM templates.
- Add mel/envelope/spectral comparison against rendered Dexed output from the
public-domain fixture. Aquarium candidate rendering and `dexed-py` reference
public-domain fixture. AquaSynth candidate rendering and `dexed-py` reference
rendering exist, and `analog1.syx` voice 13 has the first thresholded parity
rebuild. The next pressure is a harder voice that actually needs audible
operator routing rather than a sine-like behavioral match. Staged operator
@ -103,7 +103,7 @@ 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:
Pressure on AquaSynth DSL:
- Additive harmonic banks.
- Non-ADSR envelope curves.
@ -131,7 +131,7 @@ First deliverable:
The upstream ZynAddSubFX engine source is pinned as `external/zynaddsubfx`
for parity tests, currently at `3ab608c432996ba4d582176572c0b0f82328c825`.
That checkout is GPL reference machinery, not Aquarium implementation. Do not
That checkout is GPL reference machinery, not AquaSynth implementation. Do not
vendor the upstream Zyn instrument-bank submodule until its preset
license/provenance is explicit enough for this repo. Use project-authored
fixtures for parser shape and only promote external fixtures with clean source
@ -144,7 +144,7 @@ PAD, additive banks, or formant motion.
The first language-golf response is `layer`: a named layer declaration owns
engine tags, optional MIDI key range metadata, default gain, and an effect-send
label while lowering to ordinary Aquarium voices. It is intentionally only an
label while lowering to ordinary AquaSynth voices. It is intentionally only an
ownership/routing scaffold for now; it does not add PAD synthesis, free
envelopes, or effect buses by stealth.
@ -184,7 +184,7 @@ PAD engine parity across all harmonic profiles, randomness controls, or
multi-table pitch-zone behavior. `root`, `spread`, and `partials` are
authoring-time table shape, not runtime controls.
The canonical Aquarium spelling for richer spectral-table shaping is neutral:
The canonical AquaSynth spelling for richer spectral-table shaping is neutral:
`pad_mode`, `pad_bandwidth`, `pad_bwscale`, `pad_profile`, and `pad_position`.
The Zyn importer may populate those fields from Zyn's PAD controls, and the
parser still accepts older `zyn_*` names in generated parity artifacts, but
@ -204,7 +204,7 @@ 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:
Pressure on AquaSynth DSL:
- General signal graph expression.
- Multichannel routing.
@ -219,7 +219,7 @@ First deliverable:
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
- Use Faust references to decide whether AquaSynth needs a lower-level graph DSL
beneath the current instrument patch surface.
Sources:
@ -235,7 +235,7 @@ Sources:
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:
Pressure on AquaSynth DSL:
- Multi-scene patch structure.
- Multiple oscillator algorithms.
@ -256,10 +256,10 @@ 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
code. It is the right target once AquaSynth needs to prove arbitrary modular graph
expression.
Pressure on Aquarium DSL:
Pressure on AquaSynth DSL:
- Module graph representation.
- Cables as first-class edges.
@ -288,7 +288,7 @@ public sealed record ReferencePatch(
ReferenceSource Source,
IReadOnlyList<ReferenceFeature> Features,
IReadOnlyList<PatchParameter> Parameters,
string? AquariumScript);
string? AquaSynthScript);
public sealed record ReferenceSource(
string Kind,
@ -310,8 +310,8 @@ public sealed record PatchParameter(
```
This keeps source authority and runtime control authority visible. A reference
can be "known target, not yet translated" without pretending it is an Aquarium
patch, and an Aquarium patch can say which values callers may vary without
can be "known target, not yet translated" without pretending it is an AquaSynth
patch, and an AquaSynth patch can say which values callers may vary without
changing graph shape.
## Runtime Parameter Surface
@ -319,10 +319,10 @@ changing graph shape.
Faust supports this directly through UI controls such as `hslider`, `vslider`,
`nentry`, `button`, and `checkbox`. Hosted DSP code can expose a parameter map
and update control zones with `setParamValue`/`getParamValue` style APIs, so
Aquarium should compile once for a graph shape and then push parameter values at
AquaSynth should compile once for a graph shape and then push parameter values at
runtime.
Aquarium DSL needs an explicit parameter declaration surface, likely separate
AquaSynth DSL needs an explicit parameter declaration surface, likely separate
from modulation buses:
```text
@ -346,7 +346,7 @@ Lowering rules:
1. Create reference model contracts, parameter contracts, and tests.
2. Add explicit patch parameter declarations and Faust parameter emission.
3. Add DX7 SysEx parser and feature extraction.
4. Add two DX7-derived Aquarium DSL rebuilds with exposed macro controls.
4. Add two DX7-derived AquaSynth DSL rebuilds with exposed macro controls.
5. Add audio fixture workflow for rendered references.
6. Add Zyn XML feature extraction.
7. Add Faust reference catalog entries and compile checks.
@ -361,7 +361,7 @@ Lowering rules:
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
- Do not use Faust as a hidden escape hatch for AquaSynth patches. If a reference
requires raw Faust, the DSL is missing an abstraction or the reference belongs
in a lower-level graph layer.
@ -370,5 +370,5 @@ Lowering rules:
- 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
- AquaSynth can keep using readable reference patches while the synth library
grows toward serious DSP graph expression.

View file

@ -2,13 +2,13 @@
## Objective
Find the ZynAddSubFX instruments most likely to break Aquarium's current patch
Find the ZynAddSubFX instruments most likely to break AquaSynth's current patch
language before spending time on syntax polish.
This is a feature-pressure survey, not an audio parity claim. The upstream Zyn
instrument bank is GPL reference feedstock through the pinned Zyn submodule
tree. It is development/test material only: useful for reports, parity renders,
and pressure selection, never package content or Aquarium patch-library stock.
and pressure selection, never package content or AquaSynth patch-library stock.
## Source Checked
@ -53,7 +53,7 @@ GPL PAD batch through the real Zyn oracle when the renderer is built:
Each render lands in a per-synth timestamped folder under
`artifacts/parity/zyn-upstream-pad-fixtures/`, with Zyn reference WAVs, source
table WAVs, `report.txt`, and `candidate.aqua`. The candidate is now generated
per preset by `ZynInstrumentReader.RebuildFirstPadAsAquariumScript`: first
per preset by `ZynInstrumentReader.RebuildFirstPadAsAquaSynthScript`: first
enabled PAD kit item, OSCIL harmonic magnitudes, oracle-reported table
basefreq, and basic volume/envelope scaffolding. The report names missing
semantics such as Zyn harmonic profile shaping, bandwidth/profile behavior, and
@ -62,7 +62,7 @@ library candidates.
## Pressure Conclusions
- **Named layers are real.** Anonymous Aquarium voices can sketch small Zyn
- **Named layers are real.** Anonymous AquaSynth voices can sketch small Zyn
layers, but drum kits and clonewheel-style instruments need stable layer names,
key ranges, engine identity, and per-layer routing.
- **Free envelopes are not optional forever.** Zyn's worst cases use dozens of
@ -86,7 +86,7 @@ next abstractions risk becoming loose fields on anonymous voices.
## First Responses
Aquarium now has a minimal `layer` command:
AquaSynth now has a minimal `layer` command:
```text
layer name=pad_low engine=pad min_key=36 max_key=84 gain=.07

View file

@ -1,8 +1,8 @@
# Aquarium Patch Library
# AquaSynth Patch Library
This folder is the development patch library for Aquarium `.aqua` scripts. It is where reusable stock, reference rebuilds, calibration probes, and inspiration patches live as files instead of being trapped inside C# string constants or ignored render artifacts.
This folder is the development patch library for AquaSynth `.aqua` scripts. It is where reusable stock, reference rebuilds, calibration probes, and inspiration patches live as files instead of being trapped inside C# string constants or ignored render artifacts.
The library is intentionally outside `src/` and is not part of the shipped `AquariumSynth.Dsl` NuGet package. Treat it as repo-owned source material for future synth consumers, examples, and parity work.
The library is intentionally outside `src/` and is not part of the shipped `AquaSynth.Dsl` NuGet package. Treat it as repo-owned source material for future synth consumers, examples, and parity work.
## Layout

View file

@ -1,6 +1,6 @@
version: 1
purpose: Development patch library for Aquarium DSL examples, parity rebuilds, and stock inspiration.
package_boundary: Not included in AquariumSynth.Dsl.nupkg unless explicitly promoted later.
version: 1
purpose: Development patch library for AquaSynth DSL examples, parity rebuilds, and stock inspiration.
package_boundary: Not included in AquaSynth.Dsl.nupkg unless explicitly promoted later.
entries:
- path: patches/808/clap.aqua
family: 808

View file

@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>AquariumSynth.Dsl</PackageId>
<Version>0.1.0-aquarium.1</Version>
<PackageId>AquaSynth.Dsl</PackageId>
<Version>0.1.0-aquasynth.1</Version>
<Authors>GameCult</Authors>
<Company>GameCult</Company>
<Description>Aquarium synth patch DSL model, parser, analysis helpers, and Faust source emitter.</Description>
<Description>AquaSynth patch DSL model, parser, analysis helpers, and Faust source emitter.</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/GameCult/AquariumSynthCSharp</RepositoryUrl>
<RepositoryUrl>https://github.com/GameCult/AquaSynth</RepositoryUrl>
</PropertyGroup>
<ItemGroup>

View file

@ -1,6 +1,6 @@
using System.Numerics;
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public sealed record AudioAnalysisConfig(
float SampleRate = 44100,

View file

@ -1,4 +1,4 @@
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public static class BuiltInScripts
{

View file

@ -1,6 +1,6 @@
using System.Security.Cryptography;
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public enum Dx7FrequencyMode
{
@ -316,7 +316,7 @@ public static class Dx7SysEx
return new Dx7RateLevelEnvelopeApproximation(
new RateLevelEnvelope(r1, l1, r2, l2, r3, l3, r4, l4),
Math.Max(r1 + r2 + r3, 0.02f),
"Approximation: DX7 EG uses four rate/level segments; Aquarium staged envelopes preserve the intermediate target levels.");
"Approximation: DX7 EG uses four rate/level segments; AquaSynth staged envelopes preserve the intermediate target levels.");
}
public static Dx7RateLevelEnvelopeApproximation ApproximateAppliedRateLevelEnvelope(
@ -359,7 +359,7 @@ public static class Dx7SysEx
RateLevelCurve.Exponential,
RateLevelCurve.Exponential),
gateSeconds,
"Approximation: DX7 applied EG gain sampled per 64-sample block and normalized for Aquarium operator-level scaling.");
"Approximation: DX7 applied EG gain sampled per 64-sample block and normalized for AquaSynth operator-level scaling.");
}
public static IReadOnlyList<Dx7EnvelopeTracePoint> TraceEnvelope(

View file

@ -2,9 +2,9 @@ using System.Diagnostics;
using System.Globalization;
using System.Text;
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public sealed record FaustExportOptions(string Name = "aquarium_patch", bool Stereo = false);
public sealed record FaustExportOptions(string Name = "aquasynth_patch", bool Stereo = false);
public sealed record FaustExport(string Source, IReadOnlyList<string> Warnings);
@ -655,7 +655,7 @@ public static class FaustCompiler
faustPath ??= FindFaust();
if (faustPath is null) return null;
var sourcePath = Path.Combine(Path.GetTempPath(), $"aquarium-synth-{Guid.NewGuid():N}.dsp");
var sourcePath = Path.Combine(Path.GetTempPath(), $"aquasynth-{Guid.NewGuid():N}.dsp");
await File.WriteAllTextAsync(sourcePath, source, cancellationToken);
try
{
@ -691,7 +691,7 @@ public static class FaustCompiler
if (faustPath is null) return null;
var frames = Math.Max(1, (int)MathF.Round(options.SampleRate * Math.Max(options.DurationSeconds, 1f / options.SampleRate)));
var tempDir = Path.Combine(Path.GetTempPath(), $"aquarium-synth-render-{Guid.NewGuid():N}");
var tempDir = Path.Combine(Path.GetTempPath(), $"aquasynth-render-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
try
{

View file

@ -1,4 +1,4 @@
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public enum Waveform
{
@ -309,7 +309,7 @@ public sealed record ReferencePatch(
ReferenceSource Source,
IReadOnlyList<ReferenceFeature> Features,
IReadOnlyList<PatchParameter> Parameters,
string? AquariumScript = null);
string? AquaSynthScript = null);
public sealed record Repeat(float IntervalSeconds);

View file

@ -1,6 +1,6 @@
using System.Numerics;
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
internal static class PadSynthWaveform
{

View file

@ -1,6 +1,6 @@
using System.Globalization;
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public sealed class PatchScriptException(int line, string message) : Exception($"line {line}: {message}")
{

View file

@ -1,4 +1,4 @@
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
internal readonly record struct PatchScriptStatement(string Text, int Line);

View file

@ -1,8 +1,8 @@
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public static class Presets
{
public static SynthPatch AquariumPluck()
public static SynthPatch AquaSynthPluck()
{
var envelope = new Envelope(0.002f, 0, 0.7246377f, 0.43f);
return new SynthPatch
@ -18,7 +18,7 @@ public static class Presets
};
}
public static SynthPatch AquariumHeartbeat()
public static SynthPatch AquaSynthHeartbeat()
{
var envelope = new Envelope(0.004f, 0, 0.617284f, 0.2f);
return new SynthPatch
@ -33,7 +33,7 @@ public static class Presets
};
}
public static SynthPatch AquariumVoice()
public static SynthPatch AquaSynthVoice()
{
var voice = Simple(new Oscillator(Waveform.Triangle, 220), new Envelope(0.018f, 0, 0.9259259f, 0.28f), 0.34f, 0.1944f) with
{

View file

@ -1,4 +1,4 @@
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public sealed record ReferenceRebuild(
string Id,
@ -15,7 +15,7 @@ public static class ReferenceRebuildCatalog
public static readonly IReadOnlyList<ReferenceRebuild> ZynRebuilds =
[
new(
"zyn/project-additive-lead/aquarium",
"zyn/project-additive-lead/aquasynth",
"zynaddsubfx",
"Project additive lead",
"zyn/project/additive-lead",
@ -24,17 +24,17 @@ public static class ReferenceRebuildCatalog
new("engine_add", "1", "Represented as additive sine partials under named layer authority."),
new("named_layers", "body,shine", "Layer declarations group partial voices under reusable ADD engine intent."),
new("additive_harmonic_bank", "2 banks / 4 partials", "Harmonic-bank syntax records authored ADD partial sets before lowering to ordinary voices."),
new("partial_voice_count", "4", "The additive shape lowers to four Aquarium partial voices."),
new("partial_voice_count", "4", "The additive shape lowers to four AquaSynth partial voices."),
new("envelope_count", "2", "Amplitude and pitch contour pressure maps to ADSR plus vibrato in the current patch surface."),
new("lfo_count", "1", "The fixture's amplitude/frequency motion is represented as patch-level vibrato/macro motion.")
],
[
new("zyn_addsynth_oscillator_detail", "Zyn ADDsynth harmonic phase/bandwidth details", "Aquarium records harmonic ratios and gains, but not Zyn's full ADDsynth oscillator table semantics."),
new("zyn_free_envelope_exactness", "Zyn envelope parameter semantics", "The rebuild uses Aquarium ADSR/motion controls rather than Zyn's exact envelope timing model.")
new("zyn_addsynth_oscillator_detail", "Zyn ADDsynth harmonic phase/bandwidth details", "AquaSynth records harmonic ratios and gains, but not Zyn's full ADDsynth oscillator table semantics."),
new("zyn_free_envelope_exactness", "Zyn envelope parameter semantics", "The rebuild uses AquaSynth ADSR/motion controls rather than Zyn's exact envelope timing model.")
],
"This is the easy Zyn pressure case: Aquarium can now say the sound as named harmonic banks while keeping the emitted voice graph plain enough to inspect."),
"This is the easy Zyn pressure case: AquaSynth can now say the sound as named harmonic banks while keeping the emitted voice graph plain enough to inspect."),
new(
"zyn/project-pad-texture/aquarium",
"zyn/project-pad-texture/aquasynth",
"zynaddsubfx",
"Project PAD texture",
"zyn/project/pad-texture",
@ -49,12 +49,12 @@ public static class ReferenceRebuildCatalog
new("slow_modulation", "patch_lfo", "The rebuild adds slow patch modulation because PAD texture wants motion even though the minimal fixture only marks PAD/filter/free-envelope pressure.")
],
[
new("padsynth_wavetable_exactness", "Full Zyn PAD engine behavior", "Aquarium now uses frequency-domain spreading, deterministic random phase, and one inverse FFT, but does not cover every Zyn harmonic profile, randomness control, or pitch-zone table behavior."),
new("free_envelope_shape", "arbitrary Zyn envelope points", "Aquarium has staged voice envelopes now, but not arbitrary point/free-mode Zyn envelope curves.")
new("padsynth_wavetable_exactness", "Full Zyn PAD engine behavior", "AquaSynth now uses frequency-domain spreading, deterministic random phase, and one inverse FFT, but does not cover every Zyn harmonic profile, randomness control, or pitch-zone table behavior."),
new("free_envelope_shape", "arbitrary Zyn envelope points", "AquaSynth has staged voice envelopes now, but not arbitrary point/free-mode Zyn envelope curves.")
],
"This is pressure, not a claim of full Zyn PAD parity. Aquarium can now state a layered FFT spectral cloud, while exact Zyn PAD profile coverage remains separate pressure."),
"This is pressure, not a claim of full Zyn PAD parity. AquaSynth can now state a layered FFT spectral cloud, while exact Zyn PAD profile coverage remains separate pressure."),
new(
"zyn/project-vocal-layer/aquarium",
"zyn/project-vocal-layer/aquasynth",
"zynaddsubfx",
"Project vocal layer",
"zyn/project/vocal-layer",
@ -62,15 +62,15 @@ public static class ReferenceRebuildCatalog
[
new("engine_add", "1", "The air/formant layer is represented as a formant-filtered oscillator voice."),
new("engine_sub", "1", "The body layer is represented as a lower triangle voice."),
new("layered_instrument", "yes", "Multiple Zyn kit items map to multiple Aquarium voices."),
new("layered_instrument", "yes", "Multiple Zyn kit items map to multiple AquaSynth voices."),
new("named_layers", "air,body,breath", "Layer declarations preserve source identity before voice expansion."),
new("voice_rate_level_envelope", "3", "Each vocal layer uses a staged rate/level voice envelope for asymmetric onset and release shape."),
new("formant_filter_count", "1", "Represented with Aquarium formant filters and a vowel macro."),
new("formant_filter_count", "1", "Represented with AquaSynth formant filters and a vowel macro."),
new("effect_count", "1", "Approximated with local drive/soft clipping rather than a Zyn effect slot.")
],
[
new("kit_item_authority", "Zyn per-kit-item engine/effect routing", "Aquarium has layered voices but no named kit-item/layer contract with per-layer effect sends."),
new("formant_motion", "Zyn formant/vowel morph details", "Aquarium can mix static formants, but richer vowel morphing remains pressure.")
new("kit_item_authority", "Zyn per-kit-item engine/effect routing", "AquaSynth has layered voices but no named kit-item/layer contract with per-layer effect sends."),
new("formant_motion", "Zyn formant/vowel morph details", "AquaSynth can mix static formants, but richer vowel morphing remains pressure.")
],
"This rebuild keeps the useful part: layered source plus formant intent. It also exposes that repeated Zyn-style kit targets will need explicit layer naming and routing rather than anonymous voice piles.")
];
@ -78,55 +78,55 @@ public static class ReferenceRebuildCatalog
public static readonly IReadOnlyList<ReferenceRebuild> Dx7Rebuilds =
[
new(
"dx7/algo32-additive-organ/aquarium",
"dx7/algo32-additive-organ/aquasynth",
"dx7",
"Algorithm 32 additive organ",
"dx7/algo32-additive-organ",
BuiltInScripts.Dx7StyleAlgorithm32AdditiveOrgan,
[
new("carrier_operators", "1,2,3,4,5,6", "Six independent carriers map cleanly to six Aquarium voices."),
new("carrier_operators", "1,2,3,4,5,6", "Six independent carriers map cleanly to six AquaSynth voices."),
new("modulation_edge_count", "0", "No operator-to-operator modulation is needed for the additive shape."),
new("self_feedback_operators", "6", "Approximated with fold/drive on the highest partial."),
new("operator_envelope_approximation", "DX7 EG -> ADSR", "DX7 four-rate/four-level envelopes can now be lowered to labeled ADSR approximations.")
],
[
new("operator_envelope_exactness", "per-operator four-stage DX7 EG", "Aquarium has an ADSR approximation helper, not exact DX7 rate/level envelope execution."),
new("output_compensation", "algorithm ROM COM", "Aquarium voice gains are explicit and not derived from DX7 COM scaling.")
new("operator_envelope_exactness", "per-operator four-stage DX7 EG", "AquaSynth has an ADSR approximation helper, not exact DX7 rate/level envelope execution."),
new("output_compensation", "algorithm ROM COM", "AquaSynth voice gains are explicit and not derived from DX7 COM scaling.")
],
"This is the honest easy case: DX7 algorithm 32 is mostly additive synthesis, so the current voice DSL can represent the carrier layout without pretending to own a full operator graph."),
new(
"dx7/algo8-bright-pair/aquarium",
"dx7/algo8-bright-pair/aquasynth",
"dx7",
"Algorithm 8 bright pair",
"dx7/algo8-bright-pair",
BuiltInScripts.Dx7StyleAlgorithm8BrightPair,
[
new("carrier_operators", "1,3", "Represented as carriers in a first-class Aquarium operator graph."),
new("carrier_operators", "1,3", "Represented as carriers in a first-class AquaSynth operator graph."),
new("modulation_edges", "6->5,4+5->3,2->1", "Represented as explicit operator edges in the graph."),
new("feedback_sources", "4", "Represented as operator-local feedback approximation on operator 4."),
new("runtime_macros", "brightness,strike", "Field-site parameters expose useful sound controls without recompilation."),
new("operator_envelope_approximation", "DX7 EG -> ADSR", "DX7 four-rate/four-level envelopes can now be lowered to labeled ADSR approximations.")
],
[
new("dx7_feedback_register", "DX7 feedback scaling/register details", "Aquarium operator feedback is now renderable as a one-sample delayed self-reference, but not calibrated to exact DX7 feedback scaling."),
new("operator_envelope_exactness", "independent DX7 rate/level envelopes", "Aquarium has per-operator ADSR approximation, not exact DX7 EG execution.")
new("dx7_feedback_register", "DX7 feedback scaling/register details", "AquaSynth operator feedback is now renderable as a one-sample delayed self-reference, but not calibrated to exact DX7 feedback scaling."),
new("operator_envelope_exactness", "independent DX7 rate/level envelopes", "AquaSynth has per-operator ADSR approximation, not exact DX7 EG execution.")
],
"This patch is deliberately a pressure test. The new operator graph owns the topology; exact DX7 envelope and feedback-register behavior remain pressure for later refinement."),
new(
"dx7/public-domain-mc-mm-5-3/aquarium",
"dx7/public-domain-mc-mm-5-3/aquasynth",
"dx7",
"Public-domain MC-MM 5-3",
"dx7/public-domain/analog1/13",
BuiltInScripts.Dx7StylePublicDomainMcMm53,
[
new("reference_fixture", "analog1.syx#13", "Public-domain DX7 SysEx fixture from Musical Artifacts artifact 152."),
new("audio_parity", "score>=0.75 log_mel<0.12 envelope<0.10", "First rendered DX7 parity target using dexed-py reference audio and Faust-rendered Aquarium audio."),
new("audio_parity", "score>=0.75 log_mel<0.12 envelope<0.10", "First rendered DX7 parity target using dexed-py reference audio and Faust-rendered AquaSynth audio."),
new("dominant_pitch", "654.0639Hz", "The rendered reference behaves as a short C3-derived 5x-ratio sine-like tone."),
new("operator_envelope_approximation", "DX7 EG -> ADSR", "ADSR timing is fitted to the rendered reference envelope.")
],
[
new("operator_topology_exactness", "2->1 FM pair", "The first parity candidate matches the rendered behavior as a sine-like tone; it does not claim exact DX7 operator-level execution."),
new("operator_envelope_exactness", "DX7 rate/level EG", "The candidate uses Aquarium ADSR timing matched to rendered audio.")
new("operator_envelope_exactness", "DX7 rate/level EG", "The candidate uses AquaSynth ADSR timing matched to rendered audio.")
],
"This is the first thresholded audio parity rung. It proves the render/compare loop on a lawful fixture without pretending the DX7 internals are solved.")
];

View file

@ -1,4 +1,4 @@
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public sealed record PatchScriptMetrics(
int ByteCount,

View file

@ -1,4 +1,4 @@
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public sealed record SfxrParams
{

View file

@ -5,7 +5,7 @@ using System.Security.Cryptography;
using System.Text;
using System.Xml.Linq;
namespace AquariumSynth.Dsl;
namespace AquaSynth.Dsl;
public sealed record ZynInstrument(
string Name,
@ -117,25 +117,25 @@ public static class ZynInstrumentReader
public static ReferenceSource SourceForBytes(string uri, string license, ReadOnlySpan<byte> bytes, string notes = "") =>
new("zyn-xiz", uri, license, Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant(), notes);
public static ZynPadRebuild RebuildFirstPadAsAquariumScript(
public static ZynPadRebuild RebuildFirstPadAsAquaSynthScript(
string path,
float tableRootFrequencyHz,
float playbackFrequencyHz = 261.6256f) =>
RebuildFirstPadAsAquariumScript(File.ReadAllBytes(path), tableRootFrequencyHz, playbackFrequencyHz);
RebuildFirstPadAsAquaSynthScript(File.ReadAllBytes(path), tableRootFrequencyHz, playbackFrequencyHz);
public static ZynPadRebuild RebuildEnabledPadsAsAquariumScript(
public static ZynPadRebuild RebuildEnabledPadsAsAquaSynthScript(
string path,
IReadOnlyDictionary<int, float> tableRootFrequencyHzByKitItem,
float playbackFrequencyHz = 261.6256f) =>
RebuildEnabledPadsAsAquariumScript(File.ReadAllBytes(path), tableRootFrequencyHzByKitItem, playbackFrequencyHz);
RebuildEnabledPadsAsAquaSynthScript(File.ReadAllBytes(path), tableRootFrequencyHzByKitItem, playbackFrequencyHz);
public static ZynPadRebuild RebuildEnabledPadsAsAquariumScript(
public static ZynPadRebuild RebuildEnabledPadsAsAquaSynthScript(
string path,
IReadOnlyDictionary<int, ZynPadTableRoot> tableRootFrequencyHzByKitItem,
float playbackFrequencyHz = 261.6256f) =>
RebuildEnabledPadsAsAquariumScript(File.ReadAllBytes(path), tableRootFrequencyHzByKitItem, playbackFrequencyHz);
RebuildEnabledPadsAsAquaSynthScript(File.ReadAllBytes(path), tableRootFrequencyHzByKitItem, playbackFrequencyHz);
public static ZynPadRebuild RebuildFirstPadAsAquariumScript(
public static ZynPadRebuild RebuildFirstPadAsAquaSynthScript(
ReadOnlySpan<byte> bytes,
float tableRootFrequencyHz,
float playbackFrequencyHz = 261.6256f)
@ -150,19 +150,19 @@ public static class ZynInstrumentReader
.Descendants("INSTRUMENT_KIT_ITEM")
.FirstOrDefault(IsEnabledPadItem)
?? throw new ArgumentException("Zyn instrument XML has no enabled PAD kit item");
return RebuildPadItemsAsAquariumScript(
return RebuildPadItemsAsAquaSynthScript(
document,
[item],
new Dictionary<int, ZynPadTableRoot> { [IntAttribute(item, "id")] = new(tableRootFrequencyHz) },
playbackFrequencyHz);
}
public static ZynPadRebuild RebuildEnabledPadsAsAquariumScript(
public static ZynPadRebuild RebuildEnabledPadsAsAquaSynthScript(
ReadOnlySpan<byte> bytes,
IReadOnlyDictionary<int, float> tableRootFrequencyHzByKitItem,
float playbackFrequencyHz = 261.6256f)
{
return RebuildEnabledPadsAsAquariumScript(
return RebuildEnabledPadsAsAquaSynthScript(
bytes,
tableRootFrequencyHzByKitItem.ToDictionary(
pair => pair.Key,
@ -170,7 +170,7 @@ public static class ZynInstrumentReader
playbackFrequencyHz);
}
public static ZynPadRebuild RebuildEnabledPadsAsAquariumScript(
public static ZynPadRebuild RebuildEnabledPadsAsAquaSynthScript(
ReadOnlySpan<byte> bytes,
IReadOnlyDictionary<int, ZynPadTableRoot> tableRootFrequencyHzByKitItem,
float playbackFrequencyHz = 261.6256f)
@ -190,10 +190,10 @@ public static class ZynInstrumentReader
throw new ArgumentException("Zyn instrument XML has no enabled PAD kit item");
}
return RebuildPadItemsAsAquariumScript(document, items, tableRootFrequencyHzByKitItem, playbackFrequencyHz);
return RebuildPadItemsAsAquaSynthScript(document, items, tableRootFrequencyHzByKitItem, playbackFrequencyHz);
}
private static ZynPadRebuild RebuildPadItemsAsAquariumScript(
private static ZynPadRebuild RebuildPadItemsAsAquaSynthScript(
XDocument document,
IReadOnlyList<XElement> items,
IReadOnlyDictionary<int, ZynPadTableRoot> tableRootFrequencyHzByKitItem,
@ -234,7 +234,7 @@ public static class ZynInstrumentReader
throw new ArgumentException($"Missing PAD table root for kit item {kitId}.", nameof(tableRootFrequencyHzByKitItem));
}
var tableRootFrequencyHz = ZynPadRootForAquarium(item, tableRoot);
var tableRootFrequencyHz = ZynPadRootForAquaSynth(item, tableRoot);
if (tableRootFrequencyHz <= 0)
{
throw new ArgumentException($"Invalid PAD table root for kit item {kitId}.", nameof(tableRootFrequencyHzByKitItem));
@ -255,7 +255,7 @@ public static class ZynInstrumentReader
return new ZynPadRebuild(name, items.Count == 1 ? IntAttribute(items[0], "id") : -1, script.ToString(), matched, missing);
}
private static float ZynPadRootForAquarium(XElement item, ZynPadTableRoot tableRoot)
private static float ZynPadRootForAquaSynth(XElement item, ZynPadTableRoot tableRoot)
{
var oscillator = item
.Element("PAD_SYNTH_PARAMETERS")
@ -319,7 +319,7 @@ public static class ZynInstrumentReader
var partialText = string.Join(",", harmonics.Select(partial => $"{F(partial.Ratio)}:{F(partial.Gain)}"));
var featurePrefix = includeKitSuffix ? $"pad_kit_{kitId}_" : "pad_";
matched.Add(new($"{featurePrefix}oscillator_harmonics", harmonics.Count.ToString(CultureInfo.InvariantCulture), "Mapped OSCIL/HARMONICS magnitudes into Aquarium spectrum partials."));
matched.Add(new($"{featurePrefix}oscillator_harmonics", harmonics.Count.ToString(CultureInfo.InvariantCulture), "Mapped OSCIL/HARMONICS magnitudes into AquaSynth spectrum partials."));
matched.Add(new($"{featurePrefix}oscillator_base_function", IntParam(oscillator, "base_function", 0).ToString(CultureInfo.InvariantCulture), "Expanded Zyn OscilGen base function into harmonic partials before PAD synthesis."));
if (IntParam(oscillator, "base_function_modulation", 0) != 0)
{
@ -331,30 +331,30 @@ public static class ZynInstrumentReader
}
matched.Add(new($"{featurePrefix}oscillator_spectrum_adjust", $"{IntParam(oscillator, "spectrum_adjust_type", 0)}:{IntParam(oscillator, "spectrum_adjust_par", 64)}", "Applied Zyn OscilGen spectrum adjustment to generated harmonic partials."));
matched.Add(new($"{featurePrefix}table_root", F(tableRootFrequencyHz), "Root frequency comes from the Zyn oracle generated sample basefreq."));
matched.Add(new($"{featurePrefix}note_frequency", F(noteFrequencyHz), "Mapped Zyn PAD coarse/fine detune into Aquarium table playback frequency."));
matched.Add(new($"{featurePrefix}volume", volume.ToString(CultureInfo.InvariantCulture), "Mapped PAD amplitude volume into Aquarium layer gain."));
matched.Add(new($"{featurePrefix}bandwidth", bandwidth.ToString(CultureInfo.InvariantCulture), "Mapped Zyn PAD bandwidth into Aquarium spectral table generation."));
matched.Add(new($"{featurePrefix}note_frequency", F(noteFrequencyHz), "Mapped Zyn PAD coarse/fine detune into AquaSynth table playback frequency."));
matched.Add(new($"{featurePrefix}volume", volume.ToString(CultureInfo.InvariantCulture), "Mapped PAD amplitude volume into AquaSynth layer gain."));
matched.Add(new($"{featurePrefix}bandwidth", bandwidth.ToString(CultureInfo.InvariantCulture), "Mapped Zyn PAD bandwidth into AquaSynth spectral table generation."));
matched.Add(new($"{featurePrefix}bandwidth_scale", bandwidthScale.ToString(CultureInfo.InvariantCulture), "Mapped Zyn PAD bandwidth scaling exponent selection."));
matched.Add(new($"{featurePrefix}harmonic_profile", profile, "Mapped Zyn PAD harmonic profile shape into Aquarium spectral table generation."));
matched.Add(new($"{featurePrefix}harmonic_position", position, "Mapped Zyn PAD harmonic position warp into Aquarium spectral table generation."));
matched.Add(new($"{featurePrefix}harmonic_profile", profile, "Mapped Zyn PAD harmonic profile shape into AquaSynth spectral table generation."));
matched.Add(new($"{featurePrefix}harmonic_position", position, "Mapped Zyn PAD harmonic position warp into AquaSynth spectral table generation."));
if (lowPass < 1)
{
matched.Add(new($"{featurePrefix}filter_lpf", F(lowPass), "Mapped static Zyn PAD global low-pass frequency into Aquarium layer filtering."));
matched.Add(new($"{featurePrefix}filter_lpf_q", F(lowPassQ), "Mapped Zyn analog low-pass Q into Aquarium filter damping."));
matched.Add(new($"{featurePrefix}filter_lpf_order", lowPassOrder.ToString(CultureInfo.InvariantCulture), "Mapped Zyn analog LP1/LP2 and stage count into Aquarium low-pass order."));
matched.Add(new($"{featurePrefix}filter_lpf", F(lowPass), "Mapped static Zyn PAD global low-pass frequency into AquaSynth layer filtering."));
matched.Add(new($"{featurePrefix}filter_lpf_q", F(lowPassQ), "Mapped Zyn analog low-pass Q into AquaSynth filter damping."));
matched.Add(new($"{featurePrefix}filter_lpf_order", lowPassOrder.ToString(CultureInfo.InvariantCulture), "Mapped Zyn analog LP1/LP2 and stage count into AquaSynth low-pass order."));
}
if (highPass > 0)
{
matched.Add(new($"{featurePrefix}filter_hpf", F(highPass), "Mapped static Zyn PAD global high-pass frequency into Aquarium layer filtering."));
matched.Add(new($"{featurePrefix}filter_hpf_order", highPassOrder.ToString(CultureInfo.InvariantCulture), "Mapped Zyn analog HP1/HP2 and stage count into Aquarium high-pass order."));
matched.Add(new($"{featurePrefix}filter_hpf", F(highPass), "Mapped static Zyn PAD global high-pass frequency into AquaSynth layer filtering."));
matched.Add(new($"{featurePrefix}filter_hpf_order", highPassOrder.ToString(CultureInfo.InvariantCulture), "Mapped Zyn analog HP1/HP2 and stage count into AquaSynth high-pass order."));
}
if (lowPassEnvelope is not null)
{
matched.Add(new($"{featurePrefix}filter_lpf_envelope", "ratelevel", "Mapped Zyn PAD filter envelope octave offsets into Aquarium low-pass cutoff motion."));
matched.Add(new($"{featurePrefix}filter_lpf_envelope", "ratelevel", "Mapped Zyn PAD filter envelope octave offsets into AquaSynth low-pass cutoff motion."));
}
if (highPassEnvelope is not null)
{
matched.Add(new($"{featurePrefix}filter_hpf_envelope", "ratelevel", "Mapped Zyn PAD filter envelope octave offsets into Aquarium high-pass cutoff motion."));
matched.Add(new($"{featurePrefix}filter_hpf_envelope", "ratelevel", "Mapped Zyn PAD filter envelope octave offsets into AquaSynth high-pass cutoff motion."));
}
if (filter?.Element("FILTER_LFO") is { } lfo && IntParam(lfo, "intensity", 0) != 0)
{

View file

@ -1,92 +1,93 @@
{"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"}
{"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":"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":"AquaSynth 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"}
{"kind":"runtime_patch_parameters","lesson":"Synth patches must expose expressive runtime parameters. Faust supports this through UI controls and hosted parameter APIs, so Aquarium should compile a graph once and vary declared parameter values without recompilation. Recompile only for structural graph changes.","source":"user asked whether Faust can vary one number without recompiling"}
{"kind":"synth_test_ownership","lesson":"AquariumSynthCSharp owns reference-patch correctness. Aquarium should only smoke-test the engine synth host and bundled Faust boundary; SFXR/BFXR/reference parity belongs here so the engine can keep moving while the synth library is under active surgery.","source":"user questioned synth tests living in Aquarium"}
{"kind":"package_boundary","lesson":"Aquarium consumes AquariumSynth.Dsl through a pinned package, not a live project reference. Breaking changes in AquariumSynthCSharp are allowed during synth construction as long as a new package version is packed and Aquarium is updated deliberately when the boundary should move.","source":"user wanted Aquarium builds insulated from synth-agent work"}
{"kind":"runtime_patch_parameters","lesson":"Aquarium patch scripts now have first-class `param` declarations and patch/reference model contracts. Faust emission exposes declared parameters as smoothed hslider controls, but target binding is intentionally still absent and warned about; do not claim parameters shape sound until binding is designed.","source":"parameter contract implementation on 2026-05-13"}
{"kind":"runtime_patch_parameters","lesson":"Aquarium parameter binding uses Option B: numeric field values may reference declared parameters with `@/path`, producing exact field bindings such as `/voices/0/filter/lpf`. The field owns the meaning; the parameter owns the runtime value. Avoid target-routing parameter semantics unless a new authority is deliberately mapped.","source":"user rejected target binding as a landmine; field-site binding implemented on 2026-05-13"}
{"kind":"reference_synth_roadmap","lesson":"AquaSynth 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 AquaSynth DSL"}
{"kind":"runtime_patch_parameters","lesson":"Synth patches must expose expressive runtime parameters. Faust supports this through UI controls and hosted parameter APIs, so AquaSynth should compile a graph once and vary declared parameter values without recompilation. Recompile only for structural graph changes.","source":"user asked whether Faust can vary one number without recompiling"}
{"kind":"synth_test_ownership","lesson":"AquaSynth owns reference-patch correctness. AquaSynth should only smoke-test the engine synth host and bundled Faust boundary; SFXR/BFXR/reference parity belongs here so the engine can keep moving while the synth library is under active surgery.","source":"user questioned synth tests living in AquaSynth"}
{"kind":"package_boundary","lesson":"AquaSynth consumes AquaSynth.Dsl through a pinned package, not a live project reference. Breaking changes in AquaSynth are allowed during synth construction as long as a new package version is packed and AquaSynth is updated deliberately when the boundary should move.","source":"user wanted AquaSynth builds insulated from synth-agent work"}
{"kind":"runtime_patch_parameters","lesson":"AquaSynth patch scripts now have first-class `param` declarations and patch/reference model contracts. Faust emission exposes declared parameters as smoothed hslider controls, but target binding is intentionally still absent and warned about; do not claim parameters shape sound until binding is designed.","source":"parameter contract implementation on 2026-05-13"}
{"kind":"runtime_patch_parameters","lesson":"AquaSynth parameter binding uses Option B: numeric field values may reference declared parameters with `@/path`, producing exact field bindings such as `/voices/0/filter/lpf`. The field owns the meaning; the parameter owns the runtime value. Avoid target-routing parameter semantics unless a new authority is deliberately mapped.","source":"user rejected target binding as a landmine; field-site binding implemented on 2026-05-13"}
{"kind":"dx7_reference_path","lesson":"DX7 reference elaboration starts with structural extraction, not translation. The repo now parses 155-byte voice edit buffers, 128-byte packed voices, and 4104-byte packed 32-voice SysEx banks into neutral DX7 records with checksum validation and ReferencePatch features. Next pressure point is explicit algorithm topology metadata for carriers, modulation edges, and feedback edge location.","source":"DX7 SysEx parser slice on 2026-05-13"}
{"kind":"dx7_reference_path","lesson":"DX7 algorithm topology is now derived from the reverse-engineered OPS algorithm ROM table, not hand-drawn diagrams. Topology exposes carrier operators, modulation edges, feedback-register writers, self-feedback operators, delayed-feedback targets, and raw ROM steps. Use this as pressure evidence before adding an Aquarium operator graph layer.","source":"DX7 algorithm topology slice on 2026-05-13; Ken Shirriff OPS ROM table"}
{"kind":"dx7_reference_rebuilds","lesson":"Two DX7-style Aquarium rebuilds now make the pressure explicit: algorithm 32 maps cleanly to layered additive voices, but algorithm 8 exposes missing graph structure (operator modulation edges, summed modulators, and operator-local self-feedback). Do not patch this with more Voice fields; decide whether an operator graph model owns this invariant.","source":"DX7 rebuild catalog slice on 2026-05-13"}
{"kind":"dx7_reference_path","lesson":"DX7 algorithm topology is now derived from the reverse-engineered OPS algorithm ROM table, not hand-drawn diagrams. Topology exposes carrier operators, modulation edges, feedback-register writers, self-feedback operators, delayed-feedback targets, and raw ROM steps. Use this as pressure evidence before adding an AquaSynth operator graph layer.","source":"DX7 algorithm topology slice on 2026-05-13; Ken Shirriff OPS ROM table"}
{"kind":"dx7_reference_rebuilds","lesson":"Two DX7-style AquaSynth rebuilds now make the pressure explicit: algorithm 32 maps cleanly to layered additive voices, but algorithm 8 exposes missing graph structure (operator modulation edges, summed modulators, and operator-local self-feedback). Do not patch this with more Voice fields; decide whether an operator graph model owns this invariant.","source":"DX7 rebuild catalog slice on 2026-05-13"}
{"kind":"operator_graph_model","lesson":"DX7 algorithm-8 pressure justified a first-class OperatorGraph abstraction. OperatorGraph owns FM topology: operators, modulation edges, carriers, and feedback. Do not hide this authority inside Voice fields. Remaining DX7 pressure is exact feedback-register timing and rate/level envelopes.","source":"operator graph implementation slice on 2026-05-13"}
{"kind":"operator_graph_authoring","lesson":"Operator graph authoring should use readable `operator`, `route`, and `carrier` declarations with `env=ad:...` or `env=adsr:...`. Compact `ops=`/`edges=` tuples are acceptable as importer/interchange scaffolding only, not the face of the DSL.","source":"user rejected number-soup operator syntax; readable operator graph syntax slice on 2026-05-13"}
{"kind":"note_envelope_model","lesson":"Aquarium's general envelope model is ADSR: attack, decay, sustain level, release. Note timing belongs to `Note` records: frequency, gate duration, and host/MIDI source. SFXR sustain maps to note gate duration; SFXR punch maps to lower sustain level plus voice gain compensation so ADSR peak remains implicit 1 and the held level sits below the transient.","source":"user identified SFXR sustain as note duration and Punch as transient above sustain; punch mapping correction on 2026-05-13"}
{"kind":"faust_polyphony","lesson":"Prefer Faust-managed MIDI polyphony over Aquarium graph inflation. Aquarium should emit a single voice graph with standard `freq`, `gain`, and `gate` controls plus `[midi:on][nvoices:n]`; Faust architectures own MIDI decoding and voice allocation unless a concrete target proves the boundary insufficient.","source":"Faust official MIDI/polyphony docs and user preference for Faust support on 2026-05-13"}
{"kind":"note_envelope_model","lesson":"AquaSynth's general envelope model is ADSR: attack, decay, sustain level, release. Note timing belongs to `Note` records: frequency, gate duration, and host/MIDI source. SFXR sustain maps to note gate duration; SFXR punch maps to lower sustain level plus voice gain compensation so ADSR peak remains implicit 1 and the held level sits below the transient.","source":"user identified SFXR sustain as note duration and Punch as transient above sustain; punch mapping correction on 2026-05-13"}
{"kind":"faust_polyphony","lesson":"Prefer Faust-managed MIDI polyphony over AquaSynth graph inflation. AquaSynth should emit a single voice graph with standard `freq`, `gain`, and `gate` controls plus `[midi:on][nvoices:n]`; Faust architectures own MIDI decoding and voice allocation unless a concrete target proves the boundary insufficient.","source":"Faust official MIDI/polyphony docs and user preference for Faust support on 2026-05-13"}
{"kind":"operator_graph_parameters","lesson":"Readable operator graph fields bind runtime parameters at exact `/opgraphs/...` field paths: graph gain/note fields, operator ratio/level/feedback/envelope fields, and route index fields. Keep compact `ops=`/`edges=` as importer scaffolding and avoid graph-wide macro routing.","source":"operator graph field-site binding slice on 2026-05-13"}
{"kind":"dx7_envelopes","lesson":"DX7 four-rate/four-level operator envelopes can be lowered to labeled Aquarium ADSR approximations plus gate duration, but this is not exact DX7 EG execution. Reference rebuilds should record approximation as matched pressure and exactness as still missing.","source":"DX7 envelope approximation slice on 2026-05-13; Yamaha DX7 manual describes EGs as four rates and four levels"}
{"kind":"render_parity_harness","lesson":"Aquarium Faust output can now be compiled to Faust-generated C# and rendered into mono float samples for AudioAnalyzer comparisons. This proves the candidate side of parity; DX7 parity still requires a real Dexed/reference render or captured licensed fixture, not self-comparison.","source":"Faust render parity harness slice on 2026-05-13"}
{"kind":"reference_parity_doctrine","lesson":"External synth references exist to exercise Aquarium DSL expressiveness, not to clone foreign internals. Consume only references that can be backed by parity tests proving Aquarium can reproduce the behavior in terse, readable syntax; parsing a format is inventory, not evidence.","source":"user clarified reference-synth purpose and parity-test requirement on 2026-05-13"}
{"kind":"dx7_reference_rendering","lesson":"DX7 reference rendering now uses dexed-py as optional test tooling against a public-domain SysEx fixture. Keep fixtures and renderer helpers under the test project, preserve provenance and SHA-256 beside vendored banks, and keep AquariumSynth.Dsl.nupkg free of SysEx/Python/test assets.","source":"dexed-py public-domain fixture harness slice on 2026-05-13"}
{"kind":"dx7_rendered_parity","lesson":"The first thresholded rendered DX7 parity target is analog1.syx voice 13, MC-MM 5-3. Aquarium matches the rendered behavior with a short sine-like patch and explicit score/log-mel/envelope/duration/RMS/zero-crossing/centroid thresholds. Treat this as proof of the parity loop, not proof of exact DX7 operator execution; next target should audibly require operator routing.","source":"first thresholded DX7 rendered parity slice on 2026-05-13"}
{"kind":"dx7_envelopes","lesson":"DX7 four-rate/four-level operator envelopes can be lowered to labeled AquaSynth ADSR approximations plus gate duration, but this is not exact DX7 EG execution. Reference rebuilds should record approximation as matched pressure and exactness as still missing.","source":"DX7 envelope approximation slice on 2026-05-13; Yamaha DX7 manual describes EGs as four rates and four levels"}
{"kind":"render_parity_harness","lesson":"AquaSynth Faust output can now be compiled to Faust-generated C# and rendered into mono float samples for AudioAnalyzer comparisons. This proves the candidate side of parity; DX7 parity still requires a real Dexed/reference render or captured licensed fixture, not self-comparison.","source":"Faust render parity harness slice on 2026-05-13"}
{"kind":"reference_parity_doctrine","lesson":"External synth references exist to exercise AquaSynth DSL expressiveness, not to clone foreign internals. Consume only references that can be backed by parity tests proving AquaSynth can reproduce the behavior in terse, readable syntax; parsing a format is inventory, not evidence.","source":"user clarified reference-synth purpose and parity-test requirement on 2026-05-13"}
{"kind":"dx7_reference_rendering","lesson":"DX7 reference rendering now uses dexed-py as optional test tooling against a public-domain SysEx fixture. Keep fixtures and renderer helpers under the test project, preserve provenance and SHA-256 beside vendored banks, and keep AquaSynth.Dsl.nupkg free of SysEx/Python/test assets.","source":"dexed-py public-domain fixture harness slice on 2026-05-13"}
{"kind":"dx7_rendered_parity","lesson":"The first thresholded rendered DX7 parity target is analog1.syx voice 13, MC-MM 5-3. AquaSynth matches the rendered behavior with a short sine-like patch and explicit score/log-mel/envelope/duration/RMS/zero-crossing/centroid thresholds. Treat this as proof of the parity loop, not proof of exact DX7 operator execution; next target should audibly require operator routing.","source":"first thresholded DX7 rendered parity slice on 2026-05-13"}
{"kind":"operator_feedback","lesson":"A harder DX7 algorithm-8 target exposed direct operator self-reference as non-renderable Faust. Operator feedback now emits as delayed Faust recursion, which keeps the render path alive and is closer to DX7 feedback pressure; exact DX7 feedback scaling remains unsolved.","source":"PRC SYNTH1 parity probe and delayed feedback render fix on 2026-05-13"}
{"kind":"dx7_operator_levels","lesson":"DX7 operator output level is nonlinear and includes key/velocity offsets before envelope gain. Added a reference-import helper to approximate effective Aquarium operator levels, but PRC SYNTH1 still only improved to ~0.34 with static levels and ~0.42 with envelope tweaks; hard parity now points at DX7-style operator envelope/gain evolution rather than static level mapping alone.","source":"DX7 effective operator level probe on 2026-05-13"}
{"kind":"dx7_operator_levels","lesson":"DX7 operator output level is nonlinear and includes key/velocity offsets before envelope gain. Added a reference-import helper to approximate effective AquaSynth operator levels, but PRC SYNTH1 still only improved to ~0.34 with static levels and ~0.42 with envelope tweaks; hard parity now points at DX7-style operator envelope/gain evolution rather than static level mapping alone.","source":"DX7 effective operator level probe on 2026-05-13"}
{"kind":"staged_operator_envelopes","lesson":"Operator graphs now have a readable staged envelope surface, env=rl rates=... levels=..., backed by RateLevelEnvelope and Faust rl4_env rendering. This exercises the DSL with DX7-style multi-target contours without changing the general ADSR voice envelope. Direct DX7 rate/level lowering on PRC SYNTH1 peaked around ~0.316 in quick probes, so the next parity pressure is calibrated DX7 EG timing/gain rather than more syntax.","source":"staged operator envelope slice and PRC SYNTH1 probe on 2026-05-13"}
{"kind":"dx7_prc_listening_artifacts","lesson":"The hard PRC SYNTH1 parity probe now writes ignored WAV artifacts for ear checks under artifacts/parity/dx7-prc-synth1. A calibrated readable operator-graph candidate with staged env=rl envelopes scores ~0.458: duration is close, while RMS and timbre remain off. Use listening plus metrics to choose the next calibration target instead of blindly adding DSL surface.","source":"PRC SYNTH1 listening WAV probe on 2026-05-13"}
{"kind":"dx7_prc_calibration","lesson":"PRC SYNTH1 improved to a modest passing score of ~0.606 without new DSL surface. Removing soft clipping, using a 392 Hz graph base, and boosting the op3 carrier branch helped; global detune and per-edge route damping worsened metrics and were cut. Remaining pressure is DX7 output/index scaling or algorithm output compensation, not more envelope syntax.","source":"PRC SYNTH1 calibration pass on 2026-05-14"}
{"kind":"dx7_prc_logmel","lesson":"Listening and log-mel agreed that the aggregate PRC SYNTH1 score was misleading. The Dexed reference has strong low/body partials around 131 Hz and 393 Hz; Aquarium's earlier candidate lacked the low body despite a better aggregate score. The probe now gates on log-mel <=0.255 with a loose aggregate floor, and remaining mismatch points at DX7 output/index scaling and algorithm compensation.","source":"PRC SYNTH1 perceptual/log-mel calibration on 2026-05-14"}
{"kind":"dx7_prc_logmel","lesson":"Listening and log-mel agreed that the aggregate PRC SYNTH1 score was misleading. The Dexed reference has strong low/body partials around 131 Hz and 393 Hz; AquaSynth's earlier candidate lacked the low body despite a better aggregate score. The probe now gates on log-mel <=0.255 with a loose aggregate floor, and remaining mismatch points at DX7 output/index scaling and algorithm compensation.","source":"PRC SYNTH1 perceptual/log-mel calibration on 2026-05-14"}
{"kind":"dx7_output_level_curve","lesson":"A project-authored Dexed single-carrier sweep shows DX7 carrier amplitude follows 2^((outputLevel - 99) / 8). The previous internal-level proxy made mid-level operators far too loud. `Dx7SysEx.OperatorOutputAmplitude` now owns the measured curve; next calibration rung is two-operator modulation index.","source":"DX7 single-carrier calibration rung on 2026-05-14"}
{"kind":"dx7_modulation_index","lesson":"Isolated two-operator Dexed FM fits full-level modulation at about 12.55 radians. Under Aquarium's current Faust phase formula this maps to route index 6.275 for a full-level modulator. Apply this to isolated two-op branches, but do not blindly apply it to cascaded algorithm stacks before algorithm output compensation is modeled.","source":"DX7 two-operator modulation calibration rung on 2026-05-14"}
{"kind":"dx7_feedback_scale","lesson":"Isolated Dexed feedback sweeps fit a nonlinear Aquarium feedback table: 0, .01, .02, .05, .10, .19, .38, .66 for DX7 feedback values 0-7. PRC feedback value 5 was already accidentally close via feedback*0.04; the table moves that knowledge into Dx7SysEx.OperatorFeedbackAmount.","source":"DX7 feedback calibration rung on 2026-05-14"}
{"kind":"dx7_modulation_index","lesson":"Isolated two-operator Dexed FM fits full-level modulation at about 12.55 radians. Under AquaSynth's current Faust phase formula this maps to route index 6.275 for a full-level modulator. Apply this to isolated two-op branches, but do not blindly apply it to cascaded algorithm stacks before algorithm output compensation is modeled.","source":"DX7 two-operator modulation calibration rung on 2026-05-14"}
{"kind":"dx7_feedback_scale","lesson":"Isolated Dexed feedback sweeps fit a nonlinear AquaSynth feedback table: 0, .01, .02, .05, .10, .19, .38, .66 for DX7 feedback values 0-7. PRC feedback value 5 was already accidentally close via feedback*0.04; the table moves that knowledge into Dx7SysEx.OperatorFeedbackAmount.","source":"DX7 feedback calibration rung on 2026-05-14"}
{"kind":"dx7_detune_and_com","lesson":"PRC exposed two separate DX7 facts: ratio-mode detune must lower into operator frequency ratios, and ROM COM output compensation must be applied relative to the algorithm-32 six-output baseline used by the carrier-amplitude calibration. This cut the PRC carrier scale table; the remaining cascaded-route constant is still a fenced hard-target scar needing summed-modulator calibration.","source":"DX7 PRC parity rung on 2026-05-14"}
{"kind":"dx7_topology_aware_route_index","lesson":"The PRC hard target proved DX7 route index cannot key only on ROM edge kind: a direct edge feeding a later summed branch behaves like part of the cascaded/summed stack, not like the isolated two-op branch. `Dx7SysEx.OperatorRouteIndex(topology, edge)` now owns this distinction; PRC no longer has a private route function.","source":"DX7 summed/cascaded route lowering on 2026-05-14"}
{"kind":"dx7_project_authored_patch_rendering","lesson":"Project-authored Dexed patches can be generated directly inside tests from explicit operator specs. Use this for lawful calibration rungs instead of squeezing every DX7 fact out of public-domain hard patches.","source":"DX7 project-authored summed-stack parity on 2026-05-14"}
{"kind":"dx7_envelope_curve_cut","lesson":"Reusing the operator output amplitude curve for DX7 EG levels is not ready: it can improve isolated envelope shape metrics with shorter timing, but it failed the hard PRC log-mel target. Keep EG levels linear until isolated envelope parity passes without regressing PRC.","source":"DX7 envelope level experiment on 2026-05-14"}
{"kind":"dx7_envelope_trace","lesson":"Direct DX7 EG tracing shows the current `env=rl` abstraction is the wrong shape for some DX7 envelopes: the traced EG can spike above unit gain and drop below .02 within about 9 ms while `env=rl` is still near 1. A runtime `env=dx7` surface was attempted and cut because matching the local trace did not yet match Dexed plugin audio.","source":"DX7 envelope trace artifact on 2026-05-14"}
{"kind":"dx7_envelope_applied_gain","lesson":"Dexed-style operator audio does not use the raw DX7 EG state directly. It samples the EG once per 64-sample block and linearly interpolates applied gain across the block. A DX7-capable Aquarium envelope must model that applied-gain timebase; matching raw EG trace alone can make a candidate far too quiet or otherwise wrong.","source":"DX7 interpolated envelope trace artifact on 2026-05-14"}
{"kind":"curved_rate_level_envelope","lesson":"Aquarium staged operator envelopes now support per-segment curves and deliberate levels above unity. Keep this authority in `RateLevelEnvelope`: it protects the invariant that multi-target operator contours can model exponential synth envelopes without inventing one-off DX7 syntax.","source":"Curved rate/level envelope implementation on 2026-05-14"}
{"kind":"dx7_applied_envelope_lowering","lesson":"`Dx7SysEx.ApproximateAppliedRateLevelEnvelope` improves the hard PRC target by tracing Dexed-style block-interpolated EG gain, normalizing it against Aquarium operator output level, and emitting curved `env=rl`. Latest PRC metrics: log-mel 0.21931955, envelope distance 0.10271241, RMS ratio 1.0003664, score 0.5026183. Zero-crossing worsened to 0.28642866, so contour parity exposed a remaining oscillator/phase mismatch rather than solving DX7 parity outright.","source":"DX7 applied-envelope PRC probe on 2026-05-14"}
{"kind":"dx7_envelope_applied_gain","lesson":"Dexed-style operator audio does not use the raw DX7 EG state directly. It samples the EG once per 64-sample block and linearly interpolates applied gain across the block. A DX7-capable AquaSynth envelope must model that applied-gain timebase; matching raw EG trace alone can make a candidate far too quiet or otherwise wrong.","source":"DX7 interpolated envelope trace artifact on 2026-05-14"}
{"kind":"curved_rate_level_envelope","lesson":"AquaSynth staged operator envelopes now support per-segment curves and deliberate levels above unity. Keep this authority in `RateLevelEnvelope`: it protects the invariant that multi-target operator contours can model exponential synth envelopes without inventing one-off DX7 syntax.","source":"Curved rate/level envelope implementation on 2026-05-14"}
{"kind":"dx7_applied_envelope_lowering","lesson":"`Dx7SysEx.ApproximateAppliedRateLevelEnvelope` improves the hard PRC target by tracing Dexed-style block-interpolated EG gain, normalizing it against AquaSynth operator output level, and emitting curved `env=rl`. Latest PRC metrics: log-mel 0.21931955, envelope distance 0.10271241, RMS ratio 1.0003664, score 0.5026183. Zero-crossing worsened to 0.28642866, so contour parity exposed a remaining oscillator/phase mismatch rather than solving DX7 parity outright.","source":"DX7 applied-envelope PRC probe on 2026-05-14"}
{"kind":"dx7_applied_envelope_peak_picker","lesson":"Listening caught a real lowering bug: the first applied-envelope approximation treated the first 64-sample block as the attack peak, which made slower rising op2 in PRC swell late (`0.014 -> 0.613`) instead of hitting early and decaying. Picking the first near-peak before gate gives op2 `0.981 -> 0.843 -> 0.660` and improves PRC to log-mel 0.17186502, RMS 1.0000212, zero-crossing 0.7815517, score 0.6501268.","source":"PRC SYNTH1 listening-guided envelope fix on 2026-05-14"}
{"kind":"dx7_summed_route_scale","lesson":"Listening identified missing harsh stacked-modulation brightness. Raising `Dx7SysEx.SummedOperatorModulationRouteIndex` from 1.6 to 2.4 improves PRC to log-mel 0.16794802, envelope distance 0.09510617, centroid ratio 1.1254044, RMS ratio 1.00095, score 0.68491656, and also tightens the project-authored algorithm-8 summed-stack log-mel when graph gain is normalized. Keep isolated direct branches at 6.275; the stronger 2.4 scale belongs only to summed/cascaded DX7 branches.","source":"DX7 stacked modulation brightness calibration on 2026-05-14"}
{"kind":"dx7_summed_route_scale_hotter","lesson":"Follow-up listening and band analysis showed the previous summed/cascaded scale still sounded underwater. Raising `Dx7SysEx.SummedOperatorModulationRouteIndex` to 6.0 and normalizing PRC graph gain to 0.25 improves PRC to log-mel 0.13874426, envelope distance 0.067291506, RMS ratio 0.9912135, zero-crossing ratio 0.95325506, centroid ratio 1.1156512, score 0.7546445. A hotter 7.0 scale did not buy enough log-mel improvement and worsened RMS/score, so 6.0 is the current coherent stop.","source":"DX7 stacked modulation brightness hot sweep on 2026-05-14"}
{"kind":"patch_library","lesson":"Repo-owned Aquarium .aqua stock now lives under patches/ with library.yaml; every .aqua must parse/export in tests and the patch library must remain out of AquariumSynth.Dsl.nupkg unless intentionally promoted.","source":"Patch library folder slice on 2026-05-14"}
{"kind":"patch_library","lesson":"Repo-owned AquaSynth .aqua stock now lives under patches/ with library.yaml; every .aqua must parse/export in tests and the patch library must remain out of AquaSynth.Dsl.nupkg unless intentionally promoted.","source":"Patch library folder slice on 2026-05-14"}
{"kind":"dx7_algorithm8_split_route_parity","lesson":"The DX7 summed/cascaded route scale 6.0 is now backed by split project-authored algorithm-8 rendered probes, not only PRC or one combined stack. Latest real-Dexed run: combined stack log-mel 0.04029555 score 0.8664546, summed pair log-mel 0.042429477 score 0.870778, cascade log-mel 0.03811625 score 0.9157809. Keep isolated direct branches at 6.275 and summed/cascaded branches at 6.0 until broader evidence contradicts it.","source":"Split algorithm-8 parity probes on 2026-05-14"}
{"kind":"patch_library_taste","lesson":"Not every parity probe belongs in the patch library. Harsh project-authored DX7 calibration probes should stay in tests and ignored listening artifacts unless they are musically useful stock; the navigable .aqua library is for reusable inspiration, not measurement torture signals.","source":"User listening correction on 2026-05-14"}
{"kind":"dx7_community_voice_parity","lesson":"Actual public-domain analog1.syx community voices are useful broader probes, but listening rejected the first loose gate. ANLGSYN 1, RES SYNTH1, DX1 LEAD B, and MELLOWSOLO should remain pressure until their timbre/zero-crossing mismatch is understood. Only Mooger and fixed Piano Bass are currently defensible rendered community parity targets.","source":"Community DX7 analog1.syx survey on 2026-05-14"}
{"kind":"dx7_voice_transpose","lesson":"Piano Bass in analog1.syx proved imported DX7 voices must lower the voice transpose byte into graph frequency. Transpose 24 is neutral; transpose 12 lowers MIDI note 60 to 130.8128 Hz. Hardcoding 261.6256 made Piano Bass a full octave too high. After the fix, Piano Bass improves to log-mel 0.16180529, zero-crossing 0.905511, score 0.64267576. Do not call the broader analog1.syx survey passing; only Mooger and fixed Piano Bass are currently defensible.","source":"Piano Bass listening correction on 2026-05-14"}
{"kind":"dx7_fixed_frequency","lesson":"ANLGSYN 1 proved DX7 fixed-frequency operators must lower as absolute Hz divided by the graph base frequency, not as note ratios. Dexed fixed mode uses a log-frequency formula; op1 fixed 0/0 is about 1 Hz and op3 fixed 0/8 detune 14 is about 1.207 Hz, yielding ratios 0.007615 and 0.009227 against the transposed 130.8128 Hz graph base. This restores the low buzzing modulation and improves ANLGSYN 1 to log-mel 0.19427659, zero-crossing 0.94662774, score 0.659334.","source":"ANLGSYN 1 listening correction on 2026-05-14"}
{"kind":"dx7_max_feedback","lesson":"ANLGSYN 1 still sounded too 'woooh' after fixed-frequency lowering because high-band buzz was missing. Route-index sweeps did not help; increasing only DX7 feedback value 7 from 0.66 to 2.2 restored much more 1.2-5 kHz energy while leaving feedback value 5 at 0.19 for PRC. Latest ANLGSYN 1: log-mel 0.17553389, centroid 0.9519112, zero-crossing 0.90904, score 0.6448498. Envelope/RMS worsened, so this is a timbre correction, not final ANLGSYN parity.","source":"ANLGSYN 1 max-feedback listening correction on 2026-05-14"}
{"kind":"dx7_output_nonlinearity","lesson":"ANLGSYN 1 listening caught a remaining 'deeooh' failure: the max-feedback candidate had a close attack but lost sustained high-band punch halfway through. Enabling Aquarium's existing output soft clip only for max-feedback self-feedback DX7 voices improved ANLGSYN to score 0.70946133, log-mel 0.18456098, envelope 0.10628832, RMS 0.9816304 while PRC stayed passing with soft clip disabled at score 0.7546429 and log-mel 0.13874851. Do not apply the nonlinearity globally; PRC proves that would be a compensator.","source":"ANLGSYN 1 sustained-buzz parity correction on 2026-05-14"}
{"kind":"dx7_output_nonlinearity","lesson":"ANLGSYN 1 listening caught a remaining 'deeooh' failure: the max-feedback candidate had a close attack but lost sustained high-band punch halfway through. Enabling AquaSynth's existing output soft clip only for max-feedback self-feedback DX7 voices improved ANLGSYN to score 0.70946133, log-mel 0.18456098, envelope 0.10628832, RMS 0.9816304 while PRC stayed passing with soft clip disabled at score 0.7546429 and log-mel 0.13874851. Do not apply the nonlinearity globally; PRC proves that would be a compensator.","source":"ANLGSYN 1 sustained-buzz parity correction on 2026-05-14"}
{"kind":"dx7_max_feedback_envelope_floor","lesson":"Output soft clip alone did not fix ANLGSYN 1's mid-note drift. The self-feedback source operator was under-driven after the attack; preserving a 0.9 applied-envelope sustain floor for max-feedback source operators lifted the sustained 2.5-5 kHz band ratio from about 0.175 to above 1.0 and improved ANLGSYN 1 to score 0.7245976, log-mel 0.18460114, envelope 0.119950555, zero-crossing 0.97149533. Add sustained band gates when broad log-mel allows audible drift through.","source":"ANLGSYN 1 sustained high-band parity gate on 2026-05-14"}
{"kind":"dx7_com_compensation_cut","lesson":"ROM COM carrier compensation was louder but harmonically wrong for the analog1 community pass. User listening preferred the no-COM renders across patches; the real issue was that they were too quiet. Cut hidden per-carrier COM boosts: `OperatorOutputCompensation` returns unity and graph gain owns loudness recovery. Passing community gains with no COM are Mooger 0.75, Piano Bass 0.90, RES SYNTH1 0.75. DX1 LEAD B and MELLOWSOLO sound closer by ear with no COM but remain numeric pressure targets.","source":"community DX7 listening correction on 2026-05-14"}
{"kind":"dx7_effective_note_scaling","lesson":"DX7 operator key-scaling and rate-scaling must lower against the effective played MIDI note after voice transpose, not hardcoded MIDI 60. `Dx7SysEx.OperatorRateScaling` now feeds the block-interpolated applied envelope trace, and imported operator levels use the effective note. This improves RES SYNTH1 attack/log-mel and Piano Bass harmonic metrics; Piano Bass graph gain rebalances from 0.90 to 0.72. MELLOWSOLO remains the clearest harmonic mismatch and now has a pressure artifact test rather than a fake parity pass. A trial output-level-dependent applied-envelope trace was cut because it worsened MELLOWSOLO and ANLGSYN 1.","source":"community DX7 listening correction on 2026-05-14"}
{"kind":"dx7_modulator_peak_headroom","lesson":"Listening found ANLGSYN 1 and RES SYNTH1 blowing out into a drone at the modulator-envelope peak while Piano Bass was spot on. The surviving DX7 probe change caps only algorithm-2 non-carrier applied-envelope Level1 at 0.92, preserving Piano Bass and improving RES SYNTH1 while keeping ANLGSYN judged by log-mel and sustained high-band evidence rather than aggregate score. A wider summed-route headroom experiment for Mooger fan-in was tested and cut because it worsened Mooger metrics and did not isolate the overtone-emphasis issue.","source":"community DX7 listening correction on 2026-05-14"}
{"kind":"dx7_mellowsolo_tail_window","lesson":"MELLOWSOLO pressure artifacts need a longer render window than 1.0s because the Aquarium candidate emits a 0.6s release from a 0.65s gate, so the old WAV cut the tail before it could finish. Extending only the MELLOWSOLO pressure render to 1.25s improves log-mel from about 0.429 to 0.347 and makes the tail audible without changing the synth lowering or passing parity targets. A global traced-release-duration experiment was tested and cut because it shortened releases too aggressively and broke PRC, Piano Bass, RES SYNTH1, and ANLGSYN 1. RES SYNTH1 has oscillator sync enabled, so its phase-like attack issue is more likely relative operator attack/peak shape than free-running oscillator phase.","source":"community DX7 listening correction on 2026-05-14"}
{"kind":"dx7_graph_pitch_lfo","lesson":"ANLGSYN 1's missing beat is DX7 pitch LFO, not amplitude LFO: raw analog1.syx has AMD=71 but all operator AMS values are 0, while PMD=32/PMS=1/sine speed 38/delay 33 are active. Aquarium operator graphs now own graph-level vibrato (`vibrato`, `vibrato_hz`, `vibrato_delay`) and the DX7 probe lowers that LFO to about depth 0.003694 at 5.993 Hz with a 0.233s fade-in. A hard delay gate was tested and cut because it pushed log-mel over the ANLGSYN gate; fade-in keeps ANLGSYN passing at log-mel about 0.19497 and score about 0.6979.","source":"ANLGSYN 1 pitch-LFO beat correction on 2026-05-14"}
{"kind":"dx7_mellowsolo_tail_window","lesson":"MELLOWSOLO pressure artifacts need a longer render window than 1.0s because the AquaSynth candidate emits a 0.6s release from a 0.65s gate, so the old WAV cut the tail before it could finish. Extending only the MELLOWSOLO pressure render to 1.25s improves log-mel from about 0.429 to 0.347 and makes the tail audible without changing the synth lowering or passing parity targets. A global traced-release-duration experiment was tested and cut because it shortened releases too aggressively and broke PRC, Piano Bass, RES SYNTH1, and ANLGSYN 1. RES SYNTH1 has oscillator sync enabled, so its phase-like attack issue is more likely relative operator attack/peak shape than free-running oscillator phase.","source":"community DX7 listening correction on 2026-05-14"}
{"kind":"dx7_graph_pitch_lfo","lesson":"ANLGSYN 1's missing beat is DX7 pitch LFO, not amplitude LFO: raw analog1.syx has AMD=71 but all operator AMS values are 0, while PMD=32/PMS=1/sine speed 38/delay 33 are active. AquaSynth operator graphs now own graph-level vibrato (`vibrato`, `vibrato_hz`, `vibrato_delay`) and the DX7 probe lowers that LFO to about depth 0.003694 at 5.993 Hz with a 0.233s fade-in. A hard delay gate was tested and cut because it pushed log-mel over the ANLGSYN gate; fade-in keeps ANLGSYN passing at log-mel about 0.19497 and score about 0.6979.","source":"ANLGSYN 1 pitch-LFO beat correction on 2026-05-14"}
{"kind":"zyn_reference_start","lesson":"The ZynAddSubFX phase should start with structural feature extraction, not translation. Upstream Zyn instruments live in a separate bank submodule and the preset provenance is not clear enough to vendor casually, so the first parser slice uses project-authored .xiz fixtures. `ZynInstrumentReader` reads plain or gzip-compressed XML and classifies active ADD/SUB/PAD engines, kit layering, envelopes, free envelopes, LFOs, filters, formants, and effects into ReferencePatch features.","source":"ZynAddSubFX feature extraction kickoff on 2026-05-14"}
{"kind":"zyn_reference_rebuilds","lesson":"The first Zyn-style Aquarium rebuilds can sketch additive lead, PAD texture, and vocal/formant layer fixtures with existing voices, macros, formants, filters, noise, and modulation, but the missing abstractions are now concrete: additive harmonic-bank syntax, PAD/spectral source generation, free envelopes for normal voices, named kit/layer routing with per-layer effect sends, and richer formant/vowel morphing. Keep these as named pressure before adding new DSL surface.","source":"ZynAddSubFX rebuild catalog slice on 2026-05-14"}
{"kind":"zyn_reference_rebuilds","lesson":"The first Zyn-style AquaSynth rebuilds can sketch additive lead, PAD texture, and vocal/formant layer fixtures with existing voices, macros, formants, filters, noise, and modulation, but the missing abstractions are now concrete: additive harmonic-bank syntax, PAD/spectral source generation, free envelopes for normal voices, named kit/layer routing with per-layer effect sends, and richer formant/vowel morphing. Keep these as named pressure before adding new DSL surface.","source":"ZynAddSubFX rebuild catalog slice on 2026-05-14"}
{"kind":"zyn_worst_case_survey","lesson":"Scanning the upstream ZynAddSubFX instrument bank at commit e9f64a9 found 1358 .xiz files and the hardened reader now parses all of them. The nastiest target is `olivers-100/0032-Drum Kit.xiz`: 16 enabled kit items, mixed ADD/SUB/PAD engines, 106 envelopes, 25 free envelopes, 73 LFOs, 37 filters, and 3 formant filters. The broader top list says named kit/layer routing should come before syntax golf for additive banks, PAD sources, or formant motion; otherwise new abstractions will have no coherent owner.","source":"ZynAddSubFX pressure survey on 2026-05-14"}
{"kind":"layer_language_golf","lesson":"The first Zyn-driven language-golf abstraction is `layer`: named layer declarations own engine tags, optional MIDI key range metadata, default gain, and effect-send labels, while lowering to ordinary Aquarium voices for now. The abstraction generalizes across additive, PAD-texture, and vocal/formant rebuilds and gives future additive-bank, PAD-source, free-envelope, and formant-motion syntax a coherent owner. It deliberately does not implement those later features yet.","source":"Zyn named layer DSL slice on 2026-05-14"}
{"kind":"harmonic_bank_language_golf","lesson":"Additive `harmonics` syntax now records authored ratio/gain partial banks under named layers and lowers them to ordinary Aquarium voices. This generalizes additive lead and future drawbar/partial-bank targets without hiding Zyn ADDsynth phase, bandwidth, oscillator shaping, or free-envelope semantics behind a fake parity claim.","source":"Zyn harmonic-bank DSL slice on 2026-05-14"}
{"kind":"layer_language_golf","lesson":"The first Zyn-driven language-golf abstraction is `layer`: named layer declarations own engine tags, optional MIDI key range metadata, default gain, and effect-send labels, while lowering to ordinary AquaSynth voices for now. The abstraction generalizes across additive, PAD-texture, and vocal/formant rebuilds and gives future additive-bank, PAD-source, free-envelope, and formant-motion syntax a coherent owner. It deliberately does not implement those later features yet.","source":"Zyn named layer DSL slice on 2026-05-14"}
{"kind":"harmonic_bank_language_golf","lesson":"Additive `harmonics` syntax now records authored ratio/gain partial banks under named layers and lowers them to ordinary AquaSynth voices. This generalizes additive lead and future drawbar/partial-bank targets without hiding Zyn ADDsynth phase, bandwidth, oscillator shaping, or free-envelope semantics behind a fake parity claim.","source":"Zyn harmonic-bank DSL slice on 2026-05-14"}
{"kind":"voice_rate_level_envelopes","lesson":"The existing readable `env=rl rates=... levels=... curves=... gate=...` contour now applies to normal voices and therefore to named layer defaults, not only operator graphs. This gives PAD and vocal Zyn rebuilds staged non-ADSR onset/release shape while preserving the invariant that Note owns gate timing and envelope data owns level shape. Arbitrary Zyn free-envelope point curves remain missing pressure.","source":"Zyn staged voice envelope DSL slice on 2026-05-14"}
{"kind":"pad_spectral_cloud","lesson":"PAD-style layers now have `spectrum layer=... root=... spread=... partials=ratio:gain,...`, recorded as `SpectralBank` and generated as PADsynth-style FFT tables: frequency-domain harmonic spreading, deterministic random phase, one inverse FFT, normalization, then Faust waveform/rdtable playback before ordinary voice treatment. This gives PAD texture rebuilds a real spectral source authority without inflating the voice graph; roots/spreads/partials are authoring-time table shape, not runtime controls. It still does not claim full Zyn PAD harmonic-profile/randomness/pitch-zone behavior.","source":"Zyn PAD spectral-cloud DSL slice on 2026-05-14"}
{"kind":"zyn_reference_oracle","lesson":"ZynAddSubFX source is pinned as GPL test/development material under `external/zynaddsubfx` at 3ab608c432996ba4d582176572c0b0f82328c825. Treat it like dexed-py: a parity oracle for tests and artifacts, not Aquarium implementation, runtime dependency, or NuGet package content. Before further Zyn DSL golf, build a test-only PAD renderer/wrapper from this source and compare Aquarium rebuilds against actual Zyn behavior.","source":"Zyn source pinning for parity on 2026-05-14"}
{"kind":"zyn_reference_oracle","lesson":"ZynAddSubFX source is pinned as GPL test/development material under `external/zynaddsubfx` at 3ab608c432996ba4d582176572c0b0f82328c825. Treat it like dexed-py: a parity oracle for tests and artifacts, not AquaSynth implementation, runtime dependency, or NuGet package content. Before further Zyn DSL golf, build a test-only PAD renderer/wrapper from this source and compare AquaSynth rebuilds against actual Zyn behavior.","source":"Zyn source pinning for parity on 2026-05-14"}
{"date":"2026-05-14","area":"zyn-reference","lesson":"Pinned Zyn can build on Windows through MSYS2/MinGW when GUI/plugins are disabled, tests are enabled, Werror is off, and the stale non-async MiddleWare Part constructor call is patched. Apply Zyn compatibility patches to ignored artifact source copies, not the clean pinned submodule.","evidence":"tools/zyn-reference/build-zyn-reference.ps1 copies external/zynaddsubfx to artifacts/zyn-source-msys, applies tools/zyn-reference/patches/0001-msys-part-constructor.patch, builds PadNoteTest, and ctest -R PadNoteTest passes."}
{"date":"2026-05-15","area":"zyn-pad-parity","lesson":"The Zyn PAD oracle now emits real reference material through Zyn PADnoteParameters/PADnote, and the first project-authored PAD comparison is not close: log-mel 0.552296, envelope 0.774501, RMS ratio 0.234763, centroid ratio 1.025735, score 0.236117. Treat this as evidence to isolate source table generation, playback/gain, envelope, and fixture mapping before adding more Zyn DSL syntax.","evidence":"tools/zyn-reference/ZynPadReference.cpp plus tests/AquariumSynth.Dsl.Tests/ZynReferenceParityTests.cs write artifacts/parity/zyn-pad-reference/pad-texture-zyn.wav, pad-texture-aqua.wav, and report.txt when the Zyn artifact renderer is built."}
{"date":"2026-05-15","area":"zyn-pad-parity","lesson":"User listening correctly identified the first Aquarium PAD rebuild as too noisy. The fix was architectural cleanup before new syntax: cut unearned noise/drive/soft-clip from the rebuild, interpolate spectral table reads, and use Zyn-sized 131072-frame PAD tables. Score improved from 0.236117 to 0.487325, with RMS ratio 1.008516 and centroid ratio 1.083082. Remaining mismatch is source profile/table generation and envelope semantics, not decorative layers.","evidence":"artifacts/parity/zyn-pad-reference/report.txt after the de-bee pass; full dotnet test passed 96/96."}
{"date":"2026-05-15","area":"zyn-pad-parity","lesson":"The Zyn PAD oracle now emits real reference material through Zyn PADnoteParameters/PADnote, and the first project-authored PAD comparison is not close: log-mel 0.552296, envelope 0.774501, RMS ratio 0.234763, centroid ratio 1.025735, score 0.236117. Treat this as evidence to isolate source table generation, playback/gain, envelope, and fixture mapping before adding more Zyn DSL syntax.","evidence":"tools/zyn-reference/ZynPadReference.cpp plus tests/AquaSynth.Dsl.Tests/ZynReferenceParityTests.cs write artifacts/parity/zyn-pad-reference/pad-texture-zyn.wav, pad-texture-aqua.wav, and report.txt when the Zyn artifact renderer is built."}
{"date":"2026-05-15","area":"zyn-pad-parity","lesson":"User listening correctly identified the first AquaSynth PAD rebuild as too noisy. The fix was architectural cleanup before new syntax: cut unearned noise/drive/soft-clip from the rebuild, interpolate spectral table reads, and use Zyn-sized 131072-frame PAD tables. Score improved from 0.236117 to 0.487325, with RMS ratio 1.008516 and centroid ratio 1.083082. Remaining mismatch is source profile/table generation and envelope semantics, not decorative layers.","evidence":"artifacts/parity/zyn-pad-reference/report.txt after the de-bee pass; full dotnet test passed 96/96."}
{"date":"2026-05-15","area":"zyn-pad-parity","lesson":"The octave-low Zyn PAD mismatch came from conflating spectral table root with note playback frequency. `spectrum root` must own table/base frequency, while `freq` owns the played note. Using Zyn's sample basefreq 77.7813 with playback freq 261.6256, and removing gratuitous spread/vibrato, improved the PAD report to log-mel 0.353365, envelope 0.348752, RMS ratio 0.97283, centroid ratio 0.999243, score 0.612832.","evidence":"PatchScript now preserves spectrum freq as voice note frequency; PatchScriptTests.SpectralBankSeparatesTableRootFromPlaybackFrequency covers the invariant; artifacts/parity/zyn-pad-reference/report.txt records the improved score."}
{"date":"2026-05-15","area":"zyn-pad-parity","lesson":"Aqua's remaining detuned impression was caused by stacking same-note PAD spectral voices. Collapse body/high content into one coherent spectral table before adding layers for richness. A simple gain trim then brought loudness into line: RMS ratio 1.001704, centroid ratio 1.004072, score 0.6211.","evidence":"BuiltInScripts.ZynStylePadTexture now uses one active PAD spectrum with partials 1, 1.5, and 2 plus patch gain 1.1; artifacts/parity/zyn-pad-reference/report.txt records the focused render."}
{"date":"2026-05-15","area":"zyn-pad-fixtures","lesson":"More PAD fixtures immediately exposed which pressure is real: `pad-pure` and old `pad-texture` render identically through Zyn, so the filter/category XML in `pad-texture` was not live oracle behavior. `pad-harmonic` changes table and note output through explicit OSCIL harmonics and should be the next PAD parity rung.","evidence":"tests/AquariumSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored/pad-pure.xiz and pad-harmonic.xiz plus artifacts/parity/zyn-pad-fixtures/report.txt."}
{"date":"2026-05-15","area":"zyn-pad-fixtures","lesson":"More PAD fixtures immediately exposed which pressure is real: `pad-pure` and old `pad-texture` render identically through Zyn, so the filter/category XML in `pad-texture` was not live oracle behavior. `pad-harmonic` changes table and note output through explicit OSCIL harmonics and should be the next PAD parity rung.","evidence":"tests/AquaSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored/pad-pure.xiz and pad-harmonic.xiz plus artifacts/parity/zyn-pad-fixtures/report.txt."}
{"date":"2026-05-15","area":"zyn-upstream-gpl-bank","lesson":"GPL Zyn presets are acceptable as test-only feedstock. The upstream instrument submodule at `external/zynaddsubfx/instruments` commit c5c912131b31df5fdf372d2f06a25aaf2375837f provides 1318 `.xiz` files; survey and PAD render tests write ignored reports/WAVs while the NuGet boundary still excludes `.xiz`, `external/`, and artifacts.","evidence":"ZynInstrumentTests.SurveysUpstreamGplInstrumentBankWhenAvailable and ZynReferenceParityTests.ZynPadReferenceRendererSurveysUpstreamGplPadFixturesWhenBuilt; focused Zyn tests passed 14/14."}
{"date":"2026-05-15","area":"zyn-artifact-layout","lesson":"Zyn PAD listening artifacts now follow the DX7 parity shape: per-synth timestamped run folders with Zyn reference WAVs, source-table WAVs, `report.txt`, and `candidate.aqua`. Upstream GPL PAD runs write `candidate-aquarium.wav`; this was initially the current ZynStylePadTexture baseline and is now superseded by generated per-preset PAD rebuilds.","evidence":"ZynReferenceParityTests focused run passed 4/4 and wrote folders such as artifacts/parity/zyn-upstream-pad-fixtures/0121-ghost-ensemble-20260515T072330036."}
{"date":"2026-05-15","area":"zyn-pad-translation","lesson":"Zyn PAD artifacts now use per-preset generated `.aqua` instead of the generic baseline. `ZynInstrumentReader.RebuildFirstPadAsAquariumScript` translates the first enabled PAD kit item into one named PAD layer plus a `spectrum` bank using OSCIL harmonic magnitudes, Zyn oracle sample `basefreq` as table root, PAD volume as layer gain, and simple rate/level envelope scaffolding. Reports explicitly list missing Zyn semantics: bandwidth/profile spreading, harmonic profile shaping, and filter envelope/LFO lowering.","evidence":"ZynInstrumentTests.RebuildsProjectAuthoredPadAsAquariumSpectrumScript plus focused Zyn tests passed 13/13; latest upstream PAD reports include generated candidate scripts and log-mel scores from ~0.276 to ~0.637."}
{"date":"2026-05-15","area":"zyn-artifact-layout","lesson":"Zyn PAD listening artifacts now follow the DX7 parity shape: per-synth timestamped run folders with Zyn reference WAVs, source-table WAVs, `report.txt`, and `candidate.aqua`. Upstream GPL PAD runs write `candidate-aquasynth.wav`; this was initially the current ZynStylePadTexture baseline and is now superseded by generated per-preset PAD rebuilds.","evidence":"ZynReferenceParityTests focused run passed 4/4 and wrote folders such as artifacts/parity/zyn-upstream-pad-fixtures/0121-ghost-ensemble-20260515T072330036."}
{"date":"2026-05-15","area":"zyn-pad-translation","lesson":"Zyn PAD artifacts now use per-preset generated `.aqua` instead of the generic baseline. `ZynInstrumentReader.RebuildFirstPadAsAquaSynthScript` translates the first enabled PAD kit item into one named PAD layer plus a `spectrum` bank using OSCIL harmonic magnitudes, Zyn oracle sample `basefreq` as table root, PAD volume as layer gain, and simple rate/level envelope scaffolding. Reports explicitly list missing Zyn semantics: bandwidth/profile spreading, harmonic profile shaping, and filter envelope/LFO lowering.","evidence":"ZynInstrumentTests.RebuildsProjectAuthoredPadAsAquaSynthSpectrumScript plus focused Zyn tests passed 13/13; latest upstream PAD reports include generated candidate scripts and log-mel scores from ~0.276 to ~0.637."}
{"date":"2026-05-15","topic":"zyn-pad-profile-table-semantics","lesson":"Zyn PAD bandwidth/profile/position fields belong on SpectralBank as table-generation data, not in parity side channels. Mapping those fields and Zyn's exponential PAD volume curve improved gain/envelope evidence, but profile math alone did not solve log-mel parity.","evidence":"Focused Zyn tests passed 13/13 after adding PadSpectrumProfile and zyn_* script fields; upstream PAD batch still has log-mel roughly 0.34-0.69 depending on fixture."}
{"date":"2026-05-15","topic":"zyn-oscilgen-approximation","lesson":"Expanding a subset of Zyn OscilGen base_function plus spectrum_adjust is conceptually the right owner for one-harmonic PAD presets, but the first approximation is mixed and must not be treated as solved parity.","evidence":"Soft Pad log-mel improved to 0.342738 and Organ Choir Pad2 to 0.573216, while sin2x pad regressed to 0.524421, DoublePadBass to 0.689315, and Ghost Ensemble to 0.482155. Next cut needs more OscilGen semantics or gating."}
{"date":"2026-05-15","topic":"zyn-oscilgen-stage-order","lesson":"Zyn OscilGen partial extraction should only run the sampled waveform pipeline when oscillator processing stages are live; otherwise direct harmonics stay readable and capped. A broad base-function expansion made some presets worse without earning its complexity.","evidence":"After adding sampled filter/waveshape/modulation stage order and restoring a capped direct path, sin2x pad improved to log-mel 0.455193, while Soft Pad stayed 0.342738 and Ghost stayed 0.40922. DoublePadBass and Organ Choir remain poor, so this is an incremental rung, not parity."}
{"date":"2026-05-15","topic":"zyn-pad-kit-layering","lesson":"Do not treat first enabled PAD kit item as a Zyn instrument when kit_mode is multi-kit. Multi-kit PAD presets need per-kit table roots and layered Aqua spectrum banks, or parity artifacts compare against an amputated preset.","evidence":"DoublePadBass has kit_mode=1 with enabled PAD kit items 0 and 1. Rebuilding/summing both moved log-mel from about 0.655 to 0.493 and generated doublepadbass_0 plus doublepadbass_1. Raising the volume cap to 3.0 worsened log-mel to 0.521 and was cut."}
{"date":"2026-05-15","topic":"zyn-ghost-base-function","lesson":"A direct-harmonic readability gate must not erase source identity. Ghost Ensemble needed power-base OscilGen expansion even without downstream oscillator stages; otherwise it collapsed toward a generic two-partial pad.","evidence":"Ghost Ensemble has base_function=4 with two explicit harmonics. Expanding power-base multi-harmonic sources changed the candidate from two partials to 32 partials and improved score to 0.446977, with log-mel 0.402789."}
{"date":"2026-05-15","topic":"zyn-pad-static-filter","lesson":"Zyn PAD static global filter frequency is audible layer-balance authority, not optional decoration. Read nested FILTER params under FILTER_PARAMETERS and lower old Zyn cutoff values through the exponential FilterParams curve, not a linear freq/127 guess.","evidence":"DoublePadBass kit0 filter freq=12 and kit1 freq=61. Linear mapping gave lpf=0.094488/0.480315 and still sounded like all kazoo. Zyn's curve maps them to lpf=0.003325/0.047225, improving log-mel to 0.334546 and making the generated patch deep-bass-first. Filter envelopes/LFOs and level recovery remain missing pressure."}
{"date":"2026-05-15","topic":"zyn-pad-detune-and-filter-order","lesson":"Zyn PAD note frequency is not just the requested note plus table root; FREQUENCY_PARAMETERS coarse/fine detune are live playback authority, and analog LP2 needs a steeper low-pass than Aquarium's old one-pole default.","evidence":"DoublePadBass has coarse_detune=15360, which Zyn getdetune wraps from octave 15 to -1 octave. Emitting spectrum freq=130.8128 instead of 261.6256 and lpf_order=2 moved DoublePadBass log-mel from 0.334546 after static cutoff mapping to 0.259298; remaining gap is RMS/envelope."}
{"date":"2026-05-15","topic":"zyn-pad-filter-envelope","lesson":"Zyn PAD FILTER_ENVELOPE is audible transient authority. Static cutoff parity can get the tone right while still missing the opening hit; Aquarium needs explicit low-pass cutoff envelopes with nonzero start levels.","evidence":"DoublePadBass sounded tonally right but softer at onset. Mapping Zyn ADSR-filter octave offsets into lpf_env=rl with lpf_start improved log-mel from 0.259298 to 0.241993, envelope distance from 0.631427 to 0.554658, RMS ratio from 0.390177 to 0.456173, and score from 0.370218 to 0.404382."}
{"date":"2026-05-15","topic":"zyn-pad-detune-and-filter-order","lesson":"Zyn PAD note frequency is not just the requested note plus table root; FREQUENCY_PARAMETERS coarse/fine detune are live playback authority, and analog LP2 needs a steeper low-pass than AquaSynth's old one-pole default.","evidence":"DoublePadBass has coarse_detune=15360, which Zyn getdetune wraps from octave 15 to -1 octave. Emitting spectrum freq=130.8128 instead of 261.6256 and lpf_order=2 moved DoublePadBass log-mel from 0.334546 after static cutoff mapping to 0.259298; remaining gap is RMS/envelope."}
{"date":"2026-05-15","topic":"zyn-pad-filter-envelope","lesson":"Zyn PAD FILTER_ENVELOPE is audible transient authority. Static cutoff parity can get the tone right while still missing the opening hit; AquaSynth needs explicit low-pass cutoff envelopes with nonzero start levels.","evidence":"DoublePadBass sounded tonally right but softer at onset. Mapping Zyn ADSR-filter octave offsets into lpf_env=rl with lpf_start improved log-mel from 0.259298 to 0.241993, envelope distance from 0.631427 to 0.554658, RMS ratio from 0.390177 to 0.456173, and score from 0.370218 to 0.404382."}
{"date":"2026-05-15","topic":"zyn-pad-output-calibration","lesson":"After PAD table roots, detune, filter order, filter envelope, and logarithmic envelope times are mapped, remaining Zyn PAD loudness can be a generated-rebuild output calibration rather than another per-layer fixture tweak.","evidence":"Current PAD fixture RMS ratios before calibration were consistently low: DoublePadBass 0.511669, Soft Pad 0.619347, Organ Choir 0.588127, Ghost 0.730155, sin2x 0.775653. Emitting patch gain=1.6 moved DoublePadBass candidate WAV peak to 0.979 against clipped Zyn reference peak 1.0, envelope distance to 0.226563, RMS ratio to 0.818669, and score to 0.631168 while keeping batch RMS plausible."}
{"date":"2026-05-15","topic":"zyn-pad-filter-q","lesson":"Zyn PAD analog filter Q is real filter damping authority and belongs as a general Aquarium filter surface (`lpf_q`), not as a fixture-specific cutoff/gain fudge.","evidence":"Ghost Ensemble has FILTER freq_track=97 and q=28. Adding note-tracked cutoff lowered Ghost lpf from 0.369706 to 0.282776 and adding explicit lpf_q=0.499021 moved log-mel from 0.38424 to 0.375637 and RMS ratio from 1.155288 to 1.09995. DoublePadBass remained acceptable and improved to log-mel 0.247729 / score 0.650069 after Q."}
{"date":"2026-05-15","topic":"zyn-pad-filter-q","lesson":"Zyn PAD analog filter Q is real filter damping authority and belongs as a general AquaSynth filter surface (`lpf_q`), not as a fixture-specific cutoff/gain fudge.","evidence":"Ghost Ensemble has FILTER freq_track=97 and q=28. Adding note-tracked cutoff lowered Ghost lpf from 0.369706 to 0.282776 and adding explicit lpf_q=0.499021 moved log-mel from 0.38424 to 0.375637 and RMS ratio from 1.155288 to 1.09995. DoublePadBass remained acceptable and improved to log-mel 0.247729 / score 0.650069 after Q."}
{"date":"2026-05-15","topic":"zyn-pad-q-order-cascade","lesson":"Explicit low-pass Q must not erase low-pass order. When a reference filter has both damping and slope, Faust lowering should cascade resonant stages according to `lpf_order` rather than replacing the filter with one generic resonant low-pass.","evidence":"Ghost Ensemble still sounded bright in the middle after lpf_q. Cascading resonlp stages for lpf_order=4 moved Ghost log-mel from 0.375637 to 0.369402 and RMS ratio from 1.09995 to 1.054875 while keeping DoublePadBass near its accepted score: log-mel 0.247748, score 0.650029. ZynPadReference harmonic dumps showed Ghost's first 32 imported OscilGen partial ratios already matched Zyn, so this was filter slope, not first-page harmonic extraction."}
{"date":"2026-05-15","topic":"zyn-oscil-filter-index","lesson":"Zyn OscilGen oscillator filter functions use one-based harmonic indices. Do not translate them through a zero-based Aquarium index; that moves spectral features to the wrong partial and can make a preset sound like the wrong overtone is being emphasized.","evidence":"sin2x uses oscillator filter type 13. With zero-based indexing, Aquarium put the spike on h3 and had centroid ratio 1.227953. After preserving Zyn's one-based index, candidate partials are h2-dominant, centroid ratio improves to 0.998145, log-mel moves from 0.491437 to 0.479782, and UpstreamSin2xOscilFilterKeepsSpikeOnSecondHarmonicWhenAvailable asserts h2 > h3 * 10."}
{"date":"2026-05-15","topic":"zyn-oscil-filter-index","lesson":"Zyn OscilGen oscillator filter functions use one-based harmonic indices. Do not translate them through a zero-based AquaSynth index; that moves spectral features to the wrong partial and can make a preset sound like the wrong overtone is being emphasized.","evidence":"sin2x uses oscillator filter type 13. With zero-based indexing, AquaSynth put the spike on h3 and had centroid ratio 1.227953. After preserving Zyn's one-based index, candidate partials are h2-dominant, centroid ratio improves to 0.998145, log-mel moves from 0.491437 to 0.479782, and UpstreamSin2xOscilFilterKeepsSpikeOnSecondHarmonicWhenAvailable asserts h2 > h3 * 10."}
{"date":"2026-05-15","topic":"zyn-organ-highpass-selected-root","lesson":"Organ Choir's coherent octave blast needs real Zyn PAD high-pass ownership and selected table roots for OscilGen shift-base sources, but selected roots must not become the global default. The source rule is narrow: keep sample-0 roots unless the PAD OscilGen base function is the shift source (`base_function=7`) and the oracle provides the selected note table root.","evidence":"Organ Choir kit0 has analog HP2 with stages and FILTER_ENVELOPE, while kit1 uses base_function=7. Mapping hpf_order/hpf_env and using the selected 622.25 Hz table root only for kit1 moved the candidate's dominant peak to 522.67 Hz with 261 Hz at -1.29 dB, close to Zyn's 523.33 Hz lead and 261 Hz at -2.23 dB. Latest Organ log-mel is 0.499122 / score 0.51966, while accepted Soft Pad, Ghost, and DoublePadBass remain on sample-0 roots; global selected roots had regressed DoublePadBass."}
{"date":"2026-05-15","topic":"zyn-organ-basefunc-mod-adaptive","lesson":"Organ Choir's violin-vs-wind mismatch was OscilGen source-table ownership, not pitch, envelope, or gain. Zyn `base_function_modulation` warps the phase before base-function sampling, and `adaptive_harmonics` remaps the prepared oscillator spectrum at the PAD table root; both must happen before PAD bandwidth/profile spreading.","evidence":"Before the fix, Aqua kit1 partials formed a smooth ramp (h3 -3.5 dB, h4 -5.1 dB, h8 -10.2 dB relative to h1) while Zyn's harmonic dump was sparse (h3 -27.1 dB, h4 -70.3 dB, h8 -50.2 dB). Applying base_function_modulation=1 and adaptive_harmonics=1 moved kit1 table energy to 400-800 Hz 0.2713 vs Zyn 0.2780 and 8-16 kHz 0.1471 vs Zyn 0.1355; Organ log-mel improved to 0.471483 and score to 0.541933 without changing accepted DoublePadBass, Ghost, or Soft Pad."}
{"date":"2026-05-15","topic":"zyn-pad-ear-vs-logmel","lesson":"For sustained Zyn PAD fixtures, log-mel distance can remain pessimistic after the perceptual target has been reached. Keep it as a regression and triage signal, but do not keep grinding accepted fixtures solely to chase a DX7-like numeric threshold when listening and structural source-table evidence agree.","evidence":"After mapping selected roots, HP filter motion, Q/order cascades, one-based OscilGen filter indices, base-function phase modulation, and adaptive harmonics, the user accepted DoublePadBass, Ghost Ensemble, Soft Pad, Organ Choir, and the other recent community PAD fixtures by ear. Organ Choir still reports log-mel 0.471483 despite direct kit1 table energy matching the Zyn body/air balance and the listener judging all fixtures equivalent."}
{"date":"2026-05-16","topic":"pad-syntax-generalization","lesson":"Reference-synth names must not become canonical Aquarium syntax or public patch-model vocabulary. Importers may translate Zyn controls, and parser aliases may keep old parity artifacts readable, but generated/authored `.aqua` should use Aquarium-native `pad_*` terms for general spectral-table intent.","evidence":"Zyn PAD parity had produced generated scripts with `zyn_mode`, `zyn_bandwidth`, `zyn_bwscale`, `zyn_profile`, and `zyn_position`, plus public model names like `ZynHarmonicProfile`. The generalization pass changed generated scripts to `pad_mode`, `pad_bandwidth`, `pad_bwscale`, `pad_profile`, and `pad_position`, renamed the public profile/position records and enums to PAD-native names, preserved legacy `zyn_*` parser aliases, and passed `dotnet test AquariumSynthCSharp.slnx --no-restore` with 108 tests."}
{"date":"2026-05-16","topic":"pad-syntax-generalization","lesson":"Reference-synth names must not become canonical AquaSynth syntax or public patch-model vocabulary. Importers may translate Zyn controls, and parser aliases may keep old parity artifacts readable, but generated/authored `.aqua` should use AquaSynth-native `pad_*` terms for general spectral-table intent.","evidence":"Zyn PAD parity had produced generated scripts with `zyn_mode`, `zyn_bandwidth`, `zyn_bwscale`, `zyn_profile`, and `zyn_position`, plus public model names like `ZynHarmonicProfile`. The generalization pass changed generated scripts to `pad_mode`, `pad_bandwidth`, `pad_bwscale`, `pad_profile`, and `pad_position`, renamed the public profile/position records and enums to PAD-native names, preserved legacy `zyn_*` parser aliases, and passed `dotnet test AquaSynth.slnx --no-restore` with 108 tests."}
{"date":"2026-05-16","topic":"project-rename-aquasynth","lesson":"The C# synth repo is now AquaSynth and the Rust reference branch is AquaSynth-rs. Keep repo wiring, package metadata, namespaces, solution/project names, env vars, docs, and state aligned to AquaSynth; do not reintroduce AquariumSynthCSharp/AquariumSynth.Dsl names.","evidence":"Renamed solution to AquaSynth.slnx, projects to src/AquaSynth.Dsl and tests/AquaSynth.Dsl.Tests, namespaces/package to AquaSynth.Dsl, package version suffix to 0.1.0-aquasynth.1, DX7 env var to AQUASYNTH_DX7_PYTHON, and origin to https://github.com/GameCult/AquaSynth.git. Verified `dotnet build AquaSynth.slnx --no-restore`, `dotnet test tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-build -v minimal` with 108 passed, and `dotnet pack src\\AquaSynth.Dsl\\AquaSynth.Dsl.csproj -c Release --no-restore` producing AquaSynth.Dsl.0.1.0-aquasynth.1.nupkg."}

View file

@ -1,7 +1,7 @@
project: AquariumSynthCSharp
project: AquaSynth
mission: >
C# authoring, analysis, and Faust generation layer for Aquarium synth patches,
targeting the Aquarium/Vortice engine path without dragging the Rust renderer
C# authoring, analysis, and Faust generation layer for AquaSynth patches,
targeting the AquaSynth/Vortice engine path without dragging the Rust renderer
into runtime dependencies.
surfaces:
instructions: AGENTS.md
@ -10,32 +10,32 @@ surfaces:
scratch: state/scratch.md
evidence: state/evidence.jsonl
spine: state/spine.yaml
solution: AquariumSynthCSharp.slnx
library: src/AquariumSynth.Dsl
solution: AquaSynth.slnx
library: src/AquaSynth.Dsl
patch_library: patches
tests: tests/AquariumSynth.Dsl.Tests
tests: tests/AquaSynth.Dsl.Tests
reference_roadmap: docs/reference-synth-roadmap.md
dx7_public_domain_fixtures: tests/AquariumSynth.Dsl.Tests/Fixtures/Dx7/PublicDomain
dx7_public_domain_fixtures: tests/AquaSynth.Dsl.Tests/Fixtures/Dx7/PublicDomain
zyn_reference_source: external/zynaddsubfx
zyn_upstream_instrument_bank: external/zynaddsubfx/instruments
zyn_reference_build_script: tools/zyn-reference/build-zyn-reference.ps1
zyn_pad_reference_renderer: tools/zyn-reference/ZynPadReference.cpp
zyn_reference_build_patches: tools/zyn-reference/patches
zyn_project_fixtures: tests/AquariumSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored
package: AquariumSynth.Dsl 0.1.0-aquarium.1
zyn_project_fixtures: tests/AquaSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored
package: AquaSynth.Dsl 0.1.0-aquasynth.1
modules:
model: src/AquariumSynth.Dsl/Model.cs
parser: src/AquariumSynth.Dsl/PatchScript.cs
faust: src/AquariumSynth.Dsl/FaustExport.cs
faust_rendering: src/AquariumSynth.Dsl/FaustExport.cs
dx7_reference_render_tests: tests/AquariumSynth.Dsl.Tests/Dx7ReferenceParityTests.cs
zyn_reference_reader: src/AquariumSynth.Dsl/ZynInstrument.cs
package_boundary_tests: tests/AquariumSynth.Dsl.Tests/PackageBoundaryTests.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
model: src/AquaSynth.Dsl/Model.cs
parser: src/AquaSynth.Dsl/PatchScript.cs
faust: src/AquaSynth.Dsl/FaustExport.cs
faust_rendering: src/AquaSynth.Dsl/FaustExport.cs
dx7_reference_render_tests: tests/AquaSynth.Dsl.Tests/Dx7ReferenceParityTests.cs
zyn_reference_reader: src/AquaSynth.Dsl/ZynInstrument.cs
package_boundary_tests: tests/AquaSynth.Dsl.Tests/PackageBoundaryTests.cs
sfxr: src/AquaSynth.Dsl/SfxrParams.cs
analysis: src/AquaSynth.Dsl/AudioAnalysis.cs
scoring: src/AquaSynth.Dsl/ScriptMetrics.cs
presets: src/AquaSynth.Dsl/Presets.cs
built_in_scripts: src/AquaSynth.Dsl/BuiltInScripts.cs
boundaries:
leave_in_rust:
- sample renderer
@ -57,8 +57,8 @@ boundaries:
- structured .aqua patch library
checks:
default: dotnet test
package: dotnet pack src/AquariumSynth.Dsl/AquariumSynth.Dsl.csproj -c Release
package: dotnet pack src/AquaSynth.Dsl/AquaSynth.Dsl.csproj -c Release
faust_validation: automatic in tests when Faust is installed
dexed_py_reference: optional in tests when AQUARIUM_DX7_PYTHON or a Python with dexed-py is available
dexed_py_reference: optional in tests when AQUASYNTH_DX7_PYTHON or a Python with dexed-py is available
zyn_reference_source: pinned submodule used only for parity tests; never package content
zyn_upstream_instrument_bank: nested GPL submodule used only for test/development pressure surveys and oracle renders; never package content

View file

@ -1,6 +1,6 @@
{
"project": "AquariumSynthCSharp",
"mission": "Own Aquarium Synth patch authoring, analysis, preset mapping, and Faust generation in C# for the Aquarium/Vortice engine path.",
"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",
@ -69,7 +69,7 @@
"state/spine.yaml"
],
"reference_strategy": {
"purpose": "Use external synths and DSP corpora as parity pressure for Aquarium's patch DSL and Faust emitter: the DSL must reproduce behavior in terse, readable Aquarium syntax, not clone foreign internals.",
"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",
@ -80,13 +80,13 @@
"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 Aquarium 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, Aquarium 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 Aquarium 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 Aquarium 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 Aquarium 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.",
"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 Aquarium 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 Aquarium 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 Aquarium `spectrum` was not a close Zyn PAD emulation. User listening identified Zyn as pure wineglass and Aquarium as angry bees. Cutting unearned air/noise/drive/soft-clip from the rebuild, adding interpolated wavetable reads, and raising Aquarium 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. `RebuildFirstPadAsAquariumScript` 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 Aquarium 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. Aquarium 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.",
"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": {
@ -97,12 +97,12 @@
"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.",
"aquarium_requirement": "Aquarium DSL patches declare stable exposed parameters with path/default/min/max/step metadata so callers can vary sound without recompiling.",
"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 test AquariumSynthCSharp.slnx --no-restore",
"result": "Mapped Zyn base-function modulation and adaptive harmonics for Organ Choir; C# suite 107 passed",
"commit": "pending Zyn Organ Choir OscilGen source-table mapping"
"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"
}
}

View file

@ -1,18 +1,18 @@
# Scratch
Current standing order: grow AquariumSynthCSharp through external reference
Current standing order: grow AquaSynth 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 synth library while AquaSynth work continues elsewhere.
Doctrine update:
- External synths are parity pressure for Aquarium's DSL, not internals to
- External synths are parity pressure for AquaSynth's DSL, not internals to
clone. Only consume targets that can be backed by parity tests proving
Aquarium can reproduce the behavior in terse, readable syntax.
AquaSynth can reproduce the behavior in terse, readable syntax.
- Parsing a synth format is inventory. Rebuilding and testing behavior is the
proof.
- Pinned reference synths are test-only oracles, not Aquarium organs. ZynAddSubFX
- Pinned reference synths are test-only oracles, not AquaSynth organs. ZynAddSubFX
now lives under `external/zynaddsubfx` as GPL development material pinned at
`3ab608c432996ba4d582176572c0b0f82328c825`; it must not enter the NuGet
package or runtime dependency graph.
@ -44,7 +44,7 @@ Current slice:
same as the references. Treat the remaining high log-mel values as regression
pressure and diagnostic smoke, not as a mandate to keep sanding sounds that
have already landed perceptually.
- Verification: `dotnet test AquariumSynthCSharp.slnx --no-restore`: 107
- Verification: `dotnet test AquaSynth.slnx --no-restore`: 107
passed.
- Next pressure can leave this Zyn PAD batch and move to the roadmap's next
synth/abstraction rung; keep sin2x and broader OscilGen/PAD source-table
@ -58,12 +58,12 @@ Current slice:
bandwidth, bandwidth scale, harmonic profile, and harmonic-position warp.
`PadSynthWaveform` uses the Zyn PAD profile/bandwidth formulas for those
banks and keeps the old generic PADsynth path for ordinary `spectrum`.
- `ZynInstrumentReader.RebuildFirstPadAsAquariumScript` now emits readable
- `ZynInstrumentReader.RebuildFirstPadAsAquaSynthScript` now emits readable
neutral `pad_mode=`, `pad_bandwidth=`, `pad_bwscale=`, `pad_profile=...`,
and `pad_position=...` fields, and maps Zyn PAD volume with the source
exponential gain curve instead of the old tiny linear `/500` guess. The
parser still accepts older `zyn_*` aliases for parity artifacts, but authored
`.aqua` should use Aquarium-native PAD terms.
`.aqua` should use AquaSynth-native PAD terms.
- The translator now expands a useful subset of Zyn OscilGen base-function
harmonic content plus spectrum adjustment before feeding PAD synthesis. This
is a coherent source-table owner, but current parity is mixed: it improves
@ -71,7 +71,7 @@ Current slice:
regress. Treat it as live pressure on OscilGen coverage, not solved Zyn
oscillator parity.
- Latest focused verification:
`dotnet test tests\AquariumSynth.Dsl.Tests\AquariumSynth.Dsl.Tests.csproj --no-restore --filter "FullyQualifiedName~ZynInstrumentTests|FullyQualifiedName~ZynReferenceParityTests"`:
`dotnet test tests\AquaSynth.Dsl.Tests\AquaSynth.Dsl.Tests.csproj --no-restore --filter "FullyQualifiedName~ZynInstrumentTests|FullyQualifiedName~ZynReferenceParityTests"`:
13 passed.
- Latest upstream PAD batch after OscilGen subset:
`Soft Pad` log-mel `0.342738`, score `0.443083`;
@ -119,7 +119,7 @@ Current user-ear correction:
with two enabled PAD kit items across the full key range. The old parity path
rendered and rebuilt only kit item 0. The upstream PAD test now sums enabled
PAD kit notes and passes per-kit table roots into
`RebuildEnabledPadsAsAquariumScript`; generated Aqua emits
`RebuildEnabledPadsAsAquaSynthScript`; generated Aqua emits
`doublepadbass_0` and `doublepadbass_1`.
- Latest DoublePadBass after multi-kit rebuild: log-mel `0.493451`, score
`0.376854`, RMS ratio `0.826864`. Raising the volume cap to `3.0` worsened it
@ -152,7 +152,7 @@ Current user-ear correction:
- User then heard the tone as right but the Zyn reference hitting harder at the
beginning. The missing authority was Zyn's PAD filter envelope: Aqua had only
the static cutoff, while Zyn adds ADSR-filter octave offsets before the
global filter. Aquarium now has a general low-pass rate/level envelope
global filter. AquaSynth now has a general low-pass rate/level envelope
surface (`lpf_env=rl`, `lpf_start`, `lpf_rates`, `lpf_levels`) and the Zyn
importer maps `FILTER_ENVELOPE` into it. Latest DoublePadBass after this
pass: log-mel `0.241993`, envelope distance `0.554658`, RMS ratio `0.456173`,
@ -179,7 +179,7 @@ Current user-ear correction:
`log2(note/440) * tracking` rule. Aqua now applies that when importing PAD
global filters.
- Ghost still had too much upper tail, so the next cut was not a Ghost-only
brightness knob. Aquarium now has explicit low-pass Q via `lpf_q`/`lpq` on
brightness knob. AquaSynth now has explicit low-pass Q via `lpf_q`/`lpq` on
the filter model. Zyn PAD analog low-pass `q` imports through
`exp((q/127)^2 * ln(1000)) - 0.9`; Ghost emits `lpf_q=0.499021`, which Faust
lowers through `fi.resonlp(..., max(0.1, lpf_q), 1.0)`.
@ -219,7 +219,7 @@ Current user-ear correction:
by ear, with Organ Choir still a bit tinny and sin2x still overdoing a high
harmonic. The sin2x issue was a real OscilGen indexing bug, not a fixture
brightness preference: Zyn oscillator filters receive one-based harmonic
indices (`filter(1)` is the first harmonic), while Aquarium had translated
indices (`filter(1)` is the first harmonic), while AquaSynth had translated
them through a zero-based index and moved the type-13 spike filter from h2 to
h3. `ZynOscilFilterGain` now preserves Zyn's one-based index, and the
upstream sin2x regression asserts h2 dominates h3 by more than 10x.
@ -252,16 +252,16 @@ Completed this slice:
randomness control, or pitch-zone behavior. It is a source-level FFT
partial-cloud authority for readable PAD rebuilds.
- Focused verification before wavetable replacement passed:
`dotnet test tests\AquariumSynth.Dsl.Tests\AquariumSynth.Dsl.Tests.csproj --no-restore --filter "FullyQualifiedName~SpectralBank|FullyQualifiedName~ZynStyleReferenceRebuildsParseExportAndDeclarePressure|FullyQualifiedName~BuiltInReferenceScriptsParseAndExportFaust"`:
`dotnet test tests\AquaSynth.Dsl.Tests\AquaSynth.Dsl.Tests.csproj --no-restore --filter "FullyQualifiedName~SpectralBank|FullyQualifiedName~ZynStyleReferenceRebuildsParseExportAndDeclarePressure|FullyQualifiedName~BuiltInReferenceScriptsParseAndExportFaust"`:
4 passed.
- Wavetable replacement verification:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
94 passed.
- FFT table generator focused verification:
`dotnet test tests\AquariumSynth.Dsl.Tests\AquariumSynth.Dsl.Tests.csproj --no-restore --filter "FullyQualifiedName~SpectralBank|FullyQualifiedName~FaustCompilerValidatesSpectralBank|FullyQualifiedName~ZynStyleReferenceRebuildsParseExportAndDeclarePressure"`:
`dotnet test tests\AquaSynth.Dsl.Tests\AquaSynth.Dsl.Tests.csproj --no-restore --filter "FullyQualifiedName~SpectralBank|FullyQualifiedName~FaustCompilerValidatesSpectralBank|FullyQualifiedName~ZynStyleReferenceRebuildsParseExportAndDeclarePressure"`:
4 passed.
- Full verification after FFT table generator:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
94 passed.
Previous slice:
@ -276,10 +276,10 @@ Previous slice:
- Arbitrary Zyn free-envelope point curves remain missing pressure. This slice
is the coherent staged-envelope rung, not a free-mode clone.
- Focused verification passed:
`dotnet test tests\AquariumSynth.Dsl.Tests\AquariumSynth.Dsl.Tests.csproj --no-restore --filter "FullyQualifiedName~LayeredVoiceRateLevelEnvelope|FullyQualifiedName~ZynStyleReferenceRebuildsParseExportAndDeclarePressure|FullyQualifiedName~BuiltInReferenceScriptsParseAndExportFaust"`:
`dotnet test tests\AquaSynth.Dsl.Tests\AquaSynth.Dsl.Tests.csproj --no-restore --filter "FullyQualifiedName~LayeredVoiceRateLevelEnvelope|FullyQualifiedName~ZynStyleReferenceRebuildsParseExportAndDeclarePressure|FullyQualifiedName~BuiltInReferenceScriptsParseAndExportFaust"`:
3 passed.
- Full verification with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
91 passed.
Previous slice:
@ -294,7 +294,7 @@ Previous slice:
- This is not Zyn ADDsynth parity theater. Phase, bandwidth, oscillator
shaping, and exact free-envelope behavior remain explicit pressure.
- Verified with bundled Python/dexed-py still wired:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
90 passed.
Previous slice:
@ -309,7 +309,7 @@ Previous slice:
Future additive-bank, PAD-source, free-envelope, and formant-motion syntax
now has somewhere coherent to attach.
- Verified with bundled Python/dexed-py still wired:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
88 passed.
Previous slice:
@ -328,12 +328,12 @@ Previous slice:
- Survey conclusion: named kit/layer routing should probably precede syntax
golf for additive banks, PAD sources, or formant motion.
- Verified with bundled Python/dexed-py still wired:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
86 passed.
Previous slice:
- Added first Zyn-style Aquarium rebuilds for the project-authored additive
- Added first Zyn-style AquaSynth rebuilds for the project-authored additive
lead, PAD texture, and vocal/formant layer fixtures. They live in
`BuiltInScripts` and `ReferenceRebuildCatalog.ZynRebuilds`.
- The rebuild tests parse/export every Zyn script and compare matched feature
@ -343,7 +343,7 @@ Previous slice:
normal voices, named kit/layer routing with per-layer effect sends, and richer
formant/vowel morphing. No new runtime surface was added yet.
- Verified with bundled Python/dexed-py still wired:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
83 passed.
Previous slice:
@ -353,13 +353,13 @@ Previous slice:
gzip-compressed XML, extracts instrument metadata and kit items, and emits
neutral `ReferencePatch` features.
- Added project-authored Zyn fixtures under
`tests/AquariumSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored` instead
`tests/AquaSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored` instead
of vendoring upstream preset-bank files with unclear root provenance.
- Current Zyn classifier detects active ADD/SUB/PAD engines, enabled kit items,
layering, envelopes, free envelopes, LFOs, filters, formant filters, and
effects. This is inventory for choosing rebuild targets, not translation yet.
- Verified with bundled Python/dexed-py still wired:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
81 passed.
Previous slice:
@ -392,7 +392,7 @@ Previous slice:
too aggressively and broke PRC, `Piano Bass`, `RES SYNTH1`, and `ANLGSYN 1`;
release duration remains part of the broader DX7 envelope model pressure.
- Verified with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
76 passed.
Previous slice:
@ -414,7 +414,7 @@ Previous slice:
and does not deserve to live. Mooger's "too hard" harmonics remain unresolved
overtone-emphasis pressure, not a solved high-band-energy problem.
- Focused verification with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter "Dx7SysExTests|PublicDomainDx7MoogerAndPianoBassMeetRenderedParityWhenInstalled|PublicDomainDx7AnlgSyn1KeepsBuzzingModulationWhenInstalled|PublicDomainDx7MellowSoloWritesPressureWavsWhenInstalled|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled"`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter "Dx7SysExTests|PublicDomainDx7MoogerAndPianoBassMeetRenderedParityWhenInstalled|PublicDomainDx7AnlgSyn1KeepsBuzzingModulationWhenInstalled|PublicDomainDx7MellowSoloWritesPressureWavsWhenInstalled|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled"`:
28 passed.
Previous slice:
@ -436,7 +436,7 @@ Previous slice:
more Dexed-shaped on paper, but worsened `MELLOWSOLO` and `ANLGSYN 1`, so it
does not belong in the live machine yet.
- Focused verification with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter "Dx7SysExTests|PublicDomainDx7MoogerAndPianoBassMeetRenderedParityWhenInstalled|PublicDomainDx7AnlgSyn1KeepsBuzzingModulationWhenInstalled|PublicDomainDx7MellowSoloWritesPressureWavsWhenInstalled|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled"`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter "Dx7SysExTests|PublicDomainDx7MoogerAndPianoBassMeetRenderedParityWhenInstalled|PublicDomainDx7AnlgSyn1KeepsBuzzingModulationWhenInstalled|PublicDomainDx7MellowSoloWritesPressureWavsWhenInstalled|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled"`:
28 passed.
Previous slice:
@ -456,7 +456,7 @@ Previous slice:
`DX1 LEAD B` score `.45156077`, log-mel `.43533683`, RMS `.8193191`;
`MELLOWSOLO` score `.5359293`, log-mel `.39420125`, RMS `.99559265`.
- Verified with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
74 passed.
Previous slice:
@ -476,7 +476,7 @@ Previous slice:
- Fresh `ANLGSYN 1`: score `.7245976`, log-mel `.18460114`, envelope
`.119950555`, RMS `.97143364`, zero-crossing `.97149533`.
- Verified with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
74 passed.
Earlier slice:
@ -505,7 +505,7 @@ Earlier slice:
- 1.2-2.5 kHz candidate/reference energy: `.008 -> .656`
- 2.5-5 kHz candidate/reference energy: near zero -> `.321`
- Verified with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
73 passed.
Earlier slice:
@ -523,13 +523,13 @@ Earlier slice:
focused gates. Latest metrics: log-mel `.19427659`, envelope `.14924917`,
zero-crossing `.94662774`, centroid `.9182808`, score `.659334`.
- Verified with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
73 passed.
Previous slice:
- Fixed the `Piano Bass` octave bug. The parsed voice has `transpose=12`; the
old generated Aquarium script hardcoded `freq=261.6256`, so it rendered an
old generated AquaSynth script hardcoded `freq=261.6256`, so it rendered an
octave too high. `Dx7SysEx.NoteFrequencyHz(midiNote, transpose)` now treats
transpose `24` as neutral and transpose `12` as one octave down.
- `Dx7VoiceProbeScript` now uses the voice transpose for graph frequency and
@ -540,14 +540,14 @@ Previous slice:
- Fixed `Piano Bass` metrics: log-mel `.16180529`, envelope `.13544041`,
zero-crossing `.905511`, score `.64267576`.
- Verified with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
71 passed.
Previous slice:
- Added `PublicDomainDx7AnalogCommunityVoicesMeetBroadRenderedParityWhenInstalled`,
which renders actual public-domain `analog1.syx` community voices through
Dexed and the current Aquarium DX7 lowering. Kept four broad parity voices:
Dexed and the current AquaSynth DX7 lowering. Kept four broad parity voices:
`ANLGSYN 1`, `{ Mooger }`, `Piano Bass`, and `RES SYNTH1`.
- The test writes WAVs/reports under
`artifacts/parity/dx7-community-analog1/<voice>/` and gates log-mel
@ -561,7 +561,7 @@ Previous slice:
from the passing gate because log-mel and zero-crossing mismatch were too
large. That is pressure, not library stock.
- Verified with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
70 passed.
Previous slice:
@ -582,7 +582,7 @@ Previous slice:
intentionally not part of the patch library: they are useful measuring tools,
not pleasant or reusable stock.
- Verified with bundled Python/dexed-py:
`AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore`:
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
69 passed.
Previous slice:
@ -600,7 +600,7 @@ Previous slice:
- Added a test contract: every `.aqua` under `patches/` must parse through
`PatchScript.Parse` and export Faust, while package-boundary tests keep
`patches/` and `.aqua` files out of the NuGet package.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 67 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 67 passed.
Previous slice:
@ -614,7 +614,7 @@ Previous slice:
`0.9912135`, zero-crossing ratio `0.95325506`, centroid ratio `1.1156512`,
score `0.7546445`.
- Added `Dx7SysEx.ApproximateAppliedRateLevelEnvelope`, which traces the
Dexed-style block-interpolated applied EG gain and lowers it into Aquarium's
Dexed-style block-interpolated applied EG gain and lowers it into AquaSynth's
curved staged envelope surface, normalized so operator output level remains a
separate authority.
- Retried hard `PRC SYNTH1` with the applied-envelope lowering. Latest focused
@ -638,8 +638,8 @@ Previous slice:
`artifacts/parity/dx7-envelope-trace/egstep.csv`. The first rows show both
DX7 raw state and applied gain: raw jumps to `2` immediately, while applied
gain ramps from `.03125` to `2` over the first 64-sample block and remains
near `1` around 20 ms. Aquarium `env=rl` is a different contour entirely.
The artifact now also includes a curved Aquarium staged-envelope candidate
near `1` around 20 ms. AquaSynth `env=rl` is a different contour entirely.
The artifact now also includes a curved AquaSynth staged-envelope candidate
that tracks the applied-gain contour far more closely.
- Tried adding a first `env=dx7` runtime lowering, then cut it. The trace
matched the Python `graph.py` envelope but not Dexed plugin audio, so the
@ -690,7 +690,7 @@ Previous slice:
Previous slice:
- Completed the feedback calibration rung. Isolated Dexed feedback sweeps fit a
nonlinear Aquarium feedback table: `0, .01, .02, .05, .10, .19, .38, .66`
nonlinear AquaSynth feedback table: `0, .01, .02, .05, .10, .19, .38, .66`
for DX7 feedback values `0..7`.
- Added `Dx7SysEx.OperatorFeedbackAmount` and moved the PRC feedback lowering
off the old `voice.Feedback * 0.04` constant. PRC barely changed because
@ -701,7 +701,7 @@ Previous slice:
- Completed the second calibration rung for isolated two-operator FM. A Dexed
sweep fits full-scale DX7 modulation at about `12.55` radians. Given the
current Aquarium Faust formula, that maps to an Aquarium route index of
current AquaSynth Faust formula, that maps to an AquaSynth route index of
`6.275` for a full-level modulator.
- Added `Dx7SysEx.OperatorModulationRouteIndex` and a regression test for the
calibrated phase-deviation scale. The hard PRC probe applies that scale only
@ -737,7 +737,7 @@ Previous slice:
`<= 0.255` plus a loose aggregate score floor instead of treating aggregate
score as the main judge.
- Spectral diagnosis showed the Dexed reference has dominant peaks around
`393`, `131`, `524`, and `656` Hz. The prior Aquarium candidate had too
`393`, `131`, `524`, and `656` Hz. The prior AquaSynth candidate had too
little `80-160` Hz body when based at `392`, while the corrected DX7 note
basis restored the low body but exposed weak mid harmonic structure and high
centroid/zero-crossing mismatch.
@ -769,7 +769,7 @@ Previous slice:
- Added a hard DX7 `PRC SYNTH1` rendered parity probe that writes listening
artifacts when `dexed-py` and Faust are available:
`artifacts/parity/dx7-prc-synth1/reference-dexed.wav`,
`candidate-aquarium.wav`, `candidate.aqua`, and `report.txt`.
`candidate-aquasynth.wav`, `candidate.aqua`, and `report.txt`.
- The probe rebuilds `analog1.syx` voice 17 with readable operator graph syntax
and staged `env=rl` operator envelopes. It asserts a modest hard-target score
floor instead of pretending exact DX7 parity is solved.
@ -792,7 +792,7 @@ Previous slice:
that emits staged envelope script specs from four-rate/four-level operator EG
data.
- Verified the surface structurally and through the render path:
`dotnet test AquariumSynthCSharp.slnx --no-restore`: 53 passed.
`dotnet test AquaSynth.slnx --no-restore`: 53 passed.
- Retried the hard `PRC SYNTH1` probe with direct DX7 rate/level lowering. The
best quick score was only ~0.316, worse than the hand-tweaked ADSR candidate.
The DSL can now express the missing contour shape, but DX7 EG timing/gain
@ -801,33 +801,33 @@ Previous slice:
Previous slice:
- Added `Dx7OperatorLevelApproximation`, which distills DX7 operator output
level plus key/velocity scaling into a normal Aquarium `level=`-style value.
level plus key/velocity scaling into a normal AquaSynth `level=`-style value.
This remains DX7 reference-import knowledge, not a generic DSL feature.
- Retried `PRC SYNTH1` with effective levels and envelope tweaks. Static level
mapping alone did not rescue the hard target: quick probes moved from ~0.24
to ~0.34, and envelope tweaks reached ~0.42. The next missing pressure is
DX7-style operator envelope/gain evolution, not just static operator levels.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 50 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 50 passed.
Previous slice:
- Tried the harder `analog1.syx` voice 17, `PRC SYNTH1` (algorithm 8). The
initial routed Aquarium candidate scored badly (~0.23), which exposed a real
initial routed AquaSynth candidate scored badly (~0.23), which exposed a real
missing invariant rather than a threshold-tuning problem.
- Reworked operator feedback emission from a cyclic smoothed self-reference to
a renderable Faust feedback expression using delayed recursion. Added
`FaustCompilerRendersOperatorFeedbackWhenInstalled`.
- Exact DX7 feedback scaling and EG behavior are still not solved, but feedback
no longer makes the Faust render path fall over.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 49 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 49 passed.
Previous slice:
- Added the first thresholded DX7 rendered-audio parity rebuild:
`BuiltInScripts.Dx7StylePublicDomainMcMm53` maps public-domain
`analog1.syx` voice 13, `MC-MM 5-3`, to a terse Aquarium sine patch.
`analog1.syx` voice 13, `MC-MM 5-3`, to a terse AquaSynth sine patch.
- Added `PublicDomainDx7McMm53MeetsFirstRenderedParityThresholdWhenInstalled`,
which renders the DX7 reference through `dexed-py`, renders the Aquarium
which renders the DX7 reference through `dexed-py`, renders the AquaSynth
candidate through Faust-generated C#, and asserts score, log-mel distance,
envelope distance, duration/RMS ratios, zero-crossing ratio, and centroid
ratio.
@ -835,43 +835,43 @@ Previous slice:
DX7 operator execution. The next DX7 pressure should be a harder voice that
audibly needs the operator graph.
- Verified with bundled Python plus `dexed-py`: parity test passed.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 48 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 48 passed.
Previous slice:
- Added a public-domain DX7 SysEx fixture from Musical Artifacts artifact 152:
`tests/AquariumSynth.Dsl.Tests/Fixtures/Dx7/PublicDomain/analog1.syx`, with
`tests/AquaSynth.Dsl.Tests/Fixtures/Dx7/PublicDomain/analog1.syx`, with
provenance and SHA-256 recorded beside it.
- Added a test-only `dexed-py` reference renderer. It uses
`AQUARIUM_DX7_PYTHON` when set, otherwise probes `py`, `python`, and
`AQUASYNTH_DX7_PYTHON` when set, otherwise probes `py`, `python`, and
`python3`; if `dexed-py` is absent, the render test returns without turning
optional tooling into a hard dependency.
- Added a `.nupkg` boundary test that packs `AquariumSynth.Dsl` and asserts
- Added a `.nupkg` boundary test that packs `AquaSynth.Dsl` and asserts
test fixtures, SysEx banks, and Python helpers are not shipped.
- Verified with bundled Python plus `dexed-py`: the DX7 fixture renders through
Dexed successfully.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 47 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 47 passed.
Previous slice:
- Added `FaustCompiler.RenderAsync`, which compiles Aquarium Faust output to
- Added `FaustCompiler.RenderAsync`, which compiles AquaSynth Faust output to
Faust-generated C#, runs it in a temp .NET project, and returns a mono float
sample buffer for analysis.
- Added a render test that proves a generated Aquarium patch produces non-silent
- Added a render test that proves a generated AquaSynth patch produces non-silent
audio and can be compared through `AudioAnalyzer`.
- DX7 audio parity is still not claimed: the candidate renderer exists, but the
reference side needs Dexed output or a captured licensed fixture.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 44 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 44 passed.
Previous slice:
- Added `Dx7EnvelopeApproximation`, which lowers a DX7 four-rate/four-level EG
to a labeled Aquarium ADSR approximation plus gate duration.
to a labeled AquaSynth ADSR approximation plus gate duration.
- Reference rebuilds now record `operator_envelope_approximation` as matched
pressure and `operator_envelope_exactness` as still missing.
- Doctrine now states that DX7 EG approximation is not exact DX7 envelope
execution.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 43 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 43 passed.
Previous slice:
@ -882,7 +882,7 @@ Previous slice:
the exact `/opgraphs/...` field paths.
- Fixed AD operator envelope binding to use `/env/decay` for the second AD
value, matching the ADSR model instead of calling it release by accident.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 42 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 42 passed.
Previous slice:
@ -892,7 +892,7 @@ Previous slice:
`[midi:on][nvoices:8]` option and `freq`, `gain`, `gate` controls.
- Host/MIDI playback no longer emits per-voice `/voices/0/note/frequency` and
`/voices/0/note/gate` controls; Faust architecture owns allocation.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 41 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 41 passed.
Previous slice:
@ -905,7 +905,7 @@ Previous slice:
- Added host note mode for MIDI-oriented patches through stable note frequency
and note gate controls in generated Faust.
- Moved built-in authoring examples off `punch=` and onto `sustain_level=`.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 40 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 40 passed.
Previous slice:
@ -915,7 +915,7 @@ Previous slice:
envelope forms for operator declarations.
- Kept compact `ops=`/`edges=` syntax as parser/interchange scaffolding, but it
is no longer the built-in authoring example.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 36 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 36 passed.
Previous slice:
@ -927,21 +927,21 @@ Previous slice:
- Updated the DX7 algorithm-8 rebuild to use a real operator graph for the
topology. The remaining missing feature is exact DX7 feedback-register timing
and DX7 rate/level envelopes, not graph ownership.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 35 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 35 passed.
Earlier rebuild slice:
- Added `ReferenceRebuild` and `ReferenceRebuildCatalog` for explicit
reference-target rebuild attempts.
- Added two DX7-style Aquarium DSL rebuilds:
- Added two DX7-style AquaSynth DSL rebuilds:
- `dx7/algo32-additive-organ`: algorithm 32's six-carrier additive shape,
which maps cleanly to six Aquarium voices.
which maps cleanly to six AquaSynth voices.
- `dx7/algo8-bright-pair`: algorithm 8's two-carrier FM shape, which is only
an approximation because the current voice DSL cannot express
`6->5`, `4+5->3`, `2->1`, or operator-local self-feedback.
- Added matched/missing feature records so topology mismatch becomes evidence
instead of being hidden inside prose.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 34 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 34 passed.
Earlier topology slice:
@ -954,7 +954,7 @@ Earlier topology slice:
`modulation_edge_count`, `feedback_sources`, and
`self_feedback_operators`.
- Representative tests cover algorithm 8, algorithm 16, and algorithm 32.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 31 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 31 passed.
Earlier slice:
@ -965,7 +965,7 @@ Earlier slice:
- Added `Dx7Voice.ToReferencePatch` and structural feature extraction so DX7
voices can pressure the reference model before translation exists.
- Checksum validation rejects bad wrapped SysEx payloads.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 28 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 28 passed.
Parameter slice:
@ -982,7 +982,7 @@ Parameter slice:
parameter default as the graph value.
- Faust emission substitutes the parameter expression only at the bound field
site. Unbound parameters still emit a warning.
- Verified with `dotnet test AquariumSynthCSharp.slnx --no-restore`: 23 passed.
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 23 passed.
Next likely slice:

View file

@ -1,4 +1,4 @@
project: AquariumSynthCSharp
project: AquaSynth
spine_version: 1
updated: 2026-05-15T14:15:00+01:00
@ -7,58 +7,58 @@ prime_directive: >
not to justify piling parameters onto the current patch DSL.
current_objective: >
Turn AquariumSynthCSharp into a reference-driven patch authoring and Faust
Turn AquaSynth 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.
AquaSynth'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
faust_renderer: src/AquariumSynth.Dsl/FaustExport.cs
operator_graph_model: src/AquariumSynth.Dsl/Model.cs
analysis: src/AquariumSynth.Dsl/AudioAnalysis.cs
built_ins: src/AquariumSynth.Dsl/BuiltInScripts.cs
parser: src/AquaSynth.Dsl/PatchScript.cs
model: src/AquaSynth.Dsl/Model.cs
faust_emitter: src/AquaSynth.Dsl/FaustExport.cs
faust_renderer: src/AquaSynth.Dsl/FaustExport.cs
operator_graph_model: src/AquaSynth.Dsl/Model.cs
analysis: src/AquaSynth.Dsl/AudioAnalysis.cs
built_ins: src/AquaSynth.Dsl/BuiltInScripts.cs
patch_library: patches
dx7_references: src/AquariumSynth.Dsl/Dx7SysEx.cs
dx7_reference_fixtures: tests/AquariumSynth.Dsl.Tests/Fixtures/Dx7/PublicDomain
dx7_reference_renderer_tests: tests/AquariumSynth.Dsl.Tests/Dx7ReferenceParityTests.cs
dx7_references: src/AquaSynth.Dsl/Dx7SysEx.cs
dx7_reference_fixtures: tests/AquaSynth.Dsl.Tests/Fixtures/Dx7/PublicDomain
dx7_reference_renderer_tests: tests/AquaSynth.Dsl.Tests/Dx7ReferenceParityTests.cs
zyn_reference_source: external/zynaddsubfx
zyn_upstream_instrument_bank: external/zynaddsubfx/instruments
zyn_reference_build_script: tools/zyn-reference/build-zyn-reference.ps1
zyn_pad_reference_renderer: tools/zyn-reference/ZynPadReference.cpp
zyn_reference_reader: src/AquariumSynth.Dsl/ZynInstrument.cs
zyn_project_fixtures: tests/AquariumSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored
reference_rebuilds: src/AquariumSynth.Dsl/ReferenceRebuilds.cs
tests: tests/AquariumSynth.Dsl.Tests/PatchScriptTests.cs
zyn_reference_reader: src/AquaSynth.Dsl/ZynInstrument.cs
zyn_project_fixtures: tests/AquaSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored
reference_rebuilds: src/AquaSynth.Dsl/ReferenceRebuilds.cs
tests: tests/AquaSynth.Dsl.Tests/PatchScriptTests.cs
doctrine: docs/audio-doctrine.md
dx7_calibration_plan: docs/dx7-calibration-plan.md
zyn_pressure_survey: docs/zynaddsubfx-pressure-survey.md
roadmap: docs/reference-synth-roadmap.md
parameter_contracts: src/AquariumSynth.Dsl/Model.cs
parameter_contracts: src/AquaSynth.Dsl/Model.cs
live_invariants:
- Aquarium owns its patch graph; external synths are references, not runtime dependencies.
- AquaSynth owns its patch graph; external synths are references, not runtime dependencies.
- Patch scripts lower into explicit graph records before Faust emission.
- Operator graphs own FM topology: operators, modulation edges, carriers, and feedback. Do not hide this authority inside Voice fields.
- Readable operator graph authoring uses `operator`, `route`, and `carrier` declarations; compact `ops=`/`edges=` is only scaffolding/interchange.
- 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 Aquarium graphs for local allocation.
- 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.
- Aquarium 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 AquariumSynthCSharp, not Aquarium.
- 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.
- Parameter binding uses field-site references like `lpf=@/macro/brightness`; do not add target-routing semantics unless a later map proves a new authority is needed.
- Operator graph parameter binding also uses exact field-site paths under `/opgraphs/...`; do not add graph-wide macro routing.
- Operator feedback must render through Faust as delayed recursion, not as a direct cyclic self-reference.
- DX7 operator output level approximation belongs in reference import/rebuild helpers, not the generic DSL model.
- DX7 carrier output amplitude follows the measured `2^((outputLevel - 99) / 8)` curve; do not restore the old internal-level proxy.
- DX7 isolated two-operator modulation uses Aquarium route index `6.275` for full-level modulators; do not apply that blindly to cascaded stacks.
- DX7 isolated two-operator modulation uses AquaSynth route index `6.275` for full-level modulators; do not apply that blindly to cascaded stacks.
- DX7 feedback lowers through `Dx7SysEx.OperatorFeedbackAmount`; feedback value 5 remains 0.19 for PRC, while feedback value 7 is much hotter at 2.2 to restore community-patch buzz. Do not restore PRC-local `feedback * constant` folklore.
- DX7 ratio-mode detune lowers through `Dx7SysEx.OperatorFrequencyRatio`; do not silently drop detune from operator graph scripts.
- DX7 voice transpose lowers through `Dx7SysEx.NoteFrequencyHz`; transpose 24 is neutral, 12 is one octave down. Do not hardcode `freq=261.6256` for imported voices.
- DX7 fixed-frequency operators lower through `Dx7SysEx.FixedOperatorFrequencyHz` as absolute Hz divided by the graph base frequency; do not treat fixed mode as a note ratio.
- DX7 ROM `COM` output compensation is not a carrier-balance authority in Aquarium lowering; `OperatorOutputCompensation` returns unity and graph gain owns loudness recovery.
- DX7 ROM `COM` output compensation is not a carrier-balance authority in AquaSynth lowering; `OperatorOutputCompensation` returns unity and graph gain owns loudness recovery.
- DX7 operator key-scaling and rate-scaling lower against the effective played MIDI note after voice transpose; do not hardcode MIDI 60 inside imported operator level or envelope timing.
- DX7 route lowering is topology-aware: isolated direct branches use `OperatorModulationRouteIndex` (`6.275`); summed edges and direct edges feeding summed modulation use `SummedOperatorModulationRouteIndex` (`6.0`).
- `SummedOperatorModulationRouteIndex` is backed by the hard PRC target, a project-authored algorithm-8 stack probe, and split cascade/summed-pair probes; do not restore the older under-bright `1.6` scale.
@ -75,15 +75,15 @@ live_invariants:
- Do not replace applied-envelope release with a naive traced threshold duration; it shortened releases too aggressively and broke broader DX7 parity.
- Recompile only for graph shape changes; vary declared parameters through the hosted DSP control API.
- Reference fixtures must preserve provenance, license status, source hash, and feature pressure.
- Public-domain reference fixtures, GPL Zyn instrument-bank fixtures, dexed-py helpers, and pinned ZynAddSubFX source are test/development material only; the NuGet package must ship only AquariumSynth.Dsl.
- ZynAddSubFX import starts as feature extraction and then explicit, scoped rebuilds. Upstream GPL `.xiz` instruments may be used as test-only pressure/parity feedstock with provenance reports, while project-authored `.xiz` fixtures remain the clean baseline. The Zyn engine source and instrument bank are pinned under `external/zynaddsubfx` for parity oracles only, not Aquarium runtime code.
- Public-domain reference fixtures, GPL Zyn instrument-bank fixtures, dexed-py helpers, and pinned ZynAddSubFX source are test/development material only; the NuGet package must ship only AquaSynth.Dsl.
- ZynAddSubFX import starts as feature extraction and then explicit, scoped rebuilds. Upstream GPL `.xiz` instruments may be used as test-only pressure/parity feedstock with provenance reports, while project-authored `.xiz` fixtures remain the clean baseline. The Zyn engine source and instrument bank are pinned under `external/zynaddsubfx` for parity oracles only, not AquaSynth runtime code.
- Named `layer` declarations own Zyn-style kit/layer identity: engine tag, optional key range metadata, default gain, and effect-send labels. They lower to ordinary voices for now; do not smuggle PAD/free-envelope/effect behavior into them.
- Additive `harmonics` declarations own authored ratio/gain partial banks under named layers. They lower to ordinary voices for now; do not pretend this covers Zyn ADDsynth phase, bandwidth, oscillator shaping, or free-envelope semantics.
- Normal voices may use staged `env=rl` rate/level envelopes. Notes still own gate timing; envelopes own level shape. This is not arbitrary Zyn free-envelope point editing.
- PAD `spectrum` declarations own authored partial clouds under named layers and split table root from playback pitch: `root` is source/base frequency, while `freq` is the note played by the spectral voice. They generate PADsynth-style FFT wavetables with frequency-domain spreading and deterministic random phase, then emit Faust table readers before ordinary voice treatment. Richer PAD table shaping uses neutral `pad_mode`, `pad_bandwidth`, `pad_bwscale`, `pad_profile`, and `pad_position` syntax. The parser may accept older `zyn_*` parity-artifact aliases, but generated/authored `.aqua` should not expose reference-synth names for general DSP intent. Do not pretend this covers every Zyn OscilGen modulation/waveshaping/filter/adaptive-harmonic path yet.
- Zyn OscilGen oscillator filters use one-based harmonic indices: first harmonic is `1`, not `0`. Preserve that index when translating filter types or spectral peaks move to the wrong partial.
- Zyn PAD analog high-pass filters are filter authority too: HP1/HP2 type, stages, and FILTER_ENVELOPE motion lower through `hpf_order` and `hpf_env=rl`, not ad hoc layer gain or brightness compensation.
- Zyn PAD selected table roots are not a global default in Aquarium lowering. Keep sample-0 roots unless a source rule earns the selected root; currently OscilGen shift-base sources (`base_function=7`) may use the selected note table root because Organ Choir proved sample-0 roots leave the octave voice misaligned.
- Zyn PAD selected table roots are not a global default in AquaSynth lowering. Keep sample-0 roots unless a source rule earns the selected root; currently OscilGen shift-base sources (`base_function=7`) may use the selected note table root because Organ Choir proved sample-0 roots leave the octave voice misaligned.
- Zyn OscilGen base-function modulation is source-table authority: apply it as phase modulation before base-function sampling, not as later oscillator modulation or filter/EQ compensation.
- Zyn adaptive harmonics remap the prepared oscillator spectrum at the PAD table root frequency before PAD bandwidth/profile spreading. Do not skip it for sources whose wind/body character depends on sparse remapped harmonics.
- Filter damping is first-class: `lpf_q`/`lpq` owns explicit low-pass Q when a reference synth exposes analog filter damping. Low-pass order remains slope authority even when Q is present; Faust lowering cascades resonant low-pass stages instead of replacing order with one generic resonant filter. Do not fake Q by secretly moving cutoff, slope, or layer gain.
@ -91,7 +91,7 @@ live_invariants:
- Project-authored Dexed patches may be generated inside tests for calibration; do not ship them in the NuGet package or treat them as runtime dependencies.
- Harsh calibration probes belong in tests and ignored artifacts, not in the patch library unless they are musically useful stock.
- Listening WAV artifacts belong under ignored `artifacts/`; keep them out of NuGet and commits unless explicitly promoted as fixtures.
- Do not consume external synth targets that cannot be backed by parity tests. Parsing is inventory; reproducing behavior through terse, readable Aquarium DSL is the proof.
- Do not consume external synth targets that cannot be backed by parity tests. Parsing is inventory; reproducing behavior through terse, readable AquaSynth DSL is the proof.
- 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.
@ -101,14 +101,14 @@ reference_targets:
priority: 1
purpose: "Structured FM/operator graph pressure test."
source: "https://github.com/asb2m10/dexed"
status: "DX7 SysEx parser, public-domain SysEx fixture, dexed-py reference rendering, project-authored Dexed patch rendering, first thresholded rendered parity test for analog1.syx voice 13, hard PRC SYNTH1 probe with listening WAV artifacts, project-authored algorithm-8 summed-stack parity, renderable delayed operator feedback, DX7 effective operator-level approximation, measured carrier output amplitude, isolated route index, feedback table, ratio detune lowering, voice transpose lowering, fixed-frequency lowering, ROM COM compensation cut, operator key/rate scaling at the effective played note, staged operator envelopes, DX7 envelope tracing, algorithm topology extraction, envelope-to-ADSR approximation, two Aquarium DSL rebuild attempts, first-class operator graph support, and Aquarium candidate rendering through Faust-generated C# exist."
status: "DX7 SysEx parser, public-domain SysEx fixture, dexed-py reference rendering, project-authored Dexed patch rendering, first thresholded rendered parity test for analog1.syx voice 13, hard PRC SYNTH1 probe with listening WAV artifacts, project-authored algorithm-8 summed-stack parity, renderable delayed operator feedback, DX7 effective operator-level approximation, measured carrier output amplitude, isolated route index, feedback table, ratio detune lowering, voice transpose lowering, fixed-frequency lowering, ROM COM compensation cut, operator key/rate scaling at the effective played note, staged operator envelopes, DX7 envelope tracing, algorithm topology extraction, envelope-to-ADSR approximation, two AquaSynth DSL rebuild attempts, first-class operator graph support, and AquaSynth candidate rendering through Faust-generated C# exist."
first_slice:
- "Add a harder public-domain DX7 parity target that audibly needs operator routing."
zynaddsubfx:
priority: 2
purpose: "Additive, subtractive, PAD, formant, kit, and layered-instrument pressure test."
source: "https://github.com/zynaddsubfx/zynaddsubfx"
status: "Initial ZynInstrumentReader reads plain or gzip-compressed .xiz XML from project-authored fixtures and classifies active ADD/SUB/PAD engines, enabled kit items, layering, envelopes, free envelopes, LFOs, filters, formants, and effects as ReferencePatch features. First Aquarium rebuilds exist for additive lead, PAD texture, and vocal/formant layer and now use named `layer` declarations. Additive rebuilds can use `harmonics` banks attached to layers, PAD rebuilds can use `spectrum` PADsynth-style FFT wavetable banks attached to layers, and PAD/vocal rebuilds use staged voice `env=rl` envelopes through layer defaults. `ZynInstrumentReader.RebuildFirstPadAsAquariumScript` now emits per-preset PAD `.aqua` rebuilds for enabled PAD kit items with OSCIL harmonic expansion, oracle-reported table base frequency, PAD detune, Zyn PAD profile/bandwidth/position semantics, static/filter-envelope low-pass mapping, low-pass note tracking, explicit low-pass Q, logarithmic Zyn envelope times, and calibrated PAD output gain. ZynAddSubFX source is pinned under `external/zynaddsubfx` at 3ab608c432996ba4d582176572c0b0f82328c825 for test-only parity work, and `tools/zyn-reference/build-zyn-reference.ps1` now builds an ignored patched source copy with MSYS2/MinGW, proves `PadNoteTest`, and builds `ZynPadReference`, including a test-only `harmonics` mode for dumping normalized OscilGen amplitudes before PAD generation. The optional PAD reports emit note and source-table WAV/listening artifacts. Latest accepted upstream PAD pressure has DoublePadBass close enough by ear with log-mel 0.247748 / score 0.650029 after Q/order cascade, while Ghost Ensemble improves to log-mel 0.369402 / score 0.57229. The remaining Ghost gap is no longer first-32 OscilGen harmonic extraction; oracle harmonic dumps match those ratios. The upstream GPL instrument bank is initialized at `external/zynaddsubfx/instruments` commit c5c912131b31df5fdf372d2f06a25aaf2375837f; survey tests parse 1318/1318 .xiz files, write a pressure report, and render a five-patch upstream PAD listening batch under ignored artifacts."
status: "Initial ZynInstrumentReader reads plain or gzip-compressed .xiz XML from project-authored fixtures and classifies active ADD/SUB/PAD engines, enabled kit items, layering, envelopes, free envelopes, LFOs, filters, formants, and effects as ReferencePatch features. First AquaSynth rebuilds exist for additive lead, PAD texture, and vocal/formant layer and now use named `layer` declarations. Additive rebuilds can use `harmonics` banks attached to layers, PAD rebuilds can use `spectrum` PADsynth-style FFT wavetable banks attached to layers, and PAD/vocal rebuilds use staged voice `env=rl` envelopes through layer defaults. `ZynInstrumentReader.RebuildFirstPadAsAquaSynthScript` now emits per-preset PAD `.aqua` rebuilds for enabled PAD kit items with OSCIL harmonic expansion, oracle-reported table base frequency, PAD detune, Zyn PAD profile/bandwidth/position semantics, static/filter-envelope low-pass mapping, low-pass note tracking, explicit low-pass Q, logarithmic Zyn envelope times, and calibrated PAD output gain. ZynAddSubFX source is pinned under `external/zynaddsubfx` at 3ab608c432996ba4d582176572c0b0f82328c825 for test-only parity work, and `tools/zyn-reference/build-zyn-reference.ps1` now builds an ignored patched source copy with MSYS2/MinGW, proves `PadNoteTest`, and builds `ZynPadReference`, including a test-only `harmonics` mode for dumping normalized OscilGen amplitudes before PAD generation. The optional PAD reports emit note and source-table WAV/listening artifacts. Latest accepted upstream PAD pressure has DoublePadBass close enough by ear with log-mel 0.247748 / score 0.650029 after Q/order cascade, while Ghost Ensemble improves to log-mel 0.369402 / score 0.57229. The remaining Ghost gap is no longer first-32 OscilGen harmonic extraction; oracle harmonic dumps match those ratios. The upstream GPL instrument bank is initialized at `external/zynaddsubfx/instruments` commit c5c912131b31df5fdf372d2f06a25aaf2375837f; survey tests parse 1318/1318 .xiz files, write a pressure report, and render a five-patch upstream PAD listening batch under ignored artifacts."
first_slice:
- "Use the Zyn PAD oracle report to isolate source-table, playback/gain, envelope, and fixture-mapping mismatch before further Zyn DSL golf."
- "Use named layers as the owner before promoting richer formant morphing syntax."
@ -123,7 +123,7 @@ reference_targets:
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."
- "Use failures to decide whether AquaSynth needs a lower-level graph DSL."
surge_xt:
priority: 4
purpose: "Modern hybrid synth and modulation matrix pressure test."
@ -141,8 +141,8 @@ reference_targets:
next_agent_start_here:
recommended_first_task: >
Continue the ZynAddSubFX phase. The first parser/classifier slice exists in
`src/AquariumSynth.Dsl/ZynInstrument.cs` with project-authored fixtures
under `tests/AquariumSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored`.
`src/AquaSynth.Dsl/ZynInstrument.cs` with project-authored fixtures
under `tests/AquaSynth.Dsl.Tests/Fixtures/ZynAddSubFX/ProjectAuthored`.
Named `layer` declarations own source identity, additive `harmonics` banks
attach to those layers, PAD `spectrum` banks attach to those layers as
PADsynth-style FFT wavetable sources, and staged `env=rl` envelopes now
@ -151,7 +151,7 @@ next_agent_start_here:
patched artifact copy and builds `ZynPadReference`, which emits PAD table
samples or note audio from Zyn. The first optional project-authored PAD
comparison writes WAV/report artifacts under `artifacts/parity/zyn-pad-reference`.
User listening called the reference wineglass-pure and Aquarium angry bees.
User listening called the reference wineglass-pure and AquaSynth angry bees.
The current de-bee/pitch pass cuts unearned noise/drive/soft-clip,
interpolates spectral wavetable reads, uses 131072-frame PAD tables, splits
table `root` from playback `freq`, removes gratuitous spread/vibrato,
@ -171,7 +171,7 @@ next_agent_start_here:
- state/memory.json
- state/evidence.jsonl
verification:
- "dotnet test AquariumSynthCSharp.slnx --no-restore"
- "dotnet test AquaSynth.slnx --no-restore"
completed_slices:
- date: 2026-05-15
@ -186,7 +186,7 @@ completed_slices:
(400-800 Hz 0.2713 vs 0.2780; 8-16 kHz 0.1471 vs 0.1355), and Organ
improves to log-mel 0.471483 / score 0.541933 without changing accepted
DoublePadBass, Ghost, or Soft Pad.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 107 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 107 passed"
- date: 2026-05-15
summary: >
Mapped Organ Choir's remaining PAD voice-alignment issue to two real Zyn
@ -199,20 +199,20 @@ completed_slices:
Latest Organ candidate is led by the 523 Hz choir voice, with 261 Hz body
at about -1.29 dB versus Zyn's -2.23 dB. Soft Pad, Ghost, and
DoublePadBass remain on the accepted sample-0 root path.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 107 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 107 passed"
- date: 2026-05-15
summary: >
Fixed Zyn OscilGen oscillator-filter harmonic indexing after user listening
accepted Ghost, DoublePadBass, and Soft Pad but still heard sin2x
over-emphasizing the wrong high harmonic. Zyn filter functions receive
one-based harmonic numbers, so Aquarium's zero-based translation moved
one-based harmonic numbers, so AquaSynth's zero-based translation moved
sin2x's type-13 spike from h2 to h3. Preserving the Zyn index makes the
generated sin2x table h2-dominant, improves centroid ratio from
`1.227953` to `0.998145`, and moves log-mel from `0.491437` to
`0.479782`. Organ Choir remains slightly tinny by ear and sin2x is still
louder/concentrated, so the next pressure is output/profile shape rather
than the wrong overtone index.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 106 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 106 passed"
- date: 2026-05-15
summary: >
Added generated Zyn PAD output calibration after user listening found the
@ -225,7 +225,7 @@ completed_slices:
improves to `0.226563`, RMS ratio to `0.818669`, and score to `0.631168`.
The rest of the current PAD batch stays in a plausible RMS range
(`0.94..1.24` except DoublePadBass at `0.82`).
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 104 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 104 passed"
- date: 2026-05-15
summary: >
Corrected Zyn PAD envelope timing after user listening reported that
@ -237,7 +237,7 @@ completed_slices:
multiplier remains cut because it overdrives the whole fixture batch.
DoublePadBass improved to log-mel `0.211646`, envelope distance
`0.462822`, RMS ratio `0.511669`, score `0.450507`.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 104 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 104 passed"
- date: 2026-05-15
summary: >
Added a general low-pass rate/level envelope surface and mapped Zyn PAD
@ -250,11 +250,11 @@ completed_slices:
Zyn cutoff, preserving the immediate open-filter transient. DoublePadBass
improved from log-mel `0.259298` / envelope distance `0.631427` to
`0.241993` / `0.554658`; remaining pressure is mostly level/RMS.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 104 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 104 passed"
- date: 2026-05-15
summary: >
Mapped two missing Zyn PAD authorities exposed by DoublePadBass listening:
analog low-pass slope and PAD frequency detune. Aquarium now has a general
analog low-pass slope and PAD frequency detune. AquaSynth now has a general
`lpf_order` field which emits Faust `fi.lowpass(order, ...)`; Zyn analog
`LP2` filters lower to `lpf_order=2` and include Zyn stage count. The Zyn
PAD importer also applies Zyn `getdetune` semantics to
@ -263,7 +263,7 @@ completed_slices:
to `130.8128`. DoublePadBass log-mel moved from `0.334546` after static
cutoff mapping to `0.259298`; the remaining gap is mostly envelope/RMS,
not wrong pitch or high-band balance.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 103 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 103 passed"
- date: 2026-05-15
summary: >
Mapped static Zyn PAD global low-pass filters into generated Aqua layers
@ -275,7 +275,7 @@ completed_slices:
`lpf=0.003325`, while kit1's `freq=61` lowers to `lpf=0.047225`. The
louder gain-cap experiment stayed cut. Filter envelopes and LFOs remain
explicit missing pressure.
verification: "dotnet test tests\\AquariumSynth.Dsl.Tests\\AquariumSynth.Dsl.Tests.csproj --no-restore --filter FullyQualifiedName~ZynReferenceParityTests: 4 passed"
verification: "dotnet test tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter FullyQualifiedName~ZynReferenceParityTests: 4 passed"
- date: 2026-05-15
summary: >
Addressed the two user-identified Zyn PAD offenders separately. Ghost
@ -288,7 +288,7 @@ completed_slices:
sums enabled PAD kit-note references, gathers per-kit table roots, and
rebuilds enabled PAD kit layers into one Aqua script. A louder gain-cap
experiment worsened DoublePadBass and was cut.
verification: "dotnet test tests\\AquariumSynth.Dsl.Tests\\AquariumSynth.Dsl.Tests.csproj --no-restore --filter FullyQualifiedName~ZynReferenceParityTests: 4 passed"
verification: "dotnet test tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter FullyQualifiedName~ZynReferenceParityTests: 4 passed"
- date: 2026-05-15
summary: >
Reworked the first Zyn OscilGen approximation into a sampled stage-order
@ -300,10 +300,10 @@ completed_slices:
improves the processed `sin2x pad` target versus the previous subset
while keeping the pass honest: DoublePadBass and Organ Choir Pad2 still
miss badly, so Zyn PAD parity remains open pressure.
verification: "dotnet test tests\\AquariumSynth.Dsl.Tests\\AquariumSynth.Dsl.Tests.csproj --no-restore --filter FullyQualifiedName~ZynReferenceParityTests: 4 passed"
verification: "dotnet test tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter FullyQualifiedName~ZynReferenceParityTests: 4 passed"
- date: 2026-05-15
summary: >
Promoted explicit Zyn PAD table semantics into the Aquarium model and
Promoted explicit Zyn PAD table semantics into the AquaSynth model and
parser. `SpectralBank` now carries `PadSpectrumProfile`; patch scripts can
express Zyn PAD mode, bandwidth, bandwidth scale, harmonic profile, and
harmonic-position warp through readable `zyn_*` fields, and
@ -315,7 +315,7 @@ completed_slices:
Organ Choir Pad2 improve, while sin2x, DoublePadBass, and Ghost Ensemble
regress, so the next cut must either complete more OscilGen semantics or
gate the approximation to the cases it owns.
verification: "dotnet test tests\\AquariumSynth.Dsl.Tests\\AquariumSynth.Dsl.Tests.csproj --no-restore --filter \"FullyQualifiedName~ZynInstrumentTests|FullyQualifiedName~ZynReferenceParityTests\": 13 passed"
verification: "dotnet test tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter \"FullyQualifiedName~ZynInstrumentTests|FullyQualifiedName~ZynReferenceParityTests\": 13 passed"
- date: 2026-05-15
summary: >
Initialized the upstream GPL ZynAddSubFX instrument bank submodule and
@ -326,12 +326,12 @@ completed_slices:
`artifacts/parity/zyn-upstream-pad-fixtures/`. Zyn PAD artifacts now use
per-synth timestamped folders with reference WAVs, table WAVs,
`candidate.aqua`, and `report.txt`; upstream GPL runs now render
per-preset first-pass PAD rebuilds as `candidate-aquarium.wav`, with
per-preset first-pass PAD rebuilds as `candidate-aquasynth.wav`, with
reports naming which Zyn PAD semantics are still missing. The
batch includes `Pads/0002-sin2x pad.xiz`, `Pads/0065-Soft Pad.xiz`,
`Dual/0008-Organ Choir Pad2.xiz`, `Laba170bank/0098-DoublePadBass.xiz`,
and `Companion/0121-Ghost Ensemble.xiz`.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore --filter FullyQualifiedName~ZynReferenceParityTests: 4 passed"
verification: "dotnet test AquaSynth.slnx --no-restore --filter FullyQualifiedName~ZynReferenceParityTests: 4 passed"
- date: 2026-05-15
summary: >
Added more project-authored PAD fixtures and a Zyn-only PAD fixture
@ -342,7 +342,7 @@ completed_slices:
through the oracle, so the `pad-texture` filter/category XML was not live
pressure; `pad-harmonic` changes both table and note output and is the
next useful PAD rung.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 98 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 98 passed"
- date: 2026-05-15
summary: >
Removed the detuned impression in the Zyn PAD rebuild by collapsing the
@ -351,7 +351,7 @@ completed_slices:
loud. Latest report: log-mel 0.35669, envelope 0.347572, RMS ratio
1.001704, centroid ratio 1.004072, score 0.6211. This preserves the
pitch/root split and avoids treating voice stacking as PAD richness.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 97 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 97 passed"
- date: 2026-05-15
summary: >
Fixed the octave-low Zyn PAD rebuild by splitting spectral table root
@ -362,11 +362,11 @@ completed_slices:
Removing gratuitous spread/vibrato reduced the drone the user heard.
Latest report: log-mel 0.353365, envelope 0.348752, RMS ratio 0.97283,
centroid ratio 0.999243, score 0.612832.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 97 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 97 passed"
- date: 2026-05-15
summary: >
De-beed the project-authored Zyn PAD rebuild after listening identified
Aquarium as a noisy swarm while Zyn sounded like a pure blown wineglass.
AquaSynth as a noisy swarm while Zyn sounded like a pure blown wineglass.
Cut the unearned `air` noise layer, drive, and soft clipping from the PAD
rebuild; added interpolated Faust wavetable reads for spectral banks; and
raised spectral PAD tables to Zyn's 131072-frame sample size. The optional
@ -376,7 +376,7 @@ completed_slices:
This is a real improvement from 0.236117 but not passing; remaining
pressure is exact source profile/table generation and Zyn amplitude
semantics.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 96 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 96 passed"
- date: 2026-05-15
summary: >
Added `ZynPadReference`, a test-only executable copied into the ignored
@ -385,12 +385,12 @@ completed_slices:
`PADnoteParameters`/`PADnote` path, and writes either generated PAD table
samples or rendered note audio as raw float32. Added an optional C# parity
harness that runs the renderer when the artifact executable exists and
writes Zyn/Aquarium WAVs plus a report under
writes Zyn/AquaSynth WAVs plus a report under
`artifacts/parity/zyn-pad-reference`. First report: log-mel 0.552296,
envelope 0.774501, RMS ratio 0.234763, centroid ratio 1.025735, score
0.236117, so the harness works and current Aquarium `spectrum` is not yet
0.236117, so the harness works and current AquaSynth `spectrum` is not yet
a close PAD emulation.
verification: "powershell -NoProfile -ExecutionPolicy Bypass -File tools\\zyn-reference\\build-zyn-reference.ps1; dotnet test AquariumSynthCSharp.slnx --no-restore: 96 passed"
verification: "powershell -NoProfile -ExecutionPolicy Bypass -File tools\\zyn-reference\\build-zyn-reference.ps1; dotnet test AquaSynth.slnx --no-restore: 96 passed"
- date: 2026-05-14
summary: >
Built the pinned ZynAddSubFX source as a test-only MSYS2/MinGW parity
@ -399,7 +399,7 @@ completed_slices:
repo-owned compatibility patch for the stale `Part` constructor call,
configures GUI/plugins off with tests on and `Werror=OFF`, builds
`PadNoteTest`, and runs the CTest target. The next rung is a small
reference renderer/wrapper that emits PAD audio or tables for Aquarium
reference renderer/wrapper that emits PAD audio or tables for AquaSynth
`spectrum` parity.
verification: "powershell -NoProfile -ExecutionPolicy Bypass -File tools\\zyn-reference\\build-zyn-reference.ps1: PadNoteTest passed"
- date: 2026-05-14
@ -409,9 +409,9 @@ completed_slices:
Added guard tests proving the source, license, PAD implementation files,
and commit are present, and tightened the package-boundary test so
`external/`, C++ sources, and headers do not ship in
`AquariumSynth.Dsl.nupkg`. This establishes Zyn as a parity oracle, not
Aquarium implementation.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 95 passed"
`AquaSynth.Dsl.nupkg`. This establishes Zyn as a parity oracle, not
AquaSynth implementation.
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 95 passed"
- date: 2026-05-14
summary: >
Added PAD spectral-cloud syntax for named layers. `spectrum
@ -422,7 +422,7 @@ completed_slices:
playback before ordinary voice treatment. The Zyn PAD texture rebuild now
uses two spectral banks under `pad_low` and `pad_high`; full Zyn PAD
profile/randomness/pitch-zone behavior remains missing pressure.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 94 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 94 passed"
- date: 2026-05-14
summary: >
Promoted staged rate/level envelopes from operator graphs to normal
@ -433,7 +433,7 @@ completed_slices:
envelopes through named layers, proving the surface beyond a single
fixture while leaving arbitrary Zyn free-envelope point curves as missing
pressure.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 91 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 91 passed"
- date: 2026-05-14
summary: >
Added additive harmonic-bank syntax for named layers. `harmonics
@ -443,17 +443,17 @@ completed_slices:
banks under `body` and `shine`, while the catalog marks harmonic-bank
support as matched and keeps Zyn ADDsynth phase/bandwidth/oscillator
detail as missing pressure.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 90 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 90 passed"
- date: 2026-05-14
summary: >
Added the first Zyn-driven language-golf abstraction: `layer`.
Layer declarations own name, engine tag, optional MIDI key range metadata,
default gain, and effect-send labels while lowering to ordinary Aquarium
default gain, and effect-send labels while lowering to ordinary AquaSynth
voices. The three Zyn-style rebuilds now use named layers, proving the
abstraction generalizes across additive, PAD-texture, and vocal/formant
targets without adding fixture-specific shortcuts or hidden PAD/effect
behavior.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 88 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 88 passed"
- date: 2026-05-14
summary: >
Hardened the Zyn reader against the real upstream instrument bank and
@ -466,10 +466,10 @@ completed_slices:
envelopes, 73 LFOs, 37 filters, and 3 formant filters. The survey points
to named kit/layer routing as the next owner abstraction before syntax
golf.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 86 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 86 passed"
- date: 2026-05-14
summary: >
Added first Zyn-style Aquarium rebuilds for the project-authored additive
Added first Zyn-style AquaSynth rebuilds for the project-authored additive
lead, PAD texture, and vocal/formant layer fixtures. The rebuild catalog
now records matched and missing features for each target, while tests
parse/export the scripts and verify matched feature claims against source
@ -477,7 +477,7 @@ completed_slices:
additive harmonic-bank syntax, PAD/spectral source generation, free
envelopes for normal voices, named kit/layer routing with per-layer effect
sends, and richer formant/vowel morphing.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 83 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 83 passed"
- date: 2026-05-14
summary: >
Started the ZynAddSubFX phase after closing the current DX7 community
@ -488,7 +488,7 @@ completed_slices:
kit items, layering, envelopes, free envelopes, LFOs, filters, formants,
and effects. Upstream Zyn bank files were inspected but not vendored
because preset provenance was not explicit enough for this first slice.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 81 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 81 passed"
- date: 2026-05-14
summary: >
Reframed the latest community listening as envelope pressure rather than
@ -499,7 +499,7 @@ completed_slices:
to 1.25s, improving pressure log-mel to 0.3472474 and score to 0.5722256.
Tested and cut a global traced-release-duration change because it broke
PRC, `Piano Bass`, `RES SYNTH1`, and `ANLGSYN 1`.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 76 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 76 passed"
- date: 2026-05-14
summary: >
Added scoped modulator peak headroom after listening found `ANLGSYN 1`
@ -513,7 +513,7 @@ completed_slices:
0.42906323. Tested and cut a three-source summed-route headroom rule for
`{ Mooger }`; it worsened Mooger metrics and did not isolate the overtone
issue.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter Dx7... focused pass: 28 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter Dx7... focused pass: 28 passed"
- date: 2026-05-14
summary: >
Lowered DX7 operator key-scaling and rate-scaling against the effective
@ -526,7 +526,7 @@ completed_slices:
and should not be promoted as passing stock. A trial that made envelope
tracing depend on operator output level was cut because it worsened
`MELLOWSOLO` and `ANLGSYN 1`.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter Dx7... focused pass: 28 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter Dx7... focused pass: 28 passed"
- date: 2026-05-14
summary: >
Removed DX7 ROM COM carrier compensation after listening found the
@ -538,7 +538,7 @@ completed_slices:
0.10764466, RMS 0.941083. `DX1 LEAD B` and `MELLOWSOLO` sound closer by
ear under no-COM/gain-restored lowering but still fail numeric gates, so
they remain pressure targets.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 74 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 74 passed"
- date: 2026-05-14
summary: >
Fixed the remaining `ANLGSYN 1` drift after listening found the
@ -551,18 +551,18 @@ completed_slices:
graph gain moved to 0.30, bringing its RMS ratio to 0.9909909. Latest
`ANLGSYN 1`: score 0.7245976, log-mel 0.18460114, envelope 0.119950555,
zero-crossing 0.97149533.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 74 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 74 passed"
- date: 2026-05-14
summary: >
Tightened the `ANLGSYN 1` parity rung after listening caught the remaining
`deeooh` failure: the hotter feedback attack was close, but sustained
high-band buzz collapsed midway through the note. The DX7 probe lowering
now enables Aquarium's existing output soft clip only for max-feedback
now enables AquaSynth's existing output soft clip only for max-feedback
voices with a self-feedback topology. PRC remains `soft_clip=false`.
Latest `ANLGSYN 1`: score 0.70946133, log-mel 0.18456098, envelope
0.10628832, RMS ratio 0.9816304, zero-crossing 0.8657629. Latest PRC:
score 0.7546429, log-mel 0.13874851, envelope 0.067291394.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 73 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 73 passed"
- date: 2026-05-14
summary: >
Retuned DX7 max-feedback lowering after listening found `ANLGSYN 1` still
@ -575,7 +575,7 @@ completed_slices:
rises from 0.8% to 65.6%, and 2.5-5 kHz from near zero to 32.1%.
Envelope/RMS are still imperfect, so this is a timbre correction rather
than the final word on ANLGSYN.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 73 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 73 passed"
- date: 2026-05-14
summary: >
Fixed DX7 fixed-frequency operator lowering after listening caught
@ -588,7 +588,7 @@ completed_slices:
`ANLGSYN 1`; latest metrics improve to log-mel 0.19427659, envelope
0.14924917, zero-crossing 0.94662774, centroid 0.9182808, score
0.659334.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 73 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 73 passed"
- date: 2026-05-14
summary: >
Fixed DX7 voice transpose lowering after listening caught `Piano Bass`
@ -600,31 +600,31 @@ completed_slices:
`RES SYNTH1`, `DX1 LEAD B`, and `MELLOWSOLO` remain pressure, not passing
parity. Fixed `Piano Bass` improves to log-mel 0.16180529, envelope
0.13544041, zero-crossing 0.905511, and score 0.64267576.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 71 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 71 passed"
- date: 2026-05-14
summary: >
Added a rendered public-domain community DX7 parity test against four
actual `analog1.syx` voices from Musical Artifacts artifact 152:
`ANLGSYN 1`, `{ Mooger }`, `Piano Bass`, and `RES SYNTH1`. The test
renders Dexed references, lowers the same voices through the current
Aquarium DX7 operator-graph path, writes listening WAV/report artifacts
AquaSynth DX7 operator-graph path, writes listening WAV/report artifacts
under `artifacts/parity/dx7-community-analog1/`, and gates broad parity
at log-mel <= 0.3, envelope <= 0.16, score >= 0.5. First survey also
tried `DX1 LEAD B` and `MELLOWSOLO`; they remain pressure because
log-mel/zero-crossing mismatch is too large for the broad gate.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 70 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 70 passed"
- date: 2026-05-14
summary: >
Added split project-authored DX7 algorithm-8 parity probes for the
cascaded `6 -> 5 -> 3` branch and summed `4 + 5 -> 3` branch. The tests
render Dexed references, render Aquarium operator-graph candidates, write
render Dexed references, render AquaSynth operator-graph candidates, write
listening WAV/report artifacts under `artifacts/parity/dx7-project-authored/`,
and gate log-mel at <= 0.06. Latest real-Dexed run: combined stack
log-mel 0.04029555/score 0.8664546, summed pair 0.042429477/0.870778,
cascade 0.03811625/0.9157809. The split probes remain test/artifact
material rather than patch-library stock because they are calibration
signals, not usable sounds.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore: 69 passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore: 69 passed"
- date: 2026-05-14
summary: >
Added a structured development patch library under `patches/` with a
@ -632,8 +632,8 @@ completed_slices:
existing stock/reference catalogs plus the calibrated PRC SYNTH1 parity
candidate. Added tests so every library `.aqua` parses and exports Faust,
while package-boundary tests assert `patches/` and `.aqua` files do not
ship in the AquariumSynth.Dsl NuGet package.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 67 passed"
ship in the AquaSynth.Dsl NuGet package.
verification: "dotnet test AquaSynth.slnx --no-restore: 67 passed"
- date: 2026-05-14
summary: >
Increased `Dx7SysEx.SummedOperatorModulationRouteIndex` from 1.6 to 6.0
@ -642,27 +642,27 @@ completed_slices:
to 0.067291506, centroid ratio to 1.1156512, and score to 0.7546445 with
RMS ratio 0.9912135. The independent project-authored algorithm-8
summed-stack test now uses graph gain 0.218 and tighter log-mel/RMS gates.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled|ProjectAuthoredDx7AlgorithmEightSummedStackMeetsParityWhenInstalled\": passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter \"PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled|ProjectAuthoredDx7AlgorithmEightSummedStackMeetsParityWhenInstalled\": passed"
- date: 2026-05-14
summary: >
Added `Dx7SysEx.ApproximateAppliedRateLevelEnvelope` and used it in the
hard PRC SYNTH1 probe. The helper traces Dexed-style block-interpolated
applied EG gain, normalizes it against Aquarium operator-output scaling,
applied EG gain, normalizes it against AquaSynth operator-output scaling,
and emits curved `env=rl` syntax. PRC log-mel improved from 0.24476814 to
0.21931955 and envelope distance from 0.43700194 to 0.10271241, with RMS
ratio near unity. A follow-up fixed the peak picker for slower rising
envelopes after listening exposed op2's false late swell; PRC now reaches
log-mel 0.17186502, envelope distance 0.13057204, zero-crossing ratio
0.7815517, and score 0.6501268.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled|ProjectAuthoredDx7AlgorithmEightSummedStackMeetsParityWhenInstalled|ApproximatesDx7AppliedEnvelopeAsCurvedRateLevelEnvelope\": passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter \"PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled|ProjectAuthoredDx7AlgorithmEightSummedStackMeetsParityWhenInstalled|ApproximatesDx7AppliedEnvelopeAsCurvedRateLevelEnvelope\": passed"
- date: 2026-05-14
summary: >
Added per-segment curves to staged operator envelopes. `env=rl` remains
the same rate/level contour surface, but can now express linear or
exponential segments with deliberate levels above unity. The DX7 envelope
trace artifact includes a curved Aquarium candidate that is much closer
trace artifact includes a curved AquaSynth candidate that is much closer
to Dexed's block-interpolated applied gain than the old linear contour.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"OperatorGraphSyntaxParsesReadableRateLevelEnvelope|OperatorGraphSyntaxParsesCurvedRateLevelEnvelope|ProjectAuthoredDx7EnvelopeTraceWritesComparisonArtifact\": passed"
verification: "dotnet test AquaSynth.slnx --no-restore --filter \"OperatorGraphSyntaxParsesReadableRateLevelEnvelope|OperatorGraphSyntaxParsesCurvedRateLevelEnvelope|ProjectAuthoredDx7EnvelopeTraceWritesComparisonArtifact\": passed"
- date: 2026-05-14
summary: >
Added `Dx7SysEx.TraceInterpolatedEnvelope` and extended the DX7 envelope
@ -670,7 +670,7 @@ completed_slices:
rendering samples the EG once per 64-sample block and interpolates the
applied gain across the block, so matching the raw EG state alone is not
enough for audio parity.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"TracesDx7EnvelopeAppliedBlockInterpolatedGain|TracesDx7EnvelopeInternalGainShape|ProjectAuthoredDx7EnvelopeTraceWritesComparisonArtifact\": passed"
verification: "dotnet test AquaSynth.slnx --no-restore --filter \"TracesDx7EnvelopeAppliedBlockInterpolatedGain|TracesDx7EnvelopeInternalGainShape|ProjectAuthoredDx7EnvelopeTraceWritesComparisonArtifact\": passed"
- date: 2026-05-14
summary: >
Added `Dx7SysEx.TraceEnvelope` and an envelope trace comparison artifact.
@ -678,7 +678,7 @@ completed_slices:
an initial gain spike above 1 followed by millisecond-scale collapse to a
tiny sustain. A first `env=dx7` runtime lowering attempt was cut because
matching the local trace did not match Dexed plugin audio.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"ProjectAuthoredDx7EnvelopeTraceWritesComparisonArtifact|TracesDx7EnvelopeInternalGainShape\": passed"
verification: "dotnet test AquaSynth.slnx --no-restore --filter \"ProjectAuthoredDx7EnvelopeTraceWritesComparisonArtifact|TracesDx7EnvelopeInternalGainShape\": passed"
- date: 2026-05-14
summary: >
Added test-only project-authored Dexed patch rendering and an independent
@ -687,7 +687,7 @@ completed_slices:
exponential DX7 EG level curve was tested and cut: it improved some
envelope metrics but failed PRC log-mel, so EG levels remain linear until
isolated envelope parity can pass without harming PRC.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"ProjectAuthoredDx7AlgorithmEightSummedStackMeetsParityWhenInstalled|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled|Dx7SysExTests\": passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter \"ProjectAuthoredDx7AlgorithmEightSummedStackMeetsParityWhenInstalled|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled|Dx7SysExTests\": passed"
- date: 2026-05-14
summary: >
Moved the remaining PRC cascaded-route scalar into
@ -695,7 +695,7 @@ completed_slices:
use the two-op route scale 6.275; summed edges and direct edges feeding a
summed branch use the summed/cascaded route scale 1.6. The PRC probe no
longer owns a private route-index function.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"Dx7SysExTests|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled\": passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter \"Dx7SysExTests|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled\": passed"
- date: 2026-05-14
summary: >
Added DX7 ratio-mode detune and algorithm output compensation to central
@ -704,23 +704,23 @@ completed_slices:
output counts relative to the algorithm-32 six-output amplitude baseline.
The PRC probe no longer carries a manual carrier-level scale table; its
remaining cascaded-route value is fenced as a hard-target probe scar.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"Dx7SysExTests|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled\": passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter \"Dx7SysExTests|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled\": passed"
- date: 2026-05-14
summary: >
Completed the DX7 feedback calibration rung. Isolated feedback sweeps fit
Aquarium feedback amounts `0, .01, .02, .05, .10, .19, .38, .66` for DX7
AquaSynth feedback amounts `0, .01, .02, .05, .10, .19, .38, .66` for DX7
feedback values 0-7. `Dx7SysEx.OperatorFeedbackAmount` now owns that
table, and the PRC probe no longer uses a local `feedback * constant`.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"CalibratesDx7FeedbackToAquariumFeedbackAmount|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled\": passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter \"CalibratesDx7FeedbackToAquaSynthFeedbackAmount|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled\": passed"
- date: 2026-05-14
summary: >
Completed the isolated two-operator modulation-index rung. Dexed sweeps
fit full-level modulation at about 12.55 radians, which maps to Aquarium
fit full-level modulation at about 12.55 radians, which maps to AquaSynth
route index 6.275 under the current Faust phase formula. The PRC probe
applies that calibrated route only to the isolated `op2 -> op1` branch;
applying it to the cascaded stack made metrics worse, leaving algorithm
output compensation as explicit pressure.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled: passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled: passed"
- date: 2026-05-14
summary: >
Added `docs/dx7-calibration-plan.md` and completed the first calibration
@ -729,7 +729,7 @@ completed_slices:
exposes `OperatorOutputAmplitude` and `ApproximateOperatorLevel` uses that
measured curve instead of the old internal-level proxy. The PRC log-mel
gate is temporarily 0.26 while the next rung calibrates modulation index.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"Dx7SysExTests|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled\": passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter \"Dx7SysExTests|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled\": passed"
- date: 2026-05-14
summary: >
Reoriented the hard DX7 `PRC SYNTH1` probe around log-mel distance after
@ -738,25 +738,25 @@ completed_slices:
dominant 393 Hz partial; the candidate now uses the DX7 note basis and
scaled high modulators to reach log-mel 0.25093183, with remaining
zero-crossing/centroid mismatch left as explicit pressure.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled: passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled: passed"
- date: 2026-05-14
summary: >
Tightened the hard DX7 `PRC SYNTH1` probe to require score >= 0.60 and
improved the candidate to 0.6057491. The useful calibration cuts were
removing Aquarium soft clipping, setting the graph base frequency to 392,
removing AquaSynth soft clipping, setting the graph base frequency to 392,
and boosting the op3 carrier branch for algorithm-8 output/body balance.
Global detune and per-edge route damping were tested and cut because they
made the metrics worse.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled: passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled: passed"
- date: 2026-05-13
summary: >
Added a hard DX7 `PRC SYNTH1` rendered parity probe that emits listening
WAV artifacts under ignored `artifacts/parity/dx7-prc-synth1` when
dexed-py and Faust are available. The Aquarium candidate uses readable
dexed-py and Faust are available. The AquaSynth candidate uses readable
operator graph syntax and staged `env=rl` envelopes, asserts a modest
hard-target score floor, and currently scores 0.4581856 with duration
close to the Dexed reference but remaining gain/timbre mismatch.
verification: "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore --filter PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled: passed"
verification: "AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled: passed"
- date: 2026-05-13
summary: >
Added staged operator envelopes for operator graphs through
@ -766,15 +766,15 @@ completed_slices:
lowering on `PRC SYNTH1` only reached ~0.316 in quick probes, so the DSL
surface exists but DX7 EG timing/gain calibration remains the next
pressure.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 53 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 53 passed"
- date: 2026-05-13
summary: >
Added `Dx7OperatorLevelApproximation`, which maps DX7 operator output
level plus key/velocity scaling into normal Aquarium operator levels for
level plus key/velocity scaling into normal AquaSynth operator levels for
reference rebuild work. Retried `PRC SYNTH1`; static level mapping alone
did not produce parity, and envelope tweaks only reached ~0.42 score. The
next pressure is DX7-style operator envelope/gain evolution.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 50 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 50 passed"
- date: 2026-05-13
summary: >
Probed the harder `analog1.syx` voice 17 (`PRC SYNTH1`) and found the
@ -782,53 +782,53 @@ completed_slices:
cut was feedback: operator feedback now emits as Faust delayed recursion
instead of a direct cyclic self-reference, and a render test covers it.
Exact DX7 feedback scaling and EG behavior remain pressure.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 49 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 49 passed"
- date: 2026-05-13
summary: >
Added the first thresholded rendered DX7 parity rebuild for
`analog1.syx` voice 13, `MC-MM 5-3`. The candidate is a terse Aquarium
`analog1.syx` voice 13, `MC-MM 5-3`. The candidate is a terse AquaSynth
sine-like behavioral match and the test compares dexed-py reference audio
against Faust-rendered Aquarium audio using score, log-mel, envelope,
against Faust-rendered AquaSynth audio using score, log-mel, envelope,
duration, RMS, zero-crossing, and centroid thresholds. It does not claim
exact DX7 operator execution.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 48 passed; focused dexed-py parity test passed with bundled Python"
verification: "dotnet test AquaSynth.slnx --no-restore: 48 passed; focused dexed-py parity test passed with bundled Python"
- date: 2026-05-13
summary: >
Added a public-domain DX7 SysEx fixture from Musical Artifacts artifact
152, provenance metadata, and a test-only dexed-py renderer. The render
test uses `AQUARIUM_DX7_PYTHON` when set and otherwise stays optional.
Added a package-boundary test that packs `AquariumSynth.Dsl` and asserts
test uses `AQUASYNTH_DX7_PYTHON` when set and otherwise stays optional.
Added a package-boundary test that packs `AquaSynth.Dsl` and asserts
fixtures, SysEx banks, and Python helpers do not ship in the NuGet package.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 47 passed; dexed-py render test passed with bundled Python"
verification: "dotnet test AquaSynth.slnx --no-restore: 47 passed; dexed-py render test passed with bundled Python"
- date: 2026-05-13
summary: >
Added `FaustCompiler.RenderAsync`, which compiles Aquarium Faust source to
Added `FaustCompiler.RenderAsync`, which compiles AquaSynth Faust source to
Faust-generated C#, runs it in a temp .NET project, and returns mono float
samples. The first render test checks non-silent generated audio and feeds
the buffer through `AudioAnalyzer`; DX7 audio parity remains blocked on a
chosen reference renderer or captured fixture.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 44 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 44 passed"
- date: 2026-05-13
summary: >
Added `Dx7EnvelopeApproximation`, a labeled helper that lowers DX7
four-rate/four-level envelopes to Aquarium ADSR plus gate duration.
four-rate/four-level envelopes to AquaSynth ADSR plus gate duration.
Reference rebuilds now distinguish approximated envelope lowering from
still-missing exact DX7 EG execution.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 43 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 43 passed"
- date: 2026-05-13
summary: >
Added field-site parameter binding for readable operator graphs. Graph
gain/note fields, operator ratio/level/feedback/envelope fields, and route
index fields now bind through exact `/opgraphs/...` paths, and Faust
emission substitutes those bound expressions in operator graph output.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 42 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 42 passed"
- date: 2026-05-13
summary: >
Added a patch-level Playback contract and `instrument` authoring surface
for Faust-managed MIDI mono/polyphony. `instrument midi=true polyphony=8`
emits `[midi:on][nvoices:8]` and standard `freq`, `gain`, and `gate`
controls instead of Aquarium-owned voice allocation.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 41 passed"
controls instead of AquaSynth-owned voice allocation.
verification: "dotnet test AquaSynth.slnx --no-restore: 41 passed"
- date: 2026-05-13
summary: >
Standardized the live envelope model as ADSR and added explicit Note
@ -837,14 +837,14 @@ completed_slices:
ADSR sustain level plus voice gain compensation as compatibility import
behavior. Generated Faust can emit one-shot ADSR envelopes or host
gate/frequency controls.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 40 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 40 passed"
- date: 2026-05-13
summary: >
Added readable operator graph authoring syntax with `operator`, `route`,
and `carrier` declarations, plus `env=ad:attack:decay` and
`env=adsr:attack:decay:sustain:release` forms. Converted the DX7
algorithm-8 rebuild away from compact number soup.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 36 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 36 passed"
- date: 2026-05-13
summary: >
Added first-class OperatorGraph support with operators, modulation edges,
@ -852,14 +852,14 @@ completed_slices:
normal voices. Updated the DX7 algorithm-8 rebuild to use the operator
graph; remaining pressure is exact DX7 feedback-register timing and
rate/level envelopes.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 35 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 35 passed"
- date: 2026-05-13
summary: >
Added ReferenceRebuild catalog entries and two DX7-style Aquarium DSL
Added ReferenceRebuild catalog entries and two DX7-style AquaSynth DSL
rebuilds. Algorithm 32 maps cleanly to six additive voices; algorithm 8
exposes missing operator graph concepts including explicit modulation
edges, summed modulators, and operator-local self-feedback.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 34 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 34 passed"
- date: 2026-05-13
summary: >
Added DX7 algorithm topology metadata for algorithms 1-32 from the
@ -867,14 +867,14 @@ completed_slices:
operators, modulation edges, feedback-register writers, self-feedback
operators, delayed-feedback targets, raw ROM steps, and ReferencePatch
feature pressure.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 31 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 31 passed"
- date: 2026-05-13
summary: >
Added DX7 SysEx records and parsing for 155-byte voice edit buffers,
128-byte packed voices, and 4104-byte packed 32-voice banks. Extracted
operators, envelopes, algorithm, feedback, oscillator sync, LFO,
transpose, voice name, checksum validation, and ReferencePatch features.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 28 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 28 passed"
- date: 2026-05-13
summary: >
Added PatchParameter and ReferencePatch contracts, `param` script parsing,
@ -882,7 +882,7 @@ completed_slices:
controls. Parameter binding uses field-site references such as
`lpf=@/macro/brightness`; the emitter substitutes the parameter only at
the exact bound field site and warns for unbound parameters.
verification: "dotnet test AquariumSynthCSharp.slnx --no-restore: 23 passed"
verification: "dotnet test AquaSynth.slnx --no-restore: 23 passed"
cut_line:
- "Cut any importer that cannot state source provenance and license."

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
@ -23,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\AquariumSynth.Dsl\AquariumSynth.Dsl.csproj" />
<ProjectReference Include="..\..\src\AquaSynth.Dsl\AquaSynth.Dsl.csproj" />
</ItemGroup>
</Project>

View file

@ -2,7 +2,7 @@ using System.Diagnostics;
using System.Text.Json;
using System.Text;
namespace AquariumSynth.Dsl.Tests;
namespace AquaSynth.Dsl.Tests;
public sealed class Dx7ReferenceParityTests
{
@ -319,8 +319,8 @@ public sealed class Dx7ReferenceParityTests
var dx7 = Dx7SysEx.TraceEnvelope(envelope, gateSeconds: 0.75f, durationSeconds: 1.1f);
var dx7Applied = Dx7SysEx.TraceInterpolatedEnvelope(envelope, gateSeconds: 0.75f, durationSeconds: 1.1f);
var approximation = ScaledEnvelope(Dx7SysEx.ApproximateRateLevelEnvelope(envelope), 0.62f, 0.75f);
var aquarium = TraceRateLevelEnvelope(approximation.Envelope, approximation.GateSeconds, durationSeconds: 1.1f);
var aquariumCurved = TraceRateLevelEnvelope(
var aquasynth = TraceRateLevelEnvelope(approximation.Envelope, approximation.GateSeconds, durationSeconds: 1.1f);
var aquasynthCurved = TraceRateLevelEnvelope(
new RateLevelEnvelope(
64f / 44100,
2f,
@ -341,27 +341,27 @@ public sealed class Dx7ReferenceParityTests
var path = Path.Combine(artifactDir, "egstep.csv");
using var writer = new StreamWriter(path);
writer.WriteLine("time_seconds,dx7_raw_gain,dx7_applied_gain,aquarium_rl_gain,aquarium_curved_gain,dx7_stage");
for (var i = 0; i < Math.Min(dx7.Count, aquarium.Length); i += 64)
writer.WriteLine("time_seconds,dx7_raw_gain,dx7_applied_gain,aquasynth_rl_gain,aquasynth_curved_gain,dx7_stage");
for (var i = 0; i < Math.Min(dx7.Count, aquasynth.Length); i += 64)
{
writer.WriteLine(string.Join(
",",
F(dx7[i].TimeSeconds),
F(dx7[i].Gain),
F(dx7Applied[i].Gain),
F(aquarium[i]),
F(aquariumCurved[i]),
F(aquasynth[i]),
F(aquasynthCurved[i]),
dx7[i].Stage.ToString(System.Globalization.CultureInfo.InvariantCulture)));
}
var linearShape = NormalizedDistance(
dx7Applied.Select(point => point.Gain).ToArray(),
aquarium);
aquasynth);
var curvedShape = NormalizedDistance(
dx7Applied.Select(point => point.Gain).ToArray(),
aquariumCurved);
Assert.True(linearShape > 0.55f, $"expected current Aquarium envelope to visibly differ; shape distance {linearShape}, artifact: {path}");
Assert.True(curvedShape < linearShape * 0.6f, $"expected curved Aquarium envelope to improve shape; linear {linearShape}, curved {curvedShape}, artifact: {path}");
aquasynthCurved);
Assert.True(linearShape > 0.55f, $"expected current AquaSynth envelope to visibly differ; shape distance {linearShape}, artifact: {path}");
Assert.True(curvedShape < linearShape * 0.6f, $"expected curved AquaSynth envelope to improve shape; linear {linearShape}, curved {curvedShape}, artifact: {path}");
}
private static string FixturePath(params string[] parts) =>
@ -370,7 +370,7 @@ public sealed class Dx7ReferenceParityTests
private static string ArtifactPath(params string[] parts)
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null && !File.Exists(Path.Combine(current.FullName, "AquariumSynthCSharp.slnx")))
while (current is not null && !File.Exists(Path.Combine(current.FullName, "AquaSynth.slnx")))
{
current = current.Parent;
}
@ -771,7 +771,7 @@ public sealed class Dx7ReferenceParityTests
var peak = Math.Max(Peak(reference), Peak(candidate));
var scale = peak <= 0 ? 1 : 0.98f / peak;
WriteWav(Path.Combine(directory, "reference-dexed.wav"), reference, sampleRate, scale);
WriteWav(Path.Combine(directory, "candidate-aquarium.wav"), candidate, sampleRate, scale);
WriteWav(Path.Combine(directory, "candidate-aquasynth.wav"), candidate, sampleRate, scale);
File.WriteAllText(Path.Combine(directory, "candidate.aqua"), script);
File.WriteAllText(
Path.Combine(directory, "report.txt"),
@ -780,7 +780,7 @@ public sealed class Dx7ReferenceParityTests
ParityReport(comparison),
$"wav normalization scale: {scale}",
$"reference: {Path.Combine(directory, "reference-dexed.wav")}",
$"candidate: {Path.Combine(directory, "candidate-aquarium.wav")}"));
$"candidate: {Path.Combine(directory, "candidate-aquasynth.wav")}"));
}
private static void WriteWav(string path, IReadOnlyList<float> samples, int sampleRate, float scale)
@ -893,7 +893,7 @@ internal static class DexedPyRenderer
var python = await FindPythonWithDexedAsync(cancellationToken);
if (python is null) return null;
var tempDir = Path.Combine(Path.GetTempPath(), $"aquarium-dx7-render-{Guid.NewGuid():N}");
var tempDir = Path.Combine(Path.GetTempPath(), $"aquasynth-dx7-render-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
try
{
@ -945,7 +945,7 @@ internal static class DexedPyRenderer
var python = await FindPythonWithDexedAsync(cancellationToken);
if (python is null) return null;
var tempDir = Path.Combine(Path.GetTempPath(), $"aquarium-dx7-patch-render-{Guid.NewGuid():N}");
var tempDir = Path.Combine(Path.GetTempPath(), $"aquasynth-dx7-patch-render-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
try
{
@ -988,7 +988,7 @@ internal static class DexedPyRenderer
private static async Task<PythonCommand?> FindPythonWithDexedAsync(CancellationToken cancellationToken)
{
var configuredPython = Environment.GetEnvironmentVariable("AQUARIUM_DX7_PYTHON");
var configuredPython = Environment.GetEnvironmentVariable("AQUASYNTH_DX7_PYTHON");
var candidates = new List<PythonCommand>();
if (!string.IsNullOrWhiteSpace(configuredPython))
{

View file

@ -1,6 +1,6 @@
using AquariumSynth.Dsl;
using AquaSynth.Dsl;
namespace AquariumSynth.Dsl.Tests;
namespace AquaSynth.Dsl.Tests;
public sealed class Dx7SysExTests
{
@ -221,7 +221,7 @@ public sealed class Dx7SysExTests
}
[Fact]
public void CalibratesDx7OutputLevelToAquariumRouteIndex()
public void CalibratesDx7OutputLevelToAquaSynthRouteIndex()
{
var fullScalePhaseDeviationRadians = 2 * Dx7SysEx.OperatorOutputAmplitude(99) * Dx7SysEx.OperatorModulationRouteIndex;
@ -251,7 +251,7 @@ public sealed class Dx7SysExTests
}
[Fact]
public void CalibratesDx7FeedbackToAquariumFeedbackAmount()
public void CalibratesDx7FeedbackToAquaSynthFeedbackAmount()
{
Assert.Equal(0, Dx7SysEx.OperatorFeedbackAmount(0));
Assert.Equal(0.19f, Dx7SysEx.OperatorFeedbackAmount(5));

View file

@ -10,4 +10,4 @@ Artifacts artifact 152, "DX7 patches 1".
- SHA-256: `5AC120DF3A538DFB9A4B17A218F71B1D136269D2C30A33E676C1D6DDE2AC3AD2`
This fixture is test/development material only. It must not be packed into
`AquariumSynth.Dsl.nupkg`.
`AquaSynth.Dsl.nupkg`.

View file

@ -1,6 +1,6 @@
# Project-Authored ZynAddSubFX Fixtures
These `.xiz` files are tiny Aquarium-authored XML fixtures shaped like
These `.xiz` files are tiny AquaSynth-authored XML fixtures shaped like
ZynAddSubFX instrument files. They are not copied from an upstream Zyn bank.
Purpose:
@ -19,4 +19,4 @@ Current fixtures:
- `pad-harmonic.xiz`
- `vocal-layer.xiz`
License: project-authored for AquariumSynthCSharp tests.
License: project-authored for AquaSynth tests.

View file

@ -4,7 +4,7 @@
<INSTRUMENT>
<INFO>
<string name="name">AQ Additive Lead</string>
<string name="author">AquariumSynthCSharp tests</string>
<string name="author">AquaSynth tests</string>
<string name="comments">Project-authored additive fixture.</string>
<par name="type" value="0"/>
</INFO>

View file

@ -4,7 +4,7 @@
<INSTRUMENT>
<INFO>
<string name="name">AQ Pad Harmonic</string>
<string name="author">AquariumSynthCSharp tests</string>
<string name="author">AquaSynth tests</string>
<string name="comments">Project-authored PAD fixture with explicit oscillator harmonics.</string>
<par name="type" value="0"/>
</INFO>

View file

@ -4,7 +4,7 @@
<INSTRUMENT>
<INFO>
<string name="name">AQ Pad Pure</string>
<string name="author">AquariumSynthCSharp tests</string>
<string name="author">AquaSynth tests</string>
<string name="comments">Project-authored default PAD fixture for pure source-table parity.</string>
<par name="type" value="0"/>
</INFO>

View file

@ -4,7 +4,7 @@
<INSTRUMENT>
<INFO>
<string name="name">AQ Pad Texture</string>
<string name="author">AquariumSynthCSharp tests</string>
<string name="author">AquaSynth tests</string>
<string name="comments">Project-authored PAD fixture.</string>
<par name="type" value="0"/>
</INFO>

View file

@ -4,7 +4,7 @@
<INSTRUMENT>
<INFO>
<string name="name">AQ Vocal Layer</string>
<string name="author">AquariumSynthCSharp tests</string>
<string name="author">AquaSynth tests</string>
<string name="comments">Project-authored layered vocal/formant fixture.</string>
<par name="type" value="0"/>
</INFO>

View file

@ -1,7 +1,7 @@
using System.Diagnostics;
using System.IO.Compression;
namespace AquariumSynth.Dsl.Tests;
namespace AquaSynth.Dsl.Tests;
public sealed class PackageBoundaryTests
{
@ -9,20 +9,20 @@ public sealed class PackageBoundaryTests
public async Task NuGetPackageContainsOnlyPublishedSynthSurface()
{
var root = RepositoryRoot();
var outputDir = Path.Combine(Path.GetTempPath(), $"aquarium-pack-{Guid.NewGuid():N}");
var outputDir = Path.Combine(Path.GetTempPath(), $"aquasynth-pack-{Guid.NewGuid():N}");
Directory.CreateDirectory(outputDir);
try
{
var result = await RunAsync(
"dotnet",
["pack", Path.Combine(root, "src", "AquariumSynth.Dsl", "AquariumSynth.Dsl.csproj"), "-c", "Release", "--no-restore", "-o", outputDir]);
["pack", Path.Combine(root, "src", "AquaSynth.Dsl", "AquaSynth.Dsl.csproj"), "-c", "Release", "--no-restore", "-o", outputDir]);
Assert.Equal(0, result.ExitCode);
var package = Assert.Single(Directory.GetFiles(outputDir, "AquariumSynth.Dsl.*.nupkg"));
var package = Assert.Single(Directory.GetFiles(outputDir, "AquaSynth.Dsl.*.nupkg"));
using var archive = ZipFile.OpenRead(package);
var entries = archive.Entries.Select(entry => entry.FullName).ToArray();
Assert.Contains(entries, entry => entry.StartsWith("lib/net10.0/AquariumSynth.Dsl.dll", StringComparison.Ordinal));
Assert.Contains(entries, entry => entry.StartsWith("lib/net10.0/AquaSynth.Dsl.dll", StringComparison.Ordinal));
Assert.Contains("README.md", entries);
Assert.DoesNotContain(entries, entry => entry.StartsWith("tests/", StringComparison.OrdinalIgnoreCase));
Assert.DoesNotContain(entries, entry => entry.StartsWith("Fixtures/", StringComparison.OrdinalIgnoreCase));
@ -44,7 +44,7 @@ public sealed class PackageBoundaryTests
private static string RepositoryRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "AquariumSynthCSharp.slnx")))
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "AquaSynth.slnx")))
{
directory = directory.Parent;
}

View file

@ -1,6 +1,6 @@
using AquariumSynth.Dsl;
using AquaSynth.Dsl;
namespace AquariumSynth.Dsl.Tests;
namespace AquaSynth.Dsl.Tests;
public sealed class PatchScriptTests
{
@ -161,9 +161,9 @@ public sealed class PatchScriptTests
}
[Fact]
public void AquariumPresetsExportFaust()
public void AquaSynthPresetsExportFaust()
{
foreach (var patch in new[] { Presets.AquariumPluck(), Presets.AquariumHeartbeat(), Presets.AquariumVoice(), Presets.Sfxr("pickup") })
foreach (var patch in new[] { Presets.AquaSynthPluck(), Presets.AquaSynthHeartbeat(), Presets.AquaSynthVoice(), Presets.Sfxr("pickup") })
{
var export = FaustEmitter.Emit(patch);
Assert.Contains("process =", export.Source);
@ -832,7 +832,7 @@ public sealed class PatchScriptTests
public async Task FaustCompilerCanEmitCSharpWhenInstalled()
{
var export = FaustEmitter.EmitScript("v w=sin f=440 g=.2 s=.1 d=.2");
var output = Path.Combine(Path.GetTempPath(), $"aquarium-synth-{Guid.NewGuid():N}.cs");
var output = Path.Combine(Path.GetTempPath(), $"aquasynth-{Guid.NewGuid():N}.cs");
try
{
var validation = await FaustCompiler.CompileAsync(
@ -916,7 +916,7 @@ public sealed class PatchScriptTests
private static string RepositoryRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "AquariumSynthCSharp.slnx")))
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "AquaSynth.slnx")))
{
directory = directory.Parent;
}

View file

@ -1,6 +1,6 @@
using AquariumSynth.Dsl;
using AquaSynth.Dsl;
namespace AquariumSynth.Dsl.Tests;
namespace AquaSynth.Dsl.Tests;
public sealed class SfxrPatchReferenceTests
{

View file

@ -1,8 +1,8 @@
using System.IO.Compression;
using System.Text;
using AquariumSynth.Dsl;
using AquaSynth.Dsl;
namespace AquariumSynth.Dsl.Tests;
namespace AquaSynth.Dsl.Tests;
public sealed class ZynInstrumentTests
{
@ -18,7 +18,7 @@ public sealed class ZynInstrumentTests
ZynInstrumentReader.SourceForBytes("fixture://zyn/project/additive-lead.xiz", "project-authored", bytes));
Assert.Equal("AQ Additive Lead", instrument.Name);
Assert.Equal("AquariumSynthCSharp tests", instrument.Author);
Assert.Equal("AquaSynth tests", instrument.Author);
Assert.Contains(reference.Features, feature => feature.Name == "engine_add" && feature.Value == "1");
Assert.Contains(reference.Features, feature => feature.Name == "engine_sub" && feature.Value == "0");
Assert.Contains(reference.Features, feature => feature.Name == "engine_pad" && feature.Value == "0");
@ -148,11 +148,11 @@ public sealed class ZynInstrumentTests
}
[Fact]
public void RebuildsProjectAuthoredPadAsAquariumSpectrumScript()
public void RebuildsProjectAuthoredPadAsAquaSynthSpectrumScript()
{
var path = FixturePath("ZynAddSubFX", "ProjectAuthored", "pad-harmonic.xiz");
var rebuild = ZynInstrumentReader.RebuildFirstPadAsAquariumScript(path, tableRootFrequencyHz: 77.7813f);
var rebuild = ZynInstrumentReader.RebuildFirstPadAsAquaSynthScript(path, tableRootFrequencyHz: 77.7813f);
var patch = PatchScript.Parse(rebuild.Script);
Assert.Equal("AQ Pad Harmonic", rebuild.InstrumentName);
@ -181,7 +181,7 @@ public sealed class ZynInstrumentTests
return;
}
var rebuild = ZynInstrumentReader.RebuildFirstPadAsAquariumScript(path, tableRootFrequencyHz: 109.999f);
var rebuild = ZynInstrumentReader.RebuildFirstPadAsAquaSynthScript(path, tableRootFrequencyHz: 109.999f);
var patch = PatchScript.Parse(rebuild.Script);
var bank = Assert.Single(patch.SpectralBanks);
@ -200,7 +200,7 @@ public sealed class ZynInstrumentTests
return;
}
var rebuild = ZynInstrumentReader.RebuildEnabledPadsAsAquariumScript(
var rebuild = ZynInstrumentReader.RebuildEnabledPadsAsAquaSynthScript(
path,
new Dictionary<int, ZynPadTableRoot>
{
@ -275,7 +275,7 @@ public sealed class ZynInstrumentTests
private static string RepositoryRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "AquariumSynthCSharp.slnx")))
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "AquaSynth.slnx")))
{
directory = directory.Parent;
}

View file

@ -1,6 +1,6 @@
using System.Diagnostics;
namespace AquariumSynth.Dsl.Tests;
namespace AquaSynth.Dsl.Tests;
public sealed class ZynReferenceParityTests
{
@ -12,8 +12,8 @@ public sealed class ZynReferenceParityTests
Assert.True(Directory.Exists(zynRoot), "ZynAddSubFX reference source submodule is missing. Run `git submodule update --init --recursive`.");
Assert.True(File.Exists(Path.Combine(zynRoot, "COPYING")), "ZynAddSubFX reference source should keep its GPL license file.");
Assert.True(File.Exists(Path.Combine(zynRoot, "src", "Params", "PADnoteParameters.cpp")), "PAD parameter implementation is the parity source, not Aquarium runtime code.");
Assert.True(File.Exists(Path.Combine(zynRoot, "src", "Synth", "PADnote.cpp")), "PAD note implementation is the parity source, not Aquarium runtime code.");
Assert.True(File.Exists(Path.Combine(zynRoot, "src", "Params", "PADnoteParameters.cpp")), "PAD parameter implementation is the parity source, not AquaSynth runtime code.");
Assert.True(File.Exists(Path.Combine(zynRoot, "src", "Synth", "PADnote.cpp")), "PAD note implementation is the parity source, not AquaSynth runtime code.");
Assert.Contains("GNU GENERAL PUBLIC LICENSE", await File.ReadAllTextAsync(Path.Combine(zynRoot, "COPYING")));
var revision = await RunAsync("git", ["-C", zynRoot, "rev-parse", "HEAD"]);
@ -35,12 +35,12 @@ public sealed class ZynReferenceParityTests
var artifactDir = Path.Combine(root, "artifacts", "parity", "zyn-pad-reference", RunFolderName("pad-texture"));
Directory.CreateDirectory(artifactDir);
var input = Path.Combine(root, "tests", "AquariumSynth.Dsl.Tests", "Fixtures", "ZynAddSubFX", "ProjectAuthored", "pad-texture.xiz");
var input = Path.Combine(root, "tests", "AquaSynth.Dsl.Tests", "Fixtures", "ZynAddSubFX", "ProjectAuthored", "pad-texture.xiz");
var zynRaw = Path.Combine(artifactDir, "reference-zyn.f32");
var zynWav = Path.Combine(artifactDir, "reference-zyn.wav");
var zynTableRaw = Path.Combine(artifactDir, "reference-zyn-table0.f32");
var zynTableWav = Path.Combine(artifactDir, "reference-zyn-table0.wav");
var aquaWav = Path.Combine(artifactDir, "candidate-aquarium.wav");
var aquaWav = Path.Combine(artifactDir, "candidate-aquasynth.wav");
var reportPath = Path.Combine(artifactDir, "report.txt");
var command = string.Join(' ', [
@ -93,7 +93,7 @@ public sealed class ZynReferenceParityTests
$"zyn_table_rms: {Rms(zynTableSamples):0.######}"
};
var rebuild = ZynInstrumentReader.RebuildFirstPadAsAquariumScript(input, ParseBaseFrequency(tableReference.Stdout));
var rebuild = ZynInstrumentReader.RebuildFirstPadAsAquaSynthScript(input, ParseBaseFrequency(tableReference.Stdout));
report.AddRange(rebuild.MatchedFeatures.Select(feature => $"matched_{feature.Name}: {feature.Value}"));
report.AddRange(rebuild.MissingFeatures.Select(feature => $"missing_{feature.Name}: {feature.Value} - {feature.Notes}"));
var aquaExport = FaustEmitter.EmitScript(rebuild.Script, new FaustExportOptions("zyn_pad_texture"));
@ -129,7 +129,7 @@ public sealed class ZynReferenceParityTests
return;
}
var fixtureDir = Path.Combine(root, "tests", "AquariumSynth.Dsl.Tests", "Fixtures", "ZynAddSubFX", "ProjectAuthored");
var fixtureDir = Path.Combine(root, "tests", "AquaSynth.Dsl.Tests", "Fixtures", "ZynAddSubFX", "ProjectAuthored");
var artifactDir = Path.Combine(root, "artifacts", "parity", "zyn-pad-fixtures");
Directory.CreateDirectory(artifactDir);
@ -156,7 +156,7 @@ public sealed class ZynReferenceParityTests
Assert.Equal(0, note.ExitCode);
var table = await RunAsync(bash, ["-lc", ZynPadCommand(root, input, tableRaw, 0, "0")]);
Assert.Equal(0, table.ExitCode);
var rebuild = ZynInstrumentReader.RebuildFirstPadAsAquariumScript(input, ParseBaseFrequency(table.Stdout));
var rebuild = ZynInstrumentReader.RebuildFirstPadAsAquaSynthScript(input, ParseBaseFrequency(table.Stdout));
await File.WriteAllTextAsync(Path.Combine(runDir, "candidate.aqua"), rebuild.Script);
var noteSamples = await ReadFloat32Async(noteRaw);
@ -254,7 +254,7 @@ public sealed class ZynReferenceParityTests
var noteWav = Path.Combine(runDir, "reference-zyn.wav");
var tableRaw = Path.Combine(runDir, "reference-zyn-table0.f32");
var tableWav = Path.Combine(runDir, "reference-zyn-table0.wav");
var aquaWav = Path.Combine(runDir, "candidate-aquarium.wav");
var aquaWav = Path.Combine(runDir, "candidate-aquasynth.wav");
var noteStdouts = new List<string>();
var noteLayers = new List<float[]>();
@ -290,7 +290,7 @@ public sealed class ZynReferenceParityTests
}
var noteSamples = SumSamples(noteLayers);
var rebuild = ZynInstrumentReader.RebuildEnabledPadsAsAquariumScript(input, tableRoots);
var rebuild = ZynInstrumentReader.RebuildEnabledPadsAsAquaSynthScript(input, tableRoots);
await File.WriteAllTextAsync(Path.Combine(runDir, "candidate.aqua"), rebuild.Script);
Assert.True(noteSamples.Length > 0);
@ -361,7 +361,7 @@ public sealed class ZynReferenceParityTests
private static string RepositoryRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "AquariumSynthCSharp.slnx")))
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "AquaSynth.slnx")))
{
directory = directory.Parent;
}

View file

@ -1,8 +1,8 @@
/*
Test-only AquariumSynthCSharp ZynAddSubFX PAD oracle.
Test-only AquaSynth ZynAddSubFX PAD oracle.
This file links against the pinned ZynAddSubFX reference source in an ignored
artifact build. It is development/parity tooling, not Aquarium runtime code.
artifact build. It is development/parity tooling, not AquaSynth runtime code.
*/
#include "../globals.h"