993 lines
55 KiB
Markdown
993 lines
55 KiB
Markdown
# Scratch
|
|
|
|
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 AquaSynth work continues elsewhere.
|
|
|
|
Doctrine update:
|
|
|
|
- External synths are parity pressure for AquaSynth's DSL, not internals to
|
|
clone. Only consume targets that can be backed by parity tests proving
|
|
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 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.
|
|
|
|
Current slice:
|
|
|
|
- Organ Choir exposed two separate Zyn PAD authorities that are now mapped:
|
|
analog high-pass filters (`HP1`/`HP2`, stages, and FILTER_ENVELOPE motion)
|
|
and selected PAD table roots for OscilGen shift-base sources
|
|
(`base_function=7`). The root policy is source-driven: normal PAD rebuilds
|
|
keep the sample-0 oracle root, while shift-base OscilGen layers may use the
|
|
selected note table root when the parity oracle provides it.
|
|
- Latest upstream PAD survey after this pass:
|
|
`Organ Choir Pad2` artifact
|
|
`artifacts/parity/zyn-upstream-pad-fixtures/0008-organ-choir-pad2-20260515T162034810`,
|
|
log-mel `0.471483`, envelope `0.302978`, RMS ratio `0.878`,
|
|
centroid ratio `1.011328`, score `0.541933`. The violin-vs-wind issue was
|
|
kit1 source-table shape: Aqua had ignored Zyn `base_function_modulation=1`
|
|
and `adaptive_harmonics=1`, producing a smooth upper harmonic ramp. The
|
|
importer now applies base-function phase modulation before DFT and adaptive
|
|
harmonic remapping at the PAD table root.
|
|
- Direct kit1 table spectrum moved from violin-shaped to wind-shaped:
|
|
400-800 Hz energy is now `0.2713` against Zyn `0.2780`; 8-16 kHz is
|
|
`0.1471` against Zyn `0.1355`.
|
|
- The accepted fixtures stayed on the sample-0 root path: DoublePadBass
|
|
log-mel `0.247674`, score `0.650319`; Ghost Ensemble log-mel `0.369402`,
|
|
score `0.57229`; Soft Pad log-mel `0.317897`, score `0.608833`.
|
|
- User listening now accepts the recent Zyn PAD fixture batch as reading the
|
|
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 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
|
|
shape as future regression context, not the active treadmill. Do not turn the
|
|
base-function-7 selected-root rule into a global selected-table switch. That
|
|
was tested and regressed accepted DoublePadBass.
|
|
|
|
- Zyn PAD per-preset rebuilds now carry first-class Zyn PAD table semantics
|
|
instead of only `spread=0` plus harmonic magnitudes.
|
|
- `SpectralBank` owns `PadSpectrumProfile`, including Zyn PAD mode,
|
|
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.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 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
|
|
Soft Pad and Organ Choir Pad2 log-mel, while sin2x/DoublePadBass/Ghost
|
|
regress. Treat it as live pressure on OscilGen coverage, not solved Zyn
|
|
oscillator parity.
|
|
- Latest focused verification:
|
|
`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`;
|
|
`Organ Choir Pad2` log-mel `0.573216`, score `0.434351`;
|
|
`sin2x pad` log-mel `0.524421`, score `0.360653`;
|
|
`DoublePadBass` log-mel `0.689315`, score `0.226566`;
|
|
`Ghost Ensemble` log-mel `0.482155`, score `0.400393`.
|
|
- Next coherent cut: either complete more OscilGen semantics
|
|
(modulation/waveshaping/filter/adaptive harmonics) or gate the current
|
|
approximation to the cases it truly owns. Do not present the mixed OscilGen
|
|
subset as passing parity.
|
|
|
|
Current follow-up:
|
|
|
|
- Reworked Zyn PAD OscilGen partial extraction into a sampled stage-order
|
|
pipeline for presets with live oscillator processing: base waveform,
|
|
optional oscillator filter/waveshaper order, modulation, harmonic shift, then
|
|
spectrum adjustment. The implementation covers a useful subset of oscillator
|
|
filter types and waveshapers instead of pretending base-function convolution
|
|
alone is the synth.
|
|
- Gated that pipeline so direct harmonic presets remain readable and capped at
|
|
32 partials. This cuts the earlier mistake where base-function-only presets
|
|
grew huge partial lists without improving parity.
|
|
- Latest upstream PAD batch after this pass:
|
|
`sin2x pad` log-mel `0.455193`, score `0.405637` (better than the previous
|
|
OscilGen subset's `0.524421`);
|
|
`Soft Pad` log-mel `0.342738`, score `0.443083`;
|
|
`Ghost Ensemble` log-mel `0.40922`, score `0.421156`;
|
|
`Organ Choir Pad2` log-mel `0.664699`, score `0.369231`;
|
|
`DoublePadBass` log-mel `0.655288`, score `0.246664`.
|
|
- This is still not passing Zyn PAD parity. The likely next missing owners are
|
|
full OscilGen filter/waveshape coverage, adaptive harmonics, and note/global
|
|
filter/envelope behavior. Do not tune thresholds around these numbers.
|
|
|
|
Current user-ear correction:
|
|
|
|
- User clarified Soft Pad itself is fine; Ghost Ensemble was the one collapsing
|
|
into a Soft Pad-like identity. The bug was our direct-harmonic gate ignoring
|
|
Ghost's live `base_function=4` power oscillator because no downstream
|
|
oscillator stage was enabled. The gate now expands power-base multi-harmonic
|
|
OscilGen sources, restoring Ghost to a 32-partial source. Latest Ghost:
|
|
log-mel `0.402789`, score `0.446977`, and the `.aqua` no longer looks like a
|
|
two-partial Soft Pad knockoff.
|
|
- DoublePadBass exposed a separate ownership bug: the preset is `kit_mode=1`
|
|
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
|
|
`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
|
|
(log-mel `0.521113`, RMS ratio `1.220809`), so the cap was cut back to `1.5`.
|
|
- User then identified the remaining DoublePadBass balance issue: Aqua
|
|
over-emphasized kit0's high buzz while Zyn's character comes from kit1's low
|
|
rumble. The cause was visible Zyn global filter data we were still ignoring:
|
|
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.
|
|
- 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.
|
|
- User still heard Aqua as kazoo-first. The next missing authority was not
|
|
brightness but pitch: DoublePadBass PAD frequency params set
|
|
`coarse_detune=15360`, and Zyn's `getdetune` wraps octave `15` to `-1`.
|
|
Aqua had ignored PAD coarse/fine detune and played both spectral tables at
|
|
`261.6256 Hz`; it now emits `freq=130.8128` for both enabled kit layers.
|
|
- The same pass added explicit `lpf_order` so Zyn analog `LP2` filters lower to
|
|
Faust `fi.lowpass(2, ...)` instead of pretending all low-passes are first
|
|
order. Latest DoublePadBass: log-mel `0.259298`, score `0.370218`, centroid
|
|
ratio `1.027115`; remaining pressure is loudness/envelope recovery.
|
|
- 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. 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`,
|
|
score `0.404382`.
|
|
- User then heard the hit level as right but the attack as less sharp, causing
|
|
a blare. The old Zyn envelope time conversion was also wrong: Aqua used
|
|
linear `dt/127`, while Zyn's old envelope time is
|
|
`(2^(dt/127*12)-1)/100`. DoublePadBass kit0 attack `10` is about `0.00925s`,
|
|
not `0.07874s`. The importer now uses Zyn's logarithmic time curve and the
|
|
PAD gain cap is relaxed only to `3.0`, not the full raw Zyn volume multiplier.
|
|
Latest DoublePadBass: log-mel `0.211646`, envelope distance `0.462822`, RMS
|
|
ratio `0.511669`, score `0.450507`.
|
|
- User then heard the shape as right but Zyn still peaking quite a bit louder.
|
|
The remaining level delta was batch-wide output calibration, not another
|
|
DoublePadBass layer issue: latest pre-calibration PAD RMS ratios were
|
|
`0.51..0.78`. Generated Zyn PAD rebuilds now emit `patch gain=1.6`. Latest
|
|
DoublePadBass: WAV peak `0.979` against clipped Zyn reference peak `1.0`,
|
|
log-mel `0.211671`, envelope distance `0.226563`, RMS ratio `0.818669`,
|
|
score `0.631168`.
|
|
- User accepted DoublePadBass and moved the ear target to Ghost Ensemble:
|
|
Aqua over-emphasized upper harmonics and sounded brighter/tinnier than Zyn.
|
|
The first missing general authority was Zyn PAD filter note tracking:
|
|
`freq_track=97` lowers Ghost's cutoff at C4 by the Zyn
|
|
`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. 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)`.
|
|
- Latest Ghost after note tracking + Q:
|
|
artifact `artifacts/parity/zyn-upstream-pad-fixtures/0121-ghost-ensemble-20260515T131053271`,
|
|
log-mel `0.375637`, envelope distance `0.363075`, RMS ratio `1.09995`,
|
|
centroid ratio `1.010727`, score `0.561458`. This improves from the
|
|
post-tracking `0.38424` log-mel / `1.155288` RMS ratio, but a table-harmonic
|
|
diagnostic still shows candidate h8/h10/higher partials hot against the Zyn
|
|
table. Remaining Ghost pressure is OscilGen/PAD source-table shape, not
|
|
static filter ownership.
|
|
- Latest upstream PAD survey after Q kept the accepted batch intact:
|
|
DoublePadBass log-mel `0.247729`, envelope `0.208429`, RMS ratio `0.887621`,
|
|
score `0.650069`; Soft Pad log-mel `0.316754`, score `0.6089`.
|
|
- User then heard Ghost as closer but still overly bright in the middle. A
|
|
Zyn oracle `harmonics` diagnostic was added to `ZynPadReference` so the
|
|
exact normalized OscilGen harmonic vector can be dumped before PAD table
|
|
generation. That proved Ghost's first 32 imported partial ratios already
|
|
match Zyn's OscilGen output; the h8/h10 glare was not a first-page harmonic
|
|
extraction bug.
|
|
- Two tempting cuts were tested and rejected or narrowed:
|
|
using Zyn's 1024-sample OscilGen base table helped Ghost but regressed the
|
|
accepted DoublePadBass batch, so it was cut; raising generated OscilGen
|
|
partial output from 32 to 64 worsened Ghost, so the readable 32-partial cap
|
|
remains.
|
|
- The real Ghost middle-brightness fix was filter lowering ownership: explicit
|
|
`lpf_q` had accidentally replaced a 4-pole Zyn-style low-pass with a single
|
|
Faust `resonlp`, preserving Q but throwing away slope. Faust export now
|
|
cascades resonant low-pass stages according to `lpf_order` when `lpf_q` is
|
|
present. Latest Ghost:
|
|
artifact `artifacts/parity/zyn-upstream-pad-fixtures/0121-ghost-ensemble-20260515T134050872`,
|
|
log-mel `0.369402`, envelope distance `0.363685`, RMS ratio `1.054875`,
|
|
centroid ratio `1.006808`, score `0.57229`. DoublePadBass remains at
|
|
log-mel `0.247748`, envelope `0.208433`, RMS ratio `0.887555`, score
|
|
`0.650029`.
|
|
- User accepted Ghost, DoublePadBass, and Soft Pad as nigh-indistinguishable
|
|
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 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.
|
|
- Latest sin2x after the oscillator-filter index fix:
|
|
artifact `artifacts/parity/zyn-upstream-pad-fixtures/0002-sin2x-pad-20260515T140540381`,
|
|
log-mel `0.479782` (from `0.491437`), envelope distance `0.490113`, RMS
|
|
ratio `1.28196`, centroid ratio `0.998145` (from `1.227953`), score
|
|
`0.430193`. The wrong overtone placement is fixed: remaining sin2x pressure
|
|
is energy concentration/loudness/profile shape, not h3 accidentally wearing
|
|
h2's jacket.
|
|
- Zyn PAD harmonic profile lowering also now computes the profile amplitude
|
|
multiplier coordinate before width scaling, matching Zyn's `origx`
|
|
behavior. It was effectively neutral for sin2x because that preset's width is
|
|
near full, but the ownership is now faithful to the source formula.
|
|
|
|
Completed this slice:
|
|
|
|
- Added PAD spectral-cloud syntax attached to named layers:
|
|
`spectrum layer=pad_low root=130.8128 spread=.012 partials=1:.07,1.5:.052`.
|
|
- The parser preserves each bank as `SpectralBank` model data. `PadSynthWaveform`
|
|
now generates each bank as a PADsynth-style FFT table: frequency-domain
|
|
harmonic spreading, deterministic random phase, one inverse FFT, and
|
|
normalization. Faust export emits that static `waveform`/`rdtable` source
|
|
before applying the normal voice treatment path. `root`, `spread`, and
|
|
`partials` are authoring-time table shape, not runtime controls.
|
|
- Converted the Zyn PAD texture rebuild from individual PAD body voices to two
|
|
spectral banks under `pad_low` and `pad_high`; the air/noise layer remains a
|
|
normal voice.
|
|
- This is not full Zyn PAD engine parity across every harmonic profile,
|
|
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\AquaSynth.Dsl.Tests\AquaSynth.Dsl.Tests.csproj --no-restore --filter "FullyQualifiedName~SpectralBank|FullyQualifiedName~ZynStyleReferenceRebuildsParseExportAndDeclarePressure|FullyQualifiedName~BuiltInReferenceScriptsParseAndExportFaust"`:
|
|
4 passed.
|
|
- Wavetable replacement verification:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
94 passed.
|
|
- FFT table generator focused verification:
|
|
`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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
94 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Promoted staged rate/level envelopes to normal voices and layer defaults.
|
|
Voice fields now accept `env=rl rates=... levels=... curves=... gate=...`;
|
|
the model stores `Voice.RateLevelEnvelope`, and Faust emission uses
|
|
`rl4_env` for those voices.
|
|
- Converted Zyn PAD texture and vocal-layer rebuilds to use staged envelopes
|
|
through named layers. This gives two non-additive targets asymmetric
|
|
envelope contour authority without inventing a new parser.
|
|
- 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\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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
91 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Added additive harmonic-bank syntax attached to named layers:
|
|
`harmonics layer=body root=220 partials=1:.16,2:.075`.
|
|
- The parser preserves each bank as `HarmonicBank`/`HarmonicPartial` model
|
|
data, then lowers the partials into ordinary voices so Faust output remains
|
|
inspectable.
|
|
- Converted the Zyn additive lead rebuild from repeated partial voices to two
|
|
named banks under `body` and `shine`.
|
|
- 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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
90 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Added Zyn-driven language golf through `layer` declarations. A layer owns
|
|
name, engine tag, optional MIDI key range metadata, default gain, and an
|
|
effect-send label, then lowers to ordinary voices for now.
|
|
- Converted the three Zyn-style rebuilds to use named layers:
|
|
additive `body`/`shine`, PAD `pad_low`/`pad_high`/`air`, and vocal
|
|
`air`/`body`/`breath`.
|
|
- This is an ownership scaffold, not hidden PAD/free-envelope/effect machinery.
|
|
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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
88 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Hardened `ZynInstrumentReader` against the real upstream Zyn instrument bank:
|
|
it now tolerates leading whitespace before XML declarations and counts only
|
|
actual `FORMANT_FILTER`/`FILTER` blocks instead of all internal formant/vowel
|
|
nodes.
|
|
- Added `ZynInstrumentSurvey` to rank `.xiz` directories by feature pressure.
|
|
The non-vendored upstream bank scan parsed `1358/1358` files at
|
|
`zynaddsubfx/instruments` commit `e9f64a9`.
|
|
- Added `docs/zynaddsubfx-pressure-survey.md`. Worst observed target:
|
|
`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.
|
|
- 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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
86 passed.
|
|
|
|
Previous slice:
|
|
|
|
- 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
|
|
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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.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
|
|
neutral `ReferencePatch` features.
|
|
- Added project-authored Zyn fixtures under
|
|
`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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
81 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Addressed the remaining `ANLGSYN 1` beat as DX7 pitch-LFO pressure, not
|
|
amplitude-LFO pressure. Raw `analog1.syx` bytes show `AMD=71` but every
|
|
operator has `AMS=0`; the active path is `PMD=32`, `PMS=1`, sine LFO speed
|
|
`38`, and delay `33`.
|
|
- Added first-class operator-graph vibrato syntax:
|
|
`opgraph ... vibrato=<depth> vibrato_hz=<hz> vibrato_delay=<seconds>`.
|
|
Faust lowering modulates the graph frequency with a faded LFO onset instead
|
|
of a hard delay gate.
|
|
- Lowered DX7 pitch LFO for the public-domain probe through that graph vibrato
|
|
surface. Latest `ANLGSYN 1` metrics with the beat present: score `.697885`,
|
|
log-mel `.19496867`, envelope `.15124573`, zero-crossing `.9430519`.
|
|
- Tested and cut the earlier operator-tremolo direction for this target. It was
|
|
the wrong authority because ANLGSYN's operator `AMS` values are all zero.
|
|
|
|
Previous slice:
|
|
|
|
- Reframed the latest ear report as envelope pressure, not harmonic pressure:
|
|
`{ Mooger }` needs a more aggressive attack contour; `MELLOWSOLO` needed a
|
|
smoother captured tail; `RES SYNTH1` still has a phase-like attack-modulation
|
|
issue even though oscillator sync is enabled.
|
|
- Extended only the `MELLOWSOLO` pressure render from `1.0s` to `1.25s` so the
|
|
existing `0.6s` release after a `0.65s` gate is not chopped by the artifact
|
|
boundary. Latest pressure metrics improve to score `.5722256`, log-mel
|
|
`.3472474`, RMS `.9883968`.
|
|
- Tested and cut a global traced-release-duration change. It shortened releases
|
|
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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
76 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Listening split the latest community set again: `Piano Bass` is currently the
|
|
anchor and should not be disturbed; `ANLGSYN 1` and `RES SYNTH1` were blowing
|
|
out into drone at the modulator peak; `{ Mooger }` and `MELLOWSOLO` still
|
|
have overtone-emphasis issues.
|
|
- Added scoped modulator peak headroom in the DX7 probe lowering:
|
|
algorithm-2 non-carrier applied envelopes cap only Level1 at `.92`.
|
|
`Piano Bass` remains unchanged; `RES SYNTH1` improves to score `.8140223`,
|
|
log-mel `.12369786`, envelope `.06489321`; `ANLGSYN 1` keeps log-mel
|
|
`.19461302` and sustained high-band evidence but aggregate score drops to
|
|
`.6980632`, so its score gate is now `.69`.
|
|
- `MELLOWSOLO` nudges to log-mel `.42906323`, score `.55206907`, but remains a
|
|
pressure artifact, not passing parity.
|
|
- Tested and cut a wider summed-route headroom rule for `{ Mooger }`
|
|
(`5.25`/`5.75` for three-source sums). It worsened Mooger log-mel/envelope
|
|
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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter "Dx7SysExTests|PublicDomainDx7MoogerAndPianoBassMeetRenderedParityWhenInstalled|PublicDomainDx7AnlgSyn1KeepsBuzzingModulationWhenInstalled|PublicDomainDx7MellowSoloWritesPressureWavsWhenInstalled|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled"`:
|
|
28 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Listening report split the remaining DX7 mismatch: `RES SYNTH1` mostly
|
|
needed a harder attack, `ANLGSYN 1` still smells like missing LFO/operator
|
|
attack behavior, and `MELLOWSOLO` is the clearest harmonic mismatch.
|
|
- Fixed two note-dependent DX7 lowering gaps:
|
|
- operator key-scaling now uses the effective played MIDI note after voice
|
|
transpose instead of hardcoded MIDI 60;
|
|
- `Dx7SysEx.OperatorRateScaling` now feeds
|
|
`ApproximateAppliedRateLevelEnvelope`/`TraceInterpolatedEnvelope`.
|
|
- Kept graph gain as loudness authority after the scaling change:
|
|
`Piano Bass` moved from `.90` to `.72`.
|
|
- Added `PublicDomainDx7MellowSoloWritesPressureWavsWhenInstalled`; it writes
|
|
listening artifacts but only gates as pressure (`log-mel <= .45`, score
|
|
`>= .5`) because it is not passing parity by ear.
|
|
- Cut a tempting output-level-dependent envelope trace experiment. It looked
|
|
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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore --filter "Dx7SysExTests|PublicDomainDx7MoogerAndPianoBassMeetRenderedParityWhenInstalled|PublicDomainDx7AnlgSyn1KeepsBuzzingModulationWhenInstalled|PublicDomainDx7MellowSoloWritesPressureWavsWhenInstalled|PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled"`:
|
|
28 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Corrected DX7 ROM COM handling after user listening caught that the
|
|
no-compensation probe had closer harmonics across the community patches and
|
|
was mainly too quiet.
|
|
- `Dx7SysEx.OperatorOutputCompensation` now returns unity. Loudness recovery
|
|
moved to graph gain instead of hidden carrier boosts.
|
|
- Community parity gains with COM disabled:
|
|
`{ Mooger }` `.75`, `Piano Bass` `.90`, `RES SYNTH1` `.75`.
|
|
- `RES SYNTH1` is now in the passing community gate. Latest metrics:
|
|
score `.7277295`, log-mel `.1461968`, envelope `.10764466`, RMS `.941083`,
|
|
zero-crossing `.1.1022931`.
|
|
- `DX1 LEAD B` and `MELLOWSOLO` sound closer by ear with COM disabled and
|
|
graph gain restored, but still fail numeric gates. Latest survey:
|
|
`DX1 LEAD B` score `.45156077`, log-mel `.43533683`, RMS `.8193191`;
|
|
`MELLOWSOLO` score `.5359293`, log-mel `.39420125`, RMS `.99559265`.
|
|
- Verified with bundled Python/dexed-py:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
74 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Fixed the remaining `ANLGSYN 1` drift that listening caught after the
|
|
conditional output-clip pass. The old candidate still lost 2.5-5 kHz energy
|
|
midway through the note.
|
|
- Added `sustainFloor` to `Dx7SysEx.ApproximateAppliedRateLevelEnvelope` and
|
|
uses a `.9` floor for max-feedback source operators in the DX7 probe
|
|
lowering. This keeps the self-feedback source driving the loop after the
|
|
attack instead of sagging into `deeooh`.
|
|
- Added a sustained 2.5-5 kHz band-energy parity gate for `ANLGSYN 1`; the old
|
|
candidate was around `.175` at the sustained check, while the new lowering is
|
|
above `1.0`.
|
|
- Raised `Piano Bass` graph gain to `.30`; latest metrics: score `.7552858`,
|
|
log-mel `.16204594`, envelope `.06413663`, RMS `.9909909`.
|
|
- Fresh `ANLGSYN 1`: score `.7245976`, log-mel `.18460114`, envelope
|
|
`.119950555`, RMS `.97143364`, zero-crossing `.97149533`.
|
|
- Verified with bundled Python/dexed-py:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
74 passed.
|
|
|
|
Earlier slice:
|
|
|
|
- Tightened `ANLGSYN 1` after listening caught the `deeooh` failure: the
|
|
max-feedback candidate had a better attack but lost sustained buzz midway.
|
|
- Added conditional output nonlinearity in the DX7 probe lowering:
|
|
`soft_clip=true` only when a voice has DX7 feedback `7` and a self-feedback
|
|
topology. PRC stays `soft_clip=false`.
|
|
- Fresh `ANLGSYN 1`: score `.70946133`, log-mel `.18456098`, envelope
|
|
`.10628832`, RMS `.9816304`, zero-crossing `.8657629`, centroid `.95726264`.
|
|
- Fresh PRC after the conditional clip gate: score `.7546429`, log-mel
|
|
`.13874851`, envelope `.067291394`, RMS `.99121606`, zero-crossing
|
|
`.95325506`.
|
|
|
|
Earlier slice:
|
|
|
|
- Retuned DX7 max-feedback lowering for `ANLGSYN 1`. Route-index sweeps did not
|
|
materially improve the missing high-band buzz; hotter feedback did.
|
|
- Changed only feedback value `7` from `.66` to `2.2`. Feedback value `5`
|
|
remains `.19`, so the hard PRC target keeps its existing feedback amount.
|
|
- Fresh `ANLGSYN 1` with `feedback=2.2`: log-mel `.17553389`, envelope
|
|
`.16478956`, zero-crossing `.90904`, centroid `.9519112`, score `.6448498`.
|
|
This improves spectral buzz while making envelope/RMS less tidy.
|
|
- Band evidence versus the previous fixed-frequency candidate:
|
|
- 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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
73 passed.
|
|
|
|
Earlier slice:
|
|
|
|
- Fixed DX7 fixed-frequency operator lowering after `ANLGSYN 1` exposed the
|
|
missing buzzing modulation. The old lowering treated fixed mode as a fake
|
|
note ratio, so fixed carriers op1/op3 became `0.5` ratio against the graph
|
|
frequency. Dexed treats fixed mode as absolute Hz.
|
|
- Added `Dx7SysEx.FixedOperatorFrequencyHz` using Dexed's fixed-mode
|
|
log-frequency formula and changed `OperatorFrequencyRatio` to return
|
|
`fixedHz / graphFrequency` for fixed operators.
|
|
- Fresh `ANLGSYN 1` candidate now lowers fixed op3 to ratio `.009227` and op1
|
|
to `.007615` against `freq=130.8128`, instead of flattening both to `.5`.
|
|
- Added `PublicDomainDx7AnlgSyn1KeepsBuzzingModulationWhenInstalled` with
|
|
focused gates. Latest metrics: log-mel `.19427659`, envelope `.14924917`,
|
|
zero-crossing `.94662774`, centroid `.9182808`, score `.659334`.
|
|
- Verified with bundled Python/dexed-py:
|
|
`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 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
|
|
for the detune note basis. Fresh `Piano Bass` artifact emits `freq=130.8128`.
|
|
- Narrowed the community parity gate to the two voices we can currently defend:
|
|
`{ Mooger }` and fixed `Piano Bass`. `ANLGSYN 1`, `RES SYNTH1`, `DX1 LEAD B`,
|
|
and `MELLOWSOLO` remain pressure, not passing parity.
|
|
- Fixed `Piano Bass` metrics: log-mel `.16180529`, envelope `.13544041`,
|
|
zero-crossing `.905511`, score `.64267576`.
|
|
- Verified with bundled Python/dexed-py:
|
|
`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 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
|
|
`<= .3`, envelope distance `<= .16`, and score `>= .5`.
|
|
- Latest kept-voice metrics:
|
|
- `ANLGSYN 1`: log-mel `.25955316`, envelope `.12271979`, score `.5709625`
|
|
- `{ Mooger }`: log-mel `.23776375`, envelope `.07168135`, score `.66662127`
|
|
- `Piano Bass`: log-mel `.19985504`, envelope `.14781862`, score `.5205584`
|
|
- `RES SYNTH1`: log-mel `.22371109`, envelope `.109231755`, score `.54040927`
|
|
- First survey also tried `DX1 LEAD B` and `MELLOWSOLO`; they were excluded
|
|
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:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
70 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Added two split project-authored DX7 algorithm-8 rendered parity probes:
|
|
`ProjectAuthoredDx7AlgorithmEightCascadeProbeMeetsParityWhenInstalled` for
|
|
`6 -> 5 -> 3`, and
|
|
`ProjectAuthoredDx7AlgorithmEightSummedPairProbeMeetsParityWhenInstalled`
|
|
for `4 + 5 -> 3`.
|
|
- Tightened the existing combined algorithm-8 stack gate now that real metrics
|
|
are known. The project-authored probes gate log-mel at `<= .06`; 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`
|
|
- Project-authored DX7 parity tests now write listening WAVs and reports under
|
|
`artifacts/parity/dx7-project-authored/<probe>/`. These split probes are
|
|
intentionally not part of the patch library: they are useful measuring tools,
|
|
not pleasant or reusable stock.
|
|
- Verified with bundled Python/dexed-py:
|
|
`AQUASYNTH_DX7_PYTHON=<bundled python> dotnet test AquaSynth.slnx --no-restore`:
|
|
69 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Added a structured `.aqua` patch library under `patches/` with:
|
|
`examples/`, `sfxr/`, `bfxr/`, `808/`, `fm-bell/`, `wobble-bass/`,
|
|
`dx7/`, and `advanced/`.
|
|
- Added `patches/library.yaml` as the machine-readable index and
|
|
`patches/README.md` as the folder contract. The library is development
|
|
source material for stock, reference rebuilds, calibration probes, and patch
|
|
inspiration, not a shipped package surface.
|
|
- Exported the existing built-in/reference catalog into `.aqua` files and added
|
|
the calibrated `PRC SYNTH1` hard DX7 candidate at
|
|
`patches/dx7/public-domain/prc-synth1-calibrated.aqua`.
|
|
- 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 AquaSynth.slnx --no-restore`: 67 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Increased `Dx7SysEx.SummedOperatorModulationRouteIndex` from `1.6` to `6.0`.
|
|
This targets the missing harsh stacked-modulation brightness without adding a
|
|
generic drive/overdrive knob. The independent project-authored algorithm-8
|
|
summed-stack probe now uses graph gain `.218` and gates log-mel at `<= .12`
|
|
with RMS normalization.
|
|
- Latest hard `PRC SYNTH1` after stronger stack scale and graph gain `.25`:
|
|
log-mel `0.13874426`, envelope distance `0.067291506`, RMS ratio
|
|
`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 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
|
|
run after fixing the peak picker: log-mel `0.17186502` (was `0.24476814`),
|
|
envelope distance `0.13057204` (was `0.43700194`), RMS ratio `1.0000212`,
|
|
zero-crossing ratio `0.7815517`, score `0.6501268`.
|
|
- Listening note resolved: the prior applied-envelope lowering made op2 rise
|
|
late (`0.014 -> 0.613`), causing the odd end lilt. The helper now finds the
|
|
first near-peak before gate, so op2 hits early and decays
|
|
(`0.981 -> 0.843 -> 0.660`) like the traced DX7 applied gain.
|
|
- Extended staged operator envelopes with per-segment curves:
|
|
`curves=lin,exp,exp,lin`. Linear remains the default, and levels can now
|
|
express deliberate transient overshoot above `1` instead of clipping away
|
|
the DX7 evidence.
|
|
- Added `Dx7SysEx.TraceEnvelope`, a DX7 EG microscope that follows the internal
|
|
rate/level state machine closely enough to expose gain and stage over time.
|
|
- Added `Dx7SysEx.TraceInterpolatedEnvelope`, which traces the gain actually
|
|
applied by Dexed-style operator rendering: one EG sample per 64-sample block,
|
|
linearly interpolated across the block.
|
|
- Added an envelope comparison artifact test that writes
|
|
`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. 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
|
|
syntax was not allowed to survive. The durable result is the microscope and
|
|
the applied-gain trace, not a half-proven model.
|
|
|
|
Previous slice:
|
|
|
|
- Added test-only project-authored Dexed patch rendering through
|
|
`DexedPyRenderer.RenderPatchAsync`. It builds a `dexed.Patch` from explicit
|
|
operator specs and renders it without adding any shipped fixture or package
|
|
surface.
|
|
- Added an independent project-authored algorithm-8 summed-stack parity test.
|
|
This backs the topology-aware route lowering outside the public-domain PRC
|
|
patch, so `SummedOperatorModulationRouteIndex` is no longer only PRC-shaped
|
|
evidence.
|
|
- Cut the attempted DX7 EG exponential level curve again. On an isolated
|
|
envelope target, exponential EG levels plus a shorter timing scale improved
|
|
shape versus linear levels, but PRC still failed log-mel (`0.2857573` at a
|
|
tuned timing scale). The live lowering stays linear for EG levels until an
|
|
isolated envelope target passes without harming the hard target.
|
|
|
|
Previous slice:
|
|
|
|
- Moved the remaining PRC cascaded-route scalar into a topology-aware DX7 route
|
|
helper. `Dx7SysEx.OperatorRouteIndex(topology, edge)` now keeps the isolated
|
|
two-op route scale (`6.275`) for standalone direct branches and uses the
|
|
summed/cascaded scale (`1.6`) for sum edges and direct edges feeding a sum.
|
|
- The PRC probe no longer owns any private route-index function. Latest focused
|
|
run still passes with log-mel `0.24476814`, score `0.5708394`, RMS ratio
|
|
`1.0108943`.
|
|
|
|
Previous slice:
|
|
|
|
- Added the ratio-detune and algorithm-output-compensation rungs to the DX7
|
|
lowering. `Dx7SysEx.OperatorFrequencyRatio` now applies ratio-mode detune
|
|
from the Dexed/DX7 note formula, and `Dx7SysEx.OperatorOutputCompensation`
|
|
maps ROM `COM` values relative to the algorithm-32 six-output baseline.
|
|
- The PRC probe no longer has a carrier-level scale table. It still has a
|
|
fenced cascaded-route constant (`1.6`) for the `op6/op5/op4 -> op3` stack;
|
|
that is now named as the next calibration target, not promoted into core
|
|
lowering.
|
|
- Latest PRC run with detune, COM compensation, graph gain `.39`, and the
|
|
fenced cascaded-route probe: log-mel `0.24476814`, score `0.5708394`, RMS
|
|
ratio `1.0108943`, zero-crossing ratio `0.7931764`, centroid ratio
|
|
`1.1131053`.
|
|
|
|
Previous slice:
|
|
|
|
- Completed the feedback calibration rung. Isolated Dexed feedback sweeps fit a
|
|
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
|
|
feedback value `5` maps to `.19`, close to the previous accidental `.20`.
|
|
The win is ownership: feedback scaling is no longer folklore.
|
|
|
|
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 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
|
|
to the isolated `op2 -> op1` branch; applying it blindly to the cascaded
|
|
`op6/op5/op4 -> op3` stack made PRC worse, which confirms algorithm output
|
|
compensation is a separate rung.
|
|
- Latest PRC run with the carrier curve plus isolated-route calibration:
|
|
log-mel `0.2526254`, score `0.44758993`, duration ratio `0.9927914`, RMS
|
|
ratio `0.8931149`. The log-mel gate is back to `<= 0.255`.
|
|
|
|
Previous slice:
|
|
|
|
- Wrote `docs/dx7-calibration-plan.md` and referenced it from memory/spine.
|
|
The ladder is now: single-carrier amplitude, two-op modulation index,
|
|
feedback scaling, envelope level curve, algorithm output compensation, then
|
|
hard PRC replay.
|
|
- Completed the first calibration rung. A project-authored Dexed single-carrier
|
|
output-level sweep fits `2^((outputLevel - 99) / 8)` for carrier amplitude.
|
|
`Dx7SysEx.OperatorOutputAmplitude` now owns that curve, and
|
|
`ApproximateOperatorLevel` uses it instead of the old internal-level proxy.
|
|
- Added tests for the measured output-level curve. The hard PRC probe now gates
|
|
log-mel at `<= 0.26` while the next rung calibrates modulation index; latest
|
|
run after the carrier fix: log-mel `0.2562075`, score `0.40430218`.
|
|
- Calibration lesson: the old operator level helper was not a harmless
|
|
approximation. It made mid-level operators far too loud and let PRC-specific
|
|
constants compensate for a broken foundation. Next pressure is route/index
|
|
scaling for isolated two-operator FM.
|
|
|
|
Previous slice:
|
|
|
|
- Reoriented the `PRC SYNTH1` hard probe around the perceptual metric that
|
|
matched listening: log-mel distance. The probe now asserts log-mel
|
|
`<= 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 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.
|
|
- Current run with bundled Python and Faust: log-mel distance `0.25093183`,
|
|
score `0.4135618`, envelope distance `0.3595946`, duration ratio
|
|
`0.992578`, RMS ratio `1.0658501`, zero-crossing ratio `0.24567787`,
|
|
centroid ratio `1.3291793`.
|
|
- Calibration lesson: the hard target is now a spectral calibration problem.
|
|
DX7 output/index scaling still needs a real model; aggregate score can move
|
|
in the wrong direction while log-mel and the ear move in the right direction.
|
|
|
|
Previous slice:
|
|
|
|
- Tightened the hard DX7 `PRC SYNTH1` probe from a "writes WAVs" smoke test to
|
|
a modest passing parity target: threshold now requires score >= `0.60`.
|
|
- The useful cuts were not new syntax. The candidate improved by removing
|
|
patch soft clipping, setting the graph base frequency to `392`, and boosting
|
|
the `op3` carrier branch to compensate the algorithm-8 output/body balance.
|
|
Global detune and per-edge route damping both made the score worse and were
|
|
cut.
|
|
- Current best run with bundled Python and Faust writes timestamped artifacts
|
|
under `artifacts/parity/dx7-prc-synth1/<run>/`: score `0.6057491`,
|
|
log-mel distance `0.27658066`, envelope distance `0.38489524`, duration
|
|
ratio `0.98714787`, RMS ratio `0.9756519`, zero-crossing ratio `1.0458903`,
|
|
centroid ratio `1.093785`.
|
|
|
|
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-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.
|
|
- Current run with bundled Python and Faust: score `0.4581856`,
|
|
log-mel distance `0.26397973`, envelope distance `0.4109424`,
|
|
duration ratio `1.0173812`, RMS ratio `0.61393267`, zero-crossing ratio
|
|
`1.1713804`, centroid ratio `1.0978767`.
|
|
- Calibration lesson: shortening the naive DX7 EG timing and lowering the graph
|
|
base frequency made the candidate much closer. The remaining mismatch is
|
|
still gain/timbre calibration, not missing `env=rl` syntax.
|
|
|
|
Previous slice:
|
|
|
|
- Added staged operator envelopes for operator graphs:
|
|
`env=rl rates=.004,.12,.2,.4 levels=1,.7,.25,0`. This gives the DSL a
|
|
readable four-stage rate/level contour without infecting the general ADSR
|
|
voice envelope model.
|
|
- Added `RateLevelEnvelope` to the operator model, parser support for readable
|
|
and compact `env=rl` syntax, Faust `rl4_env(...)` rendering, and a DX7 helper
|
|
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 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
|
|
calibration remains the next pressure.
|
|
|
|
Previous slice:
|
|
|
|
- Added `Dx7OperatorLevelApproximation`, which distills DX7 operator output
|
|
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 AquaSynth.slnx --no-restore`: 50 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Tried the harder `analog1.syx` voice 17, `PRC SYNTH1` (algorithm 8). The
|
|
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 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 AquaSynth sine patch.
|
|
- Added `PublicDomainDx7McMm53MeetsFirstRenderedParityThresholdWhenInstalled`,
|
|
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.
|
|
- This is behavioral parity for a simple sine-like rendered voice, not exact
|
|
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 AquaSynth.slnx --no-restore`: 48 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Added a public-domain DX7 SysEx fixture from Musical Artifacts artifact 152:
|
|
`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
|
|
`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 `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 AquaSynth.slnx --no-restore`: 47 passed.
|
|
|
|
Previous slice:
|
|
|
|
- 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 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 AquaSynth.slnx --no-restore`: 44 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Added `Dx7EnvelopeApproximation`, which lowers a DX7 four-rate/four-level EG
|
|
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 AquaSynth.slnx --no-restore`: 43 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Added field-site parameter binding for readable operator graph authoring:
|
|
graph gain/note fields, operator ratio/level/feedback/envelope fields, and
|
|
route index fields now accept `@/param`.
|
|
- Operator graph Faust emission now substitutes bound parameter expressions at
|
|
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 AquaSynth.slnx --no-restore`: 42 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Added a patch-level `Playback` contract for `OneShot`, `Mono`, and `Poly`
|
|
playback with Faust MIDI polyphony settings.
|
|
- `instrument midi=true polyphony=8` now lowers to Faust's standard
|
|
`[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 AquaSynth.slnx --no-restore`: 41 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Split note timing from envelope shape. `Note` now owns frequency, one-shot
|
|
gate duration, and host/MIDI source; `Envelope` now owns ADSR shape:
|
|
attack, decay, sustain level, and release.
|
|
- SFXR sustain duration now maps to `Note.GateSeconds`; SFXR punch maps to a
|
|
lower ADSR sustain level plus voice gain compensation during import/legacy
|
|
parsing.
|
|
- 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 AquaSynth.slnx --no-restore`: 40 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Replaced the DX7 algorithm-8 authoring surface with readable operator graph
|
|
syntax: `operator`, `route`, and `carrier` declarations.
|
|
- Added `env=ad:attack:decay` and `env=adsr:attack:decay:sustain:release`
|
|
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 AquaSynth.slnx --no-restore`: 36 passed.
|
|
|
|
Previous slice:
|
|
|
|
- Added a first-class `OperatorGraph` model with operators, modulation edges,
|
|
carriers, graph gain, and operator feedback.
|
|
- Added `opgraph` patch-script syntax. Example:
|
|
`opgraph name=core freq=330 carriers=1,3 ops=6:4:.9,5:3:.8,4:2:.7:.18,3:2:.6,2:1:.75,1:1:.82 edges=6>5:1.1,5>3:.9,4>3:.75,2>1:.85`
|
|
- Faust emission now mixes operator graphs alongside normal voices.
|
|
- 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 AquaSynth.slnx --no-restore`: 35 passed.
|
|
|
|
Earlier rebuild slice:
|
|
|
|
- Added `ReferenceRebuild` and `ReferenceRebuildCatalog` for explicit
|
|
reference-target rebuild attempts.
|
|
- Added two DX7-style AquaSynth DSL rebuilds:
|
|
- `dx7/algo32-additive-organ`: algorithm 32's six-carrier additive shape,
|
|
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 AquaSynth.slnx --no-restore`: 34 passed.
|
|
|
|
Earlier topology slice:
|
|
|
|
- Added DX7 algorithm topology metadata for algorithms 1-32 using Ken
|
|
Shirriff's reverse-engineered OPS algorithm ROM table.
|
|
- `Dx7SysEx.AlgorithmTopology` now exposes carrier operators, modulation
|
|
edges, feedback-register writers, direct self-feedback operators,
|
|
delayed-feedback targets, and raw ROM steps.
|
|
- `Dx7Voice.Features()` now emits `carrier_operators`,
|
|
`modulation_edge_count`, `feedback_sources`, and
|
|
`self_feedback_operators`.
|
|
- Representative tests cover algorithm 8, algorithm 16, and algorithm 32.
|
|
- Verified with `dotnet test AquaSynth.slnx --no-restore`: 31 passed.
|
|
|
|
Earlier slice:
|
|
|
|
- Added `Dx7SysEx` records and parsing for DX7 voice edit buffers, packed
|
|
128-byte voices, and packed 32-voice SysEx banks.
|
|
- Extracted six operators, envelopes, algorithm, feedback, oscillator sync,
|
|
pitch envelope, LFO, transpose, and voice name into neutral DX7 records.
|
|
- 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 AquaSynth.slnx --no-restore`: 28 passed.
|
|
|
|
Parameter slice:
|
|
|
|
- Added explicit `PatchParameter` contracts: stable path, label, default, min,
|
|
max, step, unit, automation-rate notes, and notes.
|
|
- Added neutral `ReferencePatch`, `ReferenceSource`, and `ReferenceFeature`
|
|
contracts with provenance/license/hash-ready fields.
|
|
- Added `param` script declarations and duplicate parameter path validation.
|
|
- Updated Faust emission so declared parameters produce smoothed `hslider`
|
|
controls.
|
|
- Implemented Option B parameter binding: numeric fields may reference declared
|
|
parameters with `@/path`, for example `lpf=@/macro/brightness`. The parser
|
|
records an exact field binding such as `/voices/0/filter/lpf` and keeps the
|
|
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 AquaSynth.slnx --no-restore`: 23 passed.
|
|
|
|
Next likely slice:
|
|
|
|
- Refine operator graphs with parameter bindings inside graph fields and decide
|
|
how host MIDI note age/retrigger semantics should be represented beyond the
|
|
current frequency/gate controls.
|
|
- Keep tests focused on structure first, then add rendered audio comparison once
|
|
the render path is explicit.
|