Skip to content

Mark I OPS recalibration — plan + divisor A/B harness (markI-ops-dac findings 1–3)#15

Merged
hakaru merged 2 commits into
mainfrom
feat/mark-i-recalibration
Jun 30, 2026
Merged

Mark I OPS recalibration — plan + divisor A/B harness (markI-ops-dac findings 1–3)#15
hakaru merged 2 commits into
mainfrom
feat/mark-i-recalibration

Conversation

@hakaru

@hakaru hakaru commented Jun 30, 2026

Copy link
Copy Markdown
Owner

What

Investigation artifacts for the Mark I OPS forward-modulation ("darkness") recalibration raised by the DX7 fidelity audit (markI-ops-dac findings 1–3). No shipping code is changed yet — this is the plan + A/B evidence so the forward-mod divisor can be chosen by ear, then implemented (TDD) on this branch.

  • docs/mark-i-recalibration-plan.md — frames the coordinated fix (finding 1 <<13 output scale, finding 2 mod divisor, finding 3 feedback), the KLS / darkerSustain interaction, the A/B data, and a recommendation + implementation plan.
  • Tests/M2DXCoreTests/MarkIDivisorABTests.swift — renders BASS 1 (alg 15) + E.PIANO 1 (alg 4) through the full SynthEngine at ÷4/÷5/÷6/÷8 + Modern, writes peak-normalized WAVs (compare timbre, not loudness) + prints the sustain spectral centroid. Run: swift test --filter renderMarkIDivisorAB/tmp/m2dx-marki-ab/.

A/B data (note 60, −3 dBFS normalized, sustain centroid — lower = darker)

patch Modern ÷4 ÷5 (app default) ÷6 ÷8 (bit-parity)
BASS 1 (alg 15) 180 Hz 160 152 148 142
E.PIANO 1 (alg 4) 280 Hz 280 280 280 280
  • ÷4 still leaves BASS 1 darker than Modern (160 vs 180) — the OPS log-sin/exp is inherently a touch darker, so the audit's "÷4 = Modern mod index" is approximate in practice.
  • The ÷4…÷8 spread is a compressed ~12% on BASS 1; on E.PIANO 1 the divisor effect is in the attack (modulators decay before the sustain window) — judge by ear over the whole note.
  • findings 1+2+3 are entangled (output scale = loudness, divisor = brightness, feedback): a divisor-only change leaves the 12 dB engine-switch step, so do the coordinated fix.

Status

Decision pending (by ear). Once the divisor + scope are chosen, the implementation plan in the doc lands here (TDD: markIModScaleQ12 default, optional <<13→<<11 + feedback, darkerSustain re-baseline, dx7refmki parity kept green), then a new M2DX-Core release + app pin bump + on-device A/B. Clean-room preserved (no GPL EngineMkI.cpp consulted).

Follows the keyboard-scaling fix in #14 (v1.16.0).

🤖 Generated with Claude Code

Hakaru Hirose and others added 2 commits June 30, 2026 08:56
… findings 1-3)

Investigation artifacts for the Mark I OPS forward-mod darkness recalibration (decision pending, by ear). Adds MarkIDivisorABTests (renders BASS 1 + E.PIANO 1 through the full SynthEngine at div4/5/6/8 + Modern, peak-normalized WAVs + sustain centroid) and docs/mark-i-recalibration-plan.md framing the coordinated fix (findings 1 output-scale, 2 divisor, 3 feedback), the KLS/darkerSustain interaction, A/B data, and a recommendation. No shipping code changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…< Modern; env-gate A/B

An A/B by ear found ÷4-8 nearly indistinguishable (data: BASS 1 ~12% centroid spread, E.PIANO 1 divisor-insensitive), so the shipping Mark I forward-mod divisor stays ÷5 — markI-ops-dac finding 2 is closed as a low-impact calibration. Findings 1 (12 dB output) & 3 (feedback) remain deferred.

- MarkICalibrationTests.darkerSustain: drop the stale ÷0.6 'markedly darker' threshold (an artifact of the pre-KLS-fix over-bright Modern); assert only Mark I < Modern.
- MarkIDivisorABTests: env-gate behind M2DX_MARKI_AB so it no-ops in a normal swift test (it mutates the process-global markIModScaleQ12 and was racing the Mark I parity suite under the parallel runner); restore the ÷8 default on exit.
- docs/mark-i-recalibration-plan.md: record the decision. 241 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hakaru
hakaru marked this pull request as ready for review June 30, 2026 00:13
@hakaru
hakaru merged commit 48e3818 into main Jun 30, 2026
1 check passed
@hakaru
hakaru deleted the feat/mark-i-recalibration branch June 30, 2026 00:15

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 10abda3644

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)

let bass1 = DX7SysExParser.parse(data: bankFromVoice(rom1aBass1Packed), bankName: "ROM1A")!.presets[0]
let epiano1 = DX7FactoryPresets.all[11] // ROM1A E.PIANO 1 (alg 4, feedback)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the actual E.PIANO 1 preset

In this A/B harness, DX7FactoryPresets.all is built as [initVoice] + customPresets, and keysBatch starts with E.PIANO 1, so index 11 selects TINE BELL rather than E.PIANO 1. That means the generated WAVs and the calibration data documented as E.PIANO 1 are for the wrong patch, which can lead the divisor decision to be based on an unrelated bell preset; select the named preset or the correct index instead.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant