Add Zyn PAD fixture survey

This commit is contained in:
Meta Vens 2026-05-15 01:07:40 +01:00
commit 19210ca33f
7 changed files with 170 additions and 8 deletions

View file

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

View file

@ -85,7 +85,7 @@
},
"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. 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, not translation. Upstream Zyn presets live in a separate bank submodule and should not be vendored until license/provenance is explicit. 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`. 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 remaining gap is source-table/profile and envelope semantics, not more decorative patch layers. The report now also emits `pad-texture-zyn-table0.wav` for source-table listening. The hardened reader parses the observed upstream instrument bank at commit e9f64a9: 1358/1358 .xiz files. 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.",
"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 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 observed upstream instrument bank at commit e9f64a9: 1358/1358 .xiz files. 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.",
"next": "Use the Zyn PAD oracle report to isolate whether the main mismatch is source table generation, note playback/gain, envelope shape, or fixture mapping before adding more Zyn DSL surface."
},
"note_and_envelope_model": {
@ -101,7 +101,7 @@
},
"last_verified": {
"command": "powershell -NoProfile -ExecutionPolicy Bypass -File tools\\zyn-reference\\build-zyn-reference.ps1; dotnet test AquariumSynthCSharp.slnx --no-restore",
"result": "Single coherent PAD table and gain trim removed detuned/loud impression; score 0.6211; C# suite 97 passed",
"commit": "pending Zyn PAD coherent table pass"
"result": "Added PAD fixture survey; pad-pure and pad-texture match, pad-harmonic gives a real second rung; C# suite 98 passed",
"commit": "pending Zyn PAD fixture survey"
}
}

View file

@ -1,6 +1,6 @@
project: AquariumSynthCSharp
spine_version: 1
updated: 2026-05-15T02:05:00+01:00
updated: 2026-05-15T02:35:00+01:00
prime_directive: >
Build coherent synth machinery. References exist to reveal missing structure,
@ -101,7 +101,7 @@ 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. 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. 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`. The optional PAD report emits note and source-table WAV/listening artifacts. After cutting unearned noise/drive/soft-clip, adding interpolated table reads, raising Aquarium spectral tables to 131072 frames, splitting table root from playback frequency, removing gratuitous spread/vibrato, collapsing same-note PAD voices into one coherent table, and trimming gain, the project-authored PAD report is close by ear and still not formally passing: log-mel 0.35669, envelope 0.347572, RMS ratio 1.001704, centroid ratio 1.004072, score 0.6211. A non-vendored upstream bank survey parsed 1358/1358 .xiz files and identified named kit/layer routing as the first owner abstraction."
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. 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`. The optional PAD reports emit note and source-table WAV/listening artifacts. After cutting unearned noise/drive/soft-clip, adding interpolated table reads, raising Aquarium spectral tables to 131072 frames, splitting table root from playback frequency, removing gratuitous spread/vibrato, collapsing same-note PAD voices into one coherent table, and trimming gain, the project-authored PAD report is close by ear and still not formally passing: log-mel 0.35669, envelope 0.347572, RMS ratio 1.001704, centroid ratio 1.004072, score 0.6211. The PAD fixture survey shows `pad-pure` and `pad-texture` render identically through Zyn, while `pad-harmonic` changes the source and becomes the next real rung. A non-vendored upstream bank survey parsed 1358/1358 .xiz files and identified named kit/layer routing as the first owner abstraction."
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."
@ -150,8 +150,11 @@ next_agent_start_here:
table `root` from playback `freq`, removes gratuitous spread/vibrato,
collapses same-note PAD voices into one coherent table, trims gain, and now
reports log-mel 0.35669, envelope 0.347572, RMS ratio 1.001704, centroid
ratio 1.004072, score 0.6211. Next pressure is source-table/profile and
envelope semantics, not more decorative layers.
ratio 1.004072, score 0.6211. A PAD fixture survey now writes Zyn note/table
WAVs for `pad-pure`, `pad-texture`, and `pad-harmonic`; `pad-pure` and
`pad-texture` are identical in the oracle, while `pad-harmonic` is real
new pressure. Next pressure is source-table/profile and envelope semantics,
not more decorative layers.
must_read:
- AGENTS.md
- docs/audio-doctrine.md
@ -164,6 +167,17 @@ next_agent_start_here:
- "dotnet test AquariumSynthCSharp.slnx --no-restore"
completed_slices:
- date: 2026-05-15
summary: >
Added more project-authored PAD fixtures and a Zyn-only PAD fixture
survey. `pad-pure.xiz` captures the default pure PAD path, and
`pad-harmonic.xiz` adds explicit OSCIL harmonics. The survey writes Zyn
note/table WAVs and metrics under `artifacts/parity/zyn-pad-fixtures`.
First result: `pad-pure` and the old `pad-texture` render identically
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"
- date: 2026-05-15
summary: >
Removed the detuned impression in the Zyn PAD rebuild by collapsing the

