From abef14dbb693424cda1d4a67dafda898f769b610 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 14 May 2026 14:06:26 +0100 Subject: [PATCH] Calibrate DX7 stacked modulation brightness --- docs/dx7-calibration-plan.md | 13 +++++++----- docs/reference-synth-roadmap.md | 6 +++--- src/AquariumSynth.Dsl/Dx7SysEx.cs | 2 +- state/evidence.jsonl | 1 + state/memory.json | 11 +++++----- state/scratch.md | 9 ++++++++ state/spine.yaml | 21 +++++++++++++------ .../Dx7ReferenceParityTests.cs | 9 ++++---- .../AquariumSynth.Dsl.Tests/Dx7SysExTests.cs | 4 ++-- 9 files changed, 50 insertions(+), 26 deletions(-) diff --git a/docs/dx7-calibration-plan.md b/docs/dx7-calibration-plan.md index e9f9dcb..9a18331 100644 --- a/docs/dx7-calibration-plan.md +++ b/docs/dx7-calibration-plan.md @@ -88,17 +88,20 @@ math is still fake in the places that matter perceptually. - Replace hard `PRC SYNTH1` route probe logic with a topology-aware DX7 route rule: isolated direct branches keep the two-op scale, while summed/cascaded branches use the summed-modulator scale. - - A project-authored algorithm-8 summed-stack parity test now backs the - topology-aware summed-modulator scale. + - The summed/cascaded scale is now `2.4`. A project-authored algorithm-8 + summed-stack parity test backs the topology-aware scale outside PRC, and + the hard PRC replay improves to log-mel `0.16794802`, envelope distance + `0.09510617`, centroid ratio `1.1254044`, and score `0.68491656` after + graph gain normalization. 8. **Hard target replay** - Re-run `PRC SYNTH1`. - Keep the log-mel gate and listening artifacts. - Raise thresholds only when the calibrated lowering improves for the right reason. - - Current curved-envelope replay gates log-mel at `<= 0.18`, envelope - distance at `<= 0.14`, zero-crossing at `>= 0.75`, RMS ratio near unity, - and score at `>= 0.64`. + - Current curved-envelope plus stronger-stack replay gates log-mel at + `<= 0.18`, envelope distance at `<= 0.14`, zero-crossing at `>= 0.75`, + RMS ratio near unity, and score at `>= 0.64`. ## Rejected Path diff --git a/docs/reference-synth-roadmap.md b/docs/reference-synth-roadmap.md index f9eb886..71adb7f 100644 --- a/docs/reference-synth-roadmap.md +++ b/docs/reference-synth-roadmap.md @@ -81,9 +81,9 @@ First deliverable: operator routing rather than a sine-like behavioral match. Staged operator envelopes now exist as readable `env=rl rates=... levels=...` declarations with per-segment curves. Direct DX7 rate/level lowering on `PRC SYNTH1` - forced EG timing/gain calibration; the current applied-envelope lowering - improves log-mel and envelope distance but exposes worse zero-crossing/phase - behavior. The hard PRC probe writes ignored listening artifacts under + forced EG timing/gain calibration; the current applied-envelope lowering and + stronger summed/cascaded route scale give PRC the same character with much + closer stacked-modulation brightness. The hard PRC probe writes ignored listening artifacts under `artifacts/parity/dx7-prc-synth1` so metrics and ears can steer the next calibration pass together. Listening showed aggregate score was a weak judge for this target, so the hard PRC probe gates primarily on log-mel distance. diff --git a/src/AquariumSynth.Dsl/Dx7SysEx.cs b/src/AquariumSynth.Dsl/Dx7SysEx.cs index c2e2285..ac8433d 100644 --- a/src/AquariumSynth.Dsl/Dx7SysEx.cs +++ b/src/AquariumSynth.Dsl/Dx7SysEx.cs @@ -172,7 +172,7 @@ public static class Dx7SysEx public const int PackedBankVoiceCount = 32; public const int PackedBankDataLength = PackedVoiceLength * PackedBankVoiceCount; public const float OperatorModulationRouteIndex = 6.275f; - public const float SummedOperatorModulationRouteIndex = 1.6f; + public const float SummedOperatorModulationRouteIndex = 2.4f; public static Dx7Voice ParseVoice(ReadOnlySpan bytes) { diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 91f302a..18fb389 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -41,3 +41,4 @@ {"kind":"curved_rate_level_envelope","lesson":"Aquarium staged operator envelopes now support per-segment curves and deliberate levels above unity. Keep this authority in `RateLevelEnvelope`: it protects the invariant that multi-target operator contours can model exponential synth envelopes without inventing one-off DX7 syntax.","source":"Curved rate/level envelope implementation on 2026-05-14"} {"kind":"dx7_applied_envelope_lowering","lesson":"`Dx7SysEx.ApproximateAppliedRateLevelEnvelope` improves the hard PRC target by tracing Dexed-style block-interpolated EG gain, normalizing it against Aquarium operator output level, and emitting curved `env=rl`. Latest PRC metrics: log-mel 0.21931955, envelope distance 0.10271241, RMS ratio 1.0003664, score 0.5026183. Zero-crossing worsened to 0.28642866, so contour parity exposed a remaining oscillator/phase mismatch rather than solving DX7 parity outright.","source":"DX7 applied-envelope PRC probe on 2026-05-14"} {"kind":"dx7_applied_envelope_peak_picker","lesson":"Listening caught a real lowering bug: the first applied-envelope approximation treated the first 64-sample block as the attack peak, which made slower rising op2 in PRC swell late (`0.014 -> 0.613`) instead of hitting early and decaying. Picking the first near-peak before gate gives op2 `0.981 -> 0.843 -> 0.660` and improves PRC to log-mel 0.17186502, RMS 1.0000212, zero-crossing 0.7815517, score 0.6501268.","source":"PRC SYNTH1 listening-guided envelope fix on 2026-05-14"} +{"kind":"dx7_summed_route_scale","lesson":"Listening identified missing harsh stacked-modulation brightness. Raising `Dx7SysEx.SummedOperatorModulationRouteIndex` from 1.6 to 2.4 improves PRC to log-mel 0.16794802, envelope distance 0.09510617, centroid ratio 1.1254044, RMS ratio 1.00095, score 0.68491656, and also tightens the project-authored algorithm-8 summed-stack log-mel when graph gain is normalized. Keep isolated direct branches at 6.275; the stronger 2.4 scale belongs only to summed/cascaded DX7 branches.","source":"DX7 stacked modulation brightness calibration on 2026-05-14"} diff --git a/state/memory.json b/state/memory.json index 9fff8b1..7f04ca5 100644 --- a/state/memory.json +++ b/state/memory.json @@ -25,6 +25,7 @@ "DX7 effective operator-level approximation", "DX7 measured carrier output amplitude curve", "DX7 isolated modulation-index route scale", + "DX7 summed/cascaded modulation route scale", "DX7 calibrated feedback amount table", "DX7 ratio detune lowering", "DX7 ROM COM output compensation", @@ -72,9 +73,9 @@ "rule": "Add DSL abstractions only when reference rebuilds prove the missing invariant. Do not consume references that cannot be backed by parity tests; parsing alone is inventory, not evidence of expressivity." }, "dx7_reference_path": { - "status": "DX7 SysEx parser extracts voice edit buffers, packed voices, packed 32-voice banks, six operators, envelopes, algorithm, feedback, oscillator sync, LFO, transpose, voice name, checksum validation, ReferencePatch features, algorithm topology from the reverse-engineered OPS algorithm ROM table, envelope-to-ADSR approximations, measured carrier output amplitude, isolated two-op modulation route scale, calibrated feedback amount table, ratio detune lowering, ROM COM output compensation, topology-aware route lowering, staged operator envelopes with per-segment curves, raw and block-interpolated DX7 envelope tracing, applied-envelope curved lowering, two Aquarium DSL rebuild attempts, first-class OperatorGraph support, public-domain SysEx fixture coverage, dexed-py reference rendering, project-authored Dexed patch rendering, Aquarium candidate rendering through Faust-generated C#, one thresholded rendered parity test for analog1.syx voice 13, a hard PRC SYNTH1 parity probe with listening WAV artifacts and log-mel <= 0.225, project-authored algorithm-8 summed-stack parity, and renderable delayed operator feedback.", - "lesson": "Algorithm 32's additive six-carrier shape maps cleanly to six Aquarium voices. Algorithm 8 justified OperatorGraph: operators, modulation edges, carriers, and feedback belong to a graph authority, not Voice fields. The first rendered parity rung is behavioral and sine-like, not exact DX7 operator execution. A harder algorithm-8 target exposed that feedback must be delayed recursion in Faust, not a direct cyclic self-reference. Static DX7 output-level approximation helped import/rebuild math but was mathematically wrong for carrier amplitude. A single-carrier Dexed sweep shows carrier amplitude follows 2^((outputLevel - 99) / 8). Isolated two-op DX7 FM maps full-level modulation to Aquarium route index 6.275, while summed/cascaded algorithm-8 branches need a lower topology-aware route scale. Feedback now lowers through a fitted table instead of PRC-local constants. Ratio-mode detune belongs in operator frequency lowering. ROM COM values carry output-count compensation; because the amplitude curve was fitted against algorithm 32, DX7 carriers scale by 6/(COM+1). Project-authored Dexed patch rendering now gives lawful calibration targets. Reusing the operator output curve for EG levels was tested and cut because it failed hard-target log-mel. Direct envelope tracing shows current linear `env=rl` cannot express DX7 EG spike/drop behavior. Dexed-style audio samples EG once per 64-sample block and interpolates applied gain, so raw EG parity alone is not audio parity. Staged operator envelopes now support linear/exponential segments and supranormal levels. `ApproximateAppliedRateLevelEnvelope` uses that surface and now picks the first near-peak before gate so slower rising modulators hit early instead of swelling late. PRC log-mel is down to 0.17186502 with RMS at unity, but centroid/timbre mismatch remains.", - "next": "Use project-authored envelope and oscillator/phase sweeps to separate remaining centroid/timbre mismatch before broad DX7 import." + "status": "DX7 SysEx parser extracts voice edit buffers, packed voices, packed 32-voice banks, six operators, envelopes, algorithm, feedback, oscillator sync, LFO, transpose, voice name, checksum validation, ReferencePatch features, algorithm topology from the reverse-engineered OPS algorithm ROM table, envelope-to-ADSR approximations, measured carrier output amplitude, isolated two-op modulation route scale, summed/cascaded route scale, calibrated feedback amount table, ratio detune lowering, ROM COM output compensation, topology-aware route lowering, staged operator envelopes with per-segment curves, raw and block-interpolated DX7 envelope tracing, applied-envelope curved lowering, two Aquarium DSL rebuild attempts, first-class OperatorGraph support, public-domain SysEx fixture coverage, dexed-py reference rendering, project-authored Dexed patch rendering, Aquarium candidate rendering through Faust-generated C#, one thresholded rendered parity test for analog1.syx voice 13, a hard PRC SYNTH1 parity probe with listening WAV artifacts and log-mel <= 0.18, project-authored algorithm-8 summed-stack parity, and renderable delayed operator feedback.", + "lesson": "Algorithm 32's additive six-carrier shape maps cleanly to six Aquarium voices. Algorithm 8 justified OperatorGraph: operators, modulation edges, carriers, and feedback belong to a graph authority, not Voice fields. The first rendered parity rung is behavioral and sine-like, not exact DX7 operator execution. A harder algorithm-8 target exposed that feedback must be delayed recursion in Faust, not a direct cyclic self-reference. Static DX7 output-level approximation helped import/rebuild math but was mathematically wrong for carrier amplitude. A single-carrier Dexed sweep shows carrier amplitude follows 2^((outputLevel - 99) / 8). Isolated two-op DX7 FM maps full-level modulation to Aquarium route index 6.275; summed/cascaded algorithm-8 branches now use 2.4 after PRC listening and a project-authored stack probe showed 1.6 was under-bright. Feedback lowers through a fitted table instead of PRC-local constants. Ratio-mode detune belongs in operator frequency lowering. ROM COM values carry output-count compensation; because the amplitude curve was fitted against algorithm 32, DX7 carriers scale by 6/(COM+1). Project-authored Dexed patch rendering now gives lawful calibration targets. Reusing the operator output curve for EG levels was tested and cut because it failed hard-target log-mel. Direct envelope tracing shows current linear `env=rl` cannot express DX7 EG spike/drop behavior. Dexed-style audio samples EG once per 64-sample block and interpolates applied gain, so raw EG parity alone is not audio parity. Staged operator envelopes now support linear/exponential segments and supranormal levels. `ApproximateAppliedRateLevelEnvelope` uses that surface and now picks the first near-peak before gate so slower rising modulators hit early instead of swelling late. PRC log-mel is down to 0.16794802 with RMS at unity; remaining mismatch is residual timbre/centroid, not broad envelope shape.", + "next": "Use broader project-authored DX7 sweeps to validate summed/cascaded route scale 2.4 and isolate remaining timbre/centroid mismatch before broad DX7 import." }, "note_and_envelope_model": { "rule": "Notes own frequency, gate duration, and host/MIDI source. Envelopes own ADSR level shape.", @@ -89,7 +90,7 @@ }, "last_verified": { "command": "AQUARIUM_DX7_PYTHON= dotnet test AquariumSynthCSharp.slnx --no-restore", - "result": "66 passed; PRC log-mel 0.17186502, envelope distance 0.13057204, zero-crossing 0.7815517, score 0.6501268 after applied-envelope peak-picker fix", - "commit": "pending DX7 applied-envelope peak fix" + "result": "66 passed; PRC log-mel 0.16794802, envelope distance 0.09510617, centroid ratio 1.1254044, score 0.68491656 after summed route scale 2.4", + "commit": "pending DX7 summed route scale" } } diff --git a/state/scratch.md b/state/scratch.md index b452aad..34169c7 100644 --- a/state/scratch.md +++ b/state/scratch.md @@ -15,6 +15,15 @@ Doctrine update: Completed this slice: +- Increased `Dx7SysEx.SummedOperatorModulationRouteIndex` from `1.6` to `2.4`. + 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 `.309`: + log-mel `0.16794802`, envelope distance `0.09510617`, RMS ratio `1.00095`, + zero-crossing ratio `0.79647374`, centroid ratio `1.1254044`, score + `0.68491656`. - Added `Dx7SysEx.ApproximateAppliedRateLevelEnvelope`, which traces the Dexed-style block-interpolated applied EG gain and lowers it into Aquarium's curved staged envelope surface, normalized so operator output level remains a diff --git a/state/spine.yaml b/state/spine.yaml index 146c638..88c664e 100644 --- a/state/spine.yaml +++ b/state/spine.yaml @@ -49,8 +49,8 @@ live_invariants: - DX7 feedback lowers through `Dx7SysEx.OperatorFeedbackAmount`; do not restore PRC-local `feedback * constant` folklore. - DX7 ratio-mode detune lowers through `Dx7SysEx.OperatorFrequencyRatio`; do not silently drop detune from operator graph scripts. - DX7 algorithm output compensation lowers through ROM `COM` values relative to the algorithm-32 six-output baseline; do not restore PRC-local carrier scale tables. - - DX7 route lowering is topology-aware: isolated direct branches use `OperatorModulationRouteIndex`; summed edges and direct edges feeding summed modulation use `SummedOperatorModulationRouteIndex`. - - `SummedOperatorModulationRouteIndex` is calibrated from the hard PRC target. It is no longer PRC-local, but project-authored algorithm-8 stack sweeps should tighten it before broad DX7 import. + - DX7 route lowering is topology-aware: isolated direct branches use `OperatorModulationRouteIndex` (`6.275`); summed edges and direct edges feeding summed modulation use `SummedOperatorModulationRouteIndex` (`2.4`). + - `SummedOperatorModulationRouteIndex` is backed by the hard PRC target and a project-authored algorithm-8 stack probe; do not restore the older under-bright `1.6` scale. - DX7 calibration must proceed through isolated rungs in `docs/dx7-calibration-plan.md`; do not keep tuning hard PRC-specific constants as a substitute for a level/index model. - Staged operator envelopes own multi-target operator level contours through `env=rl rates=... levels=...`; do not force DX7-style contours back into the general ADSR voice envelope model. - Staged operator envelopes may use per-segment curves through `curves=lin,exp,...`; this belongs to the rate/level contour authority, not to the standard ADSR envelope. @@ -116,10 +116,10 @@ next_agent_start_here: two-op modulation index, feedback, ratio detune, ROM COM output compensation, and topology-aware route lowering are now fitted or mapped; a project-authored algorithm-8 stack test now backs the summed route scale - outside PRC. Curved applied-envelope lowering now improves the hard PRC - log-mel and envelope metrics, but zero-crossing/phase behavior is worse. - Next pressure is oscillator/phase behavior or a project-authored envelope - sweep that can separate contour wins from remaining centroid/timbre damage. + outside PRC. Curved applied-envelope lowering plus stronger summed/cascaded + route scale now gives PRC the same character with closer harsh stacked + modulation. Next pressure is residual centroid/timbre mismatch and broader + project-authored DX7 sweeps before broad import. must_read: - AGENTS.md - docs/audio-doctrine.md @@ -132,6 +132,15 @@ next_agent_start_here: - "dotnet test AquariumSynthCSharp.slnx --no-restore" completed_slices: + - date: 2026-05-14 + summary: > + Increased `Dx7SysEx.SummedOperatorModulationRouteIndex` from 1.6 to 2.4 + after listening identified missing harsh stacked-modulation brightness. + The stronger scale improves PRC log-mel to 0.16794802, envelope distance + to 0.09510617, centroid ratio to 1.1254044, and score to 0.68491656 with + RMS ratio 1.00095. The independent project-authored algorithm-8 + summed-stack test now uses graph gain 0.218 and tighter log-mel/RMS gates. + verification: "AQUARIUM_DX7_PYTHON= dotnet test AquariumSynthCSharp.slnx --no-restore --filter \"PublicDomainDx7PrcSynth1WritesListeningWavsWhenInstalled|ProjectAuthoredDx7AlgorithmEightSummedStackMeetsParityWhenInstalled\": passed" - date: 2026-05-14 summary: > Added `Dx7SysEx.ApproximateAppliedRateLevelEnvelope` and used it in the diff --git a/tests/AquariumSynth.Dsl.Tests/Dx7ReferenceParityTests.cs b/tests/AquariumSynth.Dsl.Tests/Dx7ReferenceParityTests.cs index 4d939c4..0f59879 100644 --- a/tests/AquariumSynth.Dsl.Tests/Dx7ReferenceParityTests.cs +++ b/tests/AquariumSynth.Dsl.Tests/Dx7ReferenceParityTests.cs @@ -101,7 +101,7 @@ public sealed class Dx7ReferenceParityTests var script = Dx7VoiceProbeScript( voice, graphName: "dx7_prc_synth1_probe", - graphGain: 0.283f, + graphGain: 0.309f, envelopeScale: 0.62f, gateSeconds: null, useAppliedEnvelope: true); @@ -165,7 +165,7 @@ public sealed class Dx7ReferenceParityTests var script = Dx7VoiceProbeScript( VoiceFromSpec(spec), graphName: "dx7_algorithm8_summed_stack_probe", - graphGain: 0.39f, + graphGain: 0.218f, envelopeScale: 0.62f, gateSeconds: 0.45f); var candidateSource = FaustEmitter.EmitScript(script); @@ -181,8 +181,9 @@ public sealed class Dx7ReferenceParityTests var comparison = new AudioAnalyzer(new AudioAnalysisConfig(SampleRate: reference.SampleRate)) .Compare(reference.Samples, candidate.Samples); - Assert.True(comparison.LogMelDistance <= 0.31f, ParityReport(comparison)); - Assert.True(comparison.Score >= 0.35f, ParityReport(comparison)); + Assert.True(comparison.LogMelDistance <= 0.12f, ParityReport(comparison)); + Assert.InRange(comparison.RmsRatio, 0.90f, 1.10f); + Assert.True(comparison.Score >= 0.45f, ParityReport(comparison)); } [Fact] diff --git a/tests/AquariumSynth.Dsl.Tests/Dx7SysExTests.cs b/tests/AquariumSynth.Dsl.Tests/Dx7SysExTests.cs index 6b179f2..8ccc723 100644 --- a/tests/AquariumSynth.Dsl.Tests/Dx7SysExTests.cs +++ b/tests/AquariumSynth.Dsl.Tests/Dx7SysExTests.cs @@ -246,8 +246,8 @@ public sealed class Dx7SysExTests edge.TargetOperator == 5); Assert.Equal(Dx7SysEx.OperatorModulationRouteIndex, Dx7SysEx.OperatorRouteIndex(topology, direct)); - Assert.Equal(1.6f, Dx7SysEx.OperatorRouteIndex(topology, summed)); - Assert.Equal(1.6f, Dx7SysEx.OperatorRouteIndex(topology, cascaded)); + Assert.Equal(2.4f, Dx7SysEx.OperatorRouteIndex(topology, summed)); + Assert.Equal(2.4f, Dx7SysEx.OperatorRouteIndex(topology, cascaded)); } [Fact]