Add Zyn reference rebuilds

This commit is contained in:
Meta Vens 2026-05-14 19:55:36 +01:00
commit 9735ef0bdd
8 changed files with 281 additions and 13 deletions

View file

@ -118,7 +118,11 @@ First deliverable:
The current classifier identifies active ADD/SUB/PAD engines, enabled kit
items, layering, envelopes, free envelopes, LFOs, filters, formants, and
effects.
- Rebuild one additive lead, one PAD texture, and one formant/vocal patch.
- Rebuild one additive lead, one PAD texture, and one formant/vocal patch. The
first rebuild catalog entries now parse/export and record the first Zyn
missing abstractions: additive harmonic-bank syntax, PAD-style spectral
source generation, free envelopes for normal voices, named kit/layer routing,
and richer formant/vowel morphing.
Do not vendor the upstream Zyn instrument-bank submodule until its preset
license/provenance is explicit enough for this repo. Use project-authored

View file

@ -606,6 +606,8 @@ public static class BuiltInScripts
public static readonly IReadOnlyList<string> Dx7StyleNames = ["algo32-additive-organ", "algo8-bright-pair", "public-domain-mc-mm-5-3"];
public static readonly IReadOnlyList<string> ZynStyleNames = ["additive-lead", "pad-texture", "vocal-layer"];
public const string Dx7StyleAlgorithm32AdditiveOrgan = """
patch
gain=0.58
@ -755,6 +757,135 @@ public static class BuiltInScripts
release=0.004
""";
public const string ZynStyleAdditiveLead = """
patch
gain=0.72
soft_clip=true
param
name=brightness
path=/macro/brightness
default=0.86
min=0
max=1
step=0.001
param
name=motion
path=/macro/motion
default=0.018
min=0
max=0.05
step=0.001
unit=ratio
defaults
wave=sine
attack=0.006
sustain=0.52
decay=0.34
sustain_level=0.72
lpf=@/macro/brightness
hpf=0.01
vibrato=@/macro/motion
vibrato_hz=5.5
voice freq=220 gain=0.16
voice freq=440 gain=0.075
voice freq=660 gain=0.045
voice freq=880 gain=0.025
""";
public const string ZynStylePadTexture = """
patch
gain=0.56
soft_clip=true
param
name=spread
path=/macro/spread
default=0.018
min=0
max=0.04
step=0.001
unit=ratio
defaults
wave=saw
attack=0.55
sustain=1.8
decay=1.4
sustain_level=0.78
lpf=0.42
hpf=0.025
drive=0.04
mod
name=slow_air
wave=sine
hz=0.18
lpf=0.18
gain=0.05
voice freq=130.8128 gain=0.07 vibrato=@/macro/spread vibrato_hz=0.09
voice freq=196 gain=0.052 vibrato=0.012 vibrato_hz=0.13
voice freq=261.6256 gain=0.044 wave=triangle vibrato=0.009 vibrato_hz=0.11
voice wave=noise freq=1800 gain=0.018 noise=0.82 attack=0.8 sustain=1.7 decay=1.1 lpf=0.36 hpf=0.22
""";
public const string ZynStyleVocalLayer = """
patch
gain=0.64
soft_clip=true
param
name=vowel
path=/macro/vowel
default=0.58
min=0
max=1
step=0.001
defaults
attack=0.08
sustain=0.95
decay=0.65
sustain_level=0.7
lpf=0.72
hpf=0.04
mod
name=vowel_sway
wave=sine
hz=0.7
formant_mix=0.22
gain=0.04
voice
wave=saw
freq=174.614
gain=0.11
formants=520:90:0.9,1080:150:1,2450:280:0.35
formant_mix=@/macro/vowel
voice
wave=triangle
freq=87.307
gain=0.075
lpf=0.48
drive=0.08
voice
wave=noise
freq=2600
gain=0.018
noise=0.75
hpf=0.58
lpf=0.62
formants=700:120:0.5,1600:220:0.6
formant_mix=0.3
""";
public static readonly IReadOnlyList<(string Name, string Script)> AdvancedReferenceScripts =
[
("aurora-pad", """
@ -945,6 +1076,7 @@ public static class BuiltInScripts
foreach (var item in FmBellPrimitiveGolfScripts) yield return ("fm-bell", item.Name, item.Script);
foreach (var item in WobbleBassPrimitiveGolfScripts) yield return ("wobble-bass", item.Name, item.Script);
foreach (var item in ReferenceRebuildCatalog.Dx7Rebuilds) yield return ("dx7", item.Name, item.Script);
foreach (var item in ReferenceRebuildCatalog.ZynRebuilds) yield return ("zynaddsubfx", item.Name, item.Script);
foreach (var item in AdvancedReferenceScripts) yield return ("advanced", item.Name, item.Script);
}

View file

@ -12,6 +12,62 @@ public sealed record ReferenceRebuild(
public static class ReferenceRebuildCatalog
{
public static readonly IReadOnlyList<ReferenceRebuild> ZynRebuilds =
[
new(
"zyn/project-additive-lead/aquarium",
"zynaddsubfx",
"Project additive lead",
"zyn/project/additive-lead",
BuiltInScripts.ZynStyleAdditiveLead,
[
new("engine_add", "1", "Represented as explicit additive sine partial voices."),
new("partial_voice_count", "4", "The additive shape is represented as four explicit Aquarium 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("additive_harmonic_bank", "Zyn ADDsynth harmonic table", "Aquarium currently writes each partial as a voice; it lacks a terse harmonic-bank source."),
new("zyn_free_envelope_exactness", "Zyn envelope parameter semantics", "The rebuild uses Aquarium ADSR/motion controls rather than Zyn's exact envelope timing model.")
],
"This is the easy Zyn pressure case: Aquarium can say the sound as layered partials, but the syntax is already showing the need for a compact additive harmonic-bank abstraction."),
new(
"zyn/project-pad-texture/aquarium",
"zynaddsubfx",
"Project PAD texture",
"zyn/project/pad-texture",
BuiltInScripts.ZynStylePadTexture,
[
new("engine_pad", "1", "Approximated as slow layered oscillators plus an air/noise layer."),
new("filter_count", "1", "Represented with low-pass/high-pass field sites."),
new("free_envelope_count", "1", "Approximated with slow ADSR timing and macro spread."),
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("pad_spectral_source", "PADsynth wavetable/spectral generation", "Aquarium has no PAD-style spectral source; this rebuild is a texture approximation."),
new("free_envelope_shape", "arbitrary Zyn envelope points", "Aquarium has ADSR and staged operator envelopes, but not a general free envelope for normal voices.")
],
"This is pressure, not a claim of PAD parity. The current voice DSL can sketch the texture, but PAD-style spectral generation deserves its own source authority if repeated targets need it."),
new(
"zyn/project-vocal-layer/aquarium",
"zynaddsubfx",
"Project vocal layer",
"zyn/project/vocal-layer",
BuiltInScripts.ZynStyleVocalLayer,
[
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("formant_filter_count", "1", "Represented with Aquarium 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.")
],
"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.")
];
public static readonly IReadOnlyList<ReferenceRebuild> Dx7Rebuilds =
[
new(
@ -70,6 +126,11 @@ public static class ReferenceRebuildCatalog
public static IEnumerable<ReferenceRebuild> All()
{
foreach (var rebuild in ZynRebuilds)
{
yield return rebuild;
}
foreach (var rebuild in Dx7Rebuilds)
{
yield return rebuild;

View file

@ -58,3 +58,4 @@
{"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":"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"}

View file

@ -81,9 +81,9 @@
"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.",
"lesson": "Start Zyn as structural classification, not translation. Upstream Zyn presets live in a separate bank submodule and should not be vendored until license/provenance is explicit. The current classifier identifies active ADD/SUB/PAD engines, enabled kit items, layering, envelopes, free envelopes, LFOs, filters, formants, and effects so rebuild targets can pressure Aquarium's DSL without pretending parser success is expressivity.",
"next": "Rebuild one additive lead, one PAD texture, and one vocal/formant layer from the project-authored fixtures in readable Aquarium DSL, then name the missing abstractions."
"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.",
"lesson": "Start Zyn as structural classification, not translation. Upstream Zyn presets live in a separate bank submodule and should not be vendored until license/provenance is explicit. The current classifier identifies active ADD/SUB/PAD engines, enabled kit items, layering, envelopes, free envelopes, LFOs, filters, formants, and effects so rebuild targets can pressure Aquarium's DSL without pretending parser success is expressivity. The first rebuilds show Aquarium can sketch these fixtures with existing voices/macros/formants/noise, but the missing abstractions are 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.",
"next": "Choose one Zyn abstraction candidate to promote only after mapping ownership and tests; do not add the whole shopping list at once."
},
"note_and_envelope_model": {
"rule": "Notes own frequency, gate duration, and host/MIDI source. Envelopes own ADSR level shape.",
@ -98,7 +98,7 @@
},
"last_verified": {
"command": "AQUARIUM_DX7_PYTHON=<bundled python> dotnet test AquariumSynthCSharp.slnx --no-restore",
"result": "81 passed; Zyn .xiz parser/classifier added without regressing DX7 parity",
"commit": "pending Zyn feature extraction"
"result": "83 passed; Zyn rebuild catalog added without regressing DX7 parity",
"commit": "pending Zyn rebuild catalog"
}
}

View file

@ -15,6 +15,21 @@ Doctrine update:
Completed this slice:
- Added first Zyn-style Aquarium 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
claims against source fixture features where the same feature name exists.
- Current missing abstractions from this first Zyn pass: terse additive
harmonic-bank syntax, a PAD/spectral source authority, free envelopes for
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`:
83 passed.
Previous slice:
- Started the ZynAddSubFX phase and stopped treating DX7 as the active trench.
- Added `ZynInstrumentReader` for `.xiz`-shaped XML. It reads plain XML and
gzip-compressed XML, extracts instrument metadata and kit items, and emits

View file

@ -93,10 +93,10 @@ reference_targets:
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."
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."
first_slice:
- "Choose the first three Aquarium rebuild targets from the project-authored additive lead, PAD texture, and vocal/formant layer fixtures."
- "Design the smallest readable Aquarium DSL patches that express those categories, then decide which missing abstractions are real."
- "Choose the first Zyn abstraction to promote only if repeated rebuilds justify it: additive harmonic bank, PAD spectral source, free envelope, named layer routing, or richer formant morphing."
- "Do not add all of them at once."
faust:
priority: 3
purpose: "General DSP graph topology pressure test."
@ -126,10 +126,11 @@ next_agent_start_here:
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`.
Next pressure is to rebuild one additive lead, one PAD texture, and one
vocal/formant layer in readable Aquarium DSL, then name the missing
abstractions instead of translating Zyn internals blindly. Leave DX7 alone
unless a regression test fails.
Next pressure is to choose one Zyn abstraction candidate from the first
rebuild evidence: additive harmonic bank, PAD spectral source, free
envelope, named layer routing, or richer formant morphing. Promote only one
after mapping ownership and tests. Leave DX7 alone unless a regression test
fails.
must_read:
- AGENTS.md
- docs/audio-doctrine.md
@ -142,6 +143,17 @@ next_agent_start_here:
- "dotnet test AquariumSynthCSharp.slnx --no-restore"
completed_slices:
- date: 2026-05-14
summary: >
Added first Zyn-style Aquarium 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
fixture features where names overlap. The first concrete Zyn pressure is:
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"
- date: 2026-05-14
summary: >
Started the ZynAddSubFX phase after closing the current DX7 community

View file

@ -246,6 +246,46 @@ public sealed class PatchScriptTests
}
}
[Fact]
public void ZynStyleReferenceRebuildsParseExportAndDeclarePressure()
{
foreach (var rebuild in ReferenceRebuildCatalog.ZynRebuilds)
{
var patch = PatchScript.Parse(rebuild.Script);
var export = FaustEmitter.Emit(patch, new FaustExportOptions(rebuild.Id.Replace('/', '_').Replace('-', '_')));
Assert.Contains("process =", export.Source);
Assert.NotEmpty(rebuild.MatchedFeatures);
Assert.NotEmpty(rebuild.MissingFeatures);
Assert.All(rebuild.MissingFeatures, feature => Assert.False(string.IsNullOrWhiteSpace(feature.Notes)));
}
}
[Fact]
public void ZynReferenceRebuildsTrackFixtureFeaturePressure()
{
var fixtures = new Dictionary<string, string>
{
["zyn/project/additive-lead"] = Path.Combine("ZynAddSubFX", "ProjectAuthored", "additive-lead.xiz"),
["zyn/project/pad-texture"] = Path.Combine("ZynAddSubFX", "ProjectAuthored", "pad-texture.xiz"),
["zyn/project/vocal-layer"] = Path.Combine("ZynAddSubFX", "ProjectAuthored", "vocal-layer.xiz")
};
foreach (var rebuild in ReferenceRebuildCatalog.ZynRebuilds)
{
var instrument = ZynInstrumentReader.ParseFile(FixturePath(fixtures[rebuild.ReferenceId]));
var sourceFeatures = instrument.Features();
foreach (var matched in rebuild.MatchedFeatures)
{
if (sourceFeatures.Any(feature => feature.Name == matched.Name))
{
Assert.Contains(sourceFeatures, feature => feature.Name == matched.Name && feature.Value == matched.Value);
}
}
}
}
[Fact]
public void Dx7Algorithm32RebuildMatchesAdditiveCarrierShape()
{
@ -636,4 +676,7 @@ public sealed class PatchScriptTests
return directory?.FullName ?? throw new InvalidOperationException("could not find repository root");
}
private static string FixturePath(string path) =>
Path.Combine(AppContext.BaseDirectory, "Fixtures", path);
}