Map Zyn PAD filter cutoff curve

This commit is contained in:
Meta Vens 2026-05-15 12:03:12 +01:00
commit 85245363b0
5 changed files with 19 additions and 11 deletions

View file

@ -821,7 +821,8 @@ public static class ZynInstrumentReader
return 1;
}
return Math.Clamp(DescendantIntParam(filter, "freq", 127) / 127f, 0.02f, 1f);
var cutoffHz = MathF.Pow(2.0f, (DescendantIntParam(filter, "freq", 127) / 64.0f - 1.0f) * 5.0f + 9.96578428f);
return Math.Clamp(cutoffHz / 18000.0f, 20.0f / 18000.0f, 1f);
}
private static int IntParam(XElement? root, string name, int fallback)

View file

@ -79,4 +79,4 @@
{"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 low-pass filter freq onto Aqua layer lpf before chasing subtler PAD table differences.","evidence":"DoublePadBass kit0 filter freq=12 and kit1 freq=61. Mapping those to lpf=0.094488 and lpf=0.480315 improved log-mel from 0.493451 to 0.421699 and score from 0.376854 to 0.412022. Filter envelopes/LFOs remain missing pressure."}
{"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."}

View file

@ -86,7 +86,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. 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.",
"next": "Continue Zyn PAD parity from the sampled OscilGen stage-order, multi-kit PAD, and static global-filter paths. Ghost Ensemble now expands its power-base multi-harmonic OscilGen source instead of collapsing into a generic two-partial pad, and DoublePadBass now rebuilds both enabled PAD kit items with per-kit static low-pass filters. DoublePadBass and Organ Choir Pad2 still miss enough to keep parity open. Do not claim Zyn PAD parity until broader OscilGen filtering/waveshaping/adaptive-harmonic behavior and note/global filter envelope/LFO ownership are mapped or deliberately excluded."
"next": "Continue Zyn PAD parity from the sampled OscilGen stage-order, multi-kit PAD, and Zyn-curve static global-filter paths. Ghost Ensemble now expands its power-base multi-harmonic OscilGen source instead of collapsing into a generic two-partial pad, and DoublePadBass now rebuilds both enabled PAD kit items with per-kit static low-pass filters using Zyn's exponential old-cutoff curve. DoublePadBass is now deep-bass-first but under-loud; remaining pressure is filter envelope/level recovery. Do not claim Zyn PAD parity until broader OscilGen filtering/waveshaping/adaptive-harmonic behavior and note/global filter envelope/LFO ownership are mapped or deliberately excluded."
},
"note_and_envelope_model": {
"rule": "Notes own frequency, gate duration, and host/MIDI source. Envelopes own ADSR level shape.",

View file

@ -95,10 +95,16 @@ Current user-ear correction:
kit0 has filter `category=0 type=2 freq=12`, kit1 has `freq=61`. Static Zyn
PAD low-pass now maps to layer `lpf`; the helper had to read nested `FILTER`
params, not direct `FILTER_PARAMETERS` children.
- Latest DoublePadBass after static LPF mapping: log-mel `0.421699`, score
`0.412022`, with generated layers `doublepadbass_0 lpf=0.094488` and
`doublepadbass_1 lpf=0.480315`. Filter envelope/LFO still remains explicit
missing pressure.
- User still heard Aqua as all kazoo, while Zyn was deep bass with a tiny kazoo
overtone. The linear `freq/127` LPF mapping was wrong: Zyn's old filter
cutoff maps through `2^((Pfreq / 64 - 1) * 5 + log2(1000))`. Kit0
`freq=12` is roughly 60 Hz, not 1.7 kHz; kit1 `freq=61` is roughly 850 Hz,
not 8.6 kHz.
- Latest DoublePadBass after Zyn filter curve mapping: log-mel `0.334546`,
score `0.351535`, RMS ratio `0.464596`, with generated layers
`doublepadbass_0 lpf=0.003325` and `doublepadbass_1 lpf=0.047225`. The
character should now be deep-bass-first; remaining pressure is filter
envelope/level recovery, not more static high-band brightness.
Completed this slice:

View file

@ -174,10 +174,11 @@ completed_slices:
after user listening identified DoublePadBass as over-emphasizing kit0's
high buzz while the reference character comes from kit1's low rumble.
The importer now reads nested `FILTER` params under
`FILTER_PARAMETERS`; kit0's `freq=12` lowers to `lpf=0.094488`, while
kit1's `freq=61` lowers to `lpf=0.480315`. The louder gain-cap
experiment stayed cut. Filter envelopes and LFOs remain explicit missing
pressure.
`FILTER_PARAMETERS` and uses Zyn's old filter cutoff curve
`2^((Pfreq / 64 - 1) * 5 + log2(1000))`; kit0's `freq=12` lowers to
`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"
- date: 2026-05-15
summary: >