View file

@ -8,7 +8,15 @@ Purpose:
- exercise Zyn instrument feature extraction without vendoring unclear preset
licenses;
- keep the first Zyn slice focused on structural classification, not audio
translation;
translation;
- preserve a clean NuGet boundary. Test fixtures are development material only.
Current fixtures:
- `additive-lead.xiz`
- `pad-pure.xiz`
- `pad-texture.xiz`
- `pad-harmonic.xiz`
- `vocal-layer.xiz`
License: project-authored for AquariumSynthCSharp tests.

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ZynAddSubFX-data>
<ZynAddSubFX-data version-major="1" version-minor="0">
<INSTRUMENT>
<INFO>
<string name="name">AQ Pad Harmonic</string>
<string name="author">AquariumSynthCSharp tests</string>
<string name="comments">Project-authored PAD fixture with explicit oscillator harmonics.</string>
<par name="type" value="0"/>
</INFO>
<INSTRUMENT_KIT>
<INSTRUMENT_KIT_ITEM id="0">
<par_bool name="enabled" value="yes"/>
<string name="name">Harmonic PAD</string>
<par_bool name="pad_enabled" value="yes"/>
<PAD_SYNTH_PARAMETERS>
<OSCIL>
<HARMONICS>
<HARMONIC id="1">
<par name="mag" value="127"/>
<par name="phase" value="64"/>
</HARMONIC>
<HARMONIC id="2">
<par name="mag" value="104"/>
<par name="phase" value="64"/>
</HARMONIC>
<HARMONIC id="3">
<par name="mag" value="88"/>
<par name="phase" value="64"/>
</HARMONIC>
</HARMONICS>
</OSCIL>
<AMPLITUDE_PARAMETERS>
<AMPLITUDE_ENVELOPE>
<par_bool name="free_mode" value="yes"/>
</AMPLITUDE_ENVELOPE>
</AMPLITUDE_PARAMETERS>
</PAD_SYNTH_PARAMETERS>
</INSTRUMENT_KIT_ITEM>
</INSTRUMENT_KIT>
</INSTRUMENT>
</ZynAddSubFX-data>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ZynAddSubFX-data>
<ZynAddSubFX-data version-major="1" version-minor="0">
<INSTRUMENT>
<INFO>
<string name="name">AQ Pad Pure</string>
<string name="author">AquariumSynthCSharp tests</string>
<string name="comments">Project-authored default PAD fixture for pure source-table parity.</string>
<par name="type" value="0"/>
</INFO>
<INSTRUMENT_KIT>
<INSTRUMENT_KIT_ITEM id="0">
<par_bool name="enabled" value="yes"/>
<string name="name">Pure PAD</string>
<par_bool name="pad_enabled" value="yes"/>
<PAD_SYNTH_PARAMETERS>
<AMPLITUDE_PARAMETERS>
<AMPLITUDE_ENVELOPE>
<par_bool name="free_mode" value="yes"/>
</AMPLITUDE_ENVELOPE>
</AMPLITUDE_PARAMETERS>
</PAD_SYNTH_PARAMETERS>
</INSTRUMENT_KIT_ITEM>
</INSTRUMENT_KIT>
</INSTRUMENT>
</ZynAddSubFX-data>

View file

@ -114,6 +114,66 @@ public sealed class ZynReferenceParityTests
await File.WriteAllLinesAsync(reportPath, report);
}
[Fact]
public async Task ZynPadReferenceRendererSurveysProjectAuthoredPadFixturesWhenBuilt()
{
var root = RepositoryRoot();
var bash = @"C:\msys64\usr\bin\bash.exe";
var renderer = Path.Combine(root, "artifacts", "zyn-reference-build-msys", "src", "Tests", "ZynPadReference.exe");
if (!File.Exists(bash) || !File.Exists(renderer))
{
return;
}
var fixtureDir = Path.Combine(root, "tests", "AquariumSynth.Dsl.Tests", "Fixtures", "ZynAddSubFX", "ProjectAuthored");
var artifactDir = Path.Combine(root, "artifacts", "parity", "zyn-pad-fixtures");
Directory.CreateDirectory(artifactDir);
var report = new List<string>
{
"Zyn PAD fixture survey",
$"renderer: {renderer}"
};
foreach (var input in Directory.GetFiles(fixtureDir, "pad-*.xiz").Order(StringComparer.OrdinalIgnoreCase))
{
var name = Path.GetFileNameWithoutExtension(input);
var instrument = ZynInstrumentReader.ParseFile(input);
Assert.Contains(instrument.KitItems, item => item.Enabled && item.Engines.Contains(ZynEngine.PadSynth));
var noteRaw = Path.Combine(artifactDir, $"{name}-zyn.f32");
var noteWav = Path.Combine(artifactDir, $"{name}-zyn.wav");
var tableRaw = Path.Combine(artifactDir, $"{name}-table0.f32");
var tableWav = Path.Combine(artifactDir, $"{name}-table0.wav");
var note = await RunAsync(bash, ["-lc", ZynPadCommand(root, input, noteRaw, "note", "261.6256", "1.5")]);
Assert.Equal(0, note.ExitCode);
var table = await RunAsync(bash, ["-lc", ZynPadCommand(root, input, tableRaw, "0")]);
Assert.Equal(0, table.ExitCode);
var noteSamples = await ReadFloat32Async(noteRaw);
var tableSamples = await ReadFloat32Async(tableRaw);
Assert.True(noteSamples.Length > 0);
Assert.True(tableSamples.Length > noteSamples.Length);
WriteWav(noteWav, noteSamples, 44100, 0.9f);
WriteWav(tableWav, tableSamples, 44100, 0.9f);
var features = AudioAnalyzer.AnalyzeAudio(noteSamples).Features;
report.Add("");
report.Add($"fixture: {name}");
report.Add($"note_stdout: {note.Stdout.Trim()}");
report.Add($"table_stdout: {table.Stdout.Trim()}");
report.Add($"peak: {Peak(noteSamples):0.######}");
report.Add($"rms: {Rms(noteSamples):0.######}");
report.Add($"duration: {features.DurationSeconds:0.######}");
report.Add($"centroid: {features.SpectralCentroidHz:0.######}");
report.Add($"table_peak: {Peak(tableSamples):0.######}");
report.Add($"table_rms: {Rms(tableSamples):0.######}");
}
await File.WriteAllLinesAsync(Path.Combine(artifactDir, "report.txt"), report);
}
private static string RepositoryRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
@ -157,6 +217,17 @@ public sealed class ZynReferenceParityTests
private static string BashQuote(string value) => "'" + value.Replace("'", "'\\''") + "'";
private static string ZynPadCommand(string root, string input, string output, params string[] modeArguments) =>
string.Join(' ', [
"export PATH=/mingw64/bin:/usr/bin:/bin:$PATH;",
$"cd {BashQuote(ToMsysPath(root))};",
"./artifacts/zyn-reference-build-msys/src/Tests/ZynPadReference.exe",
BashQuote(ToMsysPath(input)),
BashQuote(ToMsysPath(output)),
"0",
..modeArguments
]);
private static async Task<float[]> ReadFloat32Async(string path)
{
var bytes = await File.ReadAllBytesAsync(path);