Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ All of that vanishes into a working parser. Six months on, the code says *what*
| D20 | The partitions eight short AKAI images displaced, found by an anchored search ([ADR-0028](adr/0028-a-displaced-partition-is-anchored-quantised-and-floored.md)) | done |
| D21 | An E-mu record is closed by the channel it declares, and found by either one ([ADR-0029](adr/0029-a-record-is-closed-by-the-channel-it-declares.md)) | done |
| D23 | The E-mu whole-extent "no loop" is refused at both ends, not only at frame 0 ([ADR-0030](adr/0030-the-whole-extent-no-loop-is-refused-at-both-ends.md)) | done |
| D24 | An EIII/ESI bank binds the near-named header its placement predicts, recovering five banks a mistyped header name hid ([ADR-0031](adr/0031-a-bank-binds-the-near-named-header-its-placement-predicts.md)) | done |

Across the local collection, by listing: 74 of 82 images claimed, 3 096 volumes, 132 802 files, 98 061 of them samples. The AKAI discs are 44 of those images and 86 177 of those files, read across 314 partitions — 275 where the disk's table puts them and 39 displaced by blocks the rip lost. Before D15 they were 14 670 files, because only the partition at the origin was read.

Expand All @@ -77,6 +78,7 @@ Across the local collection, by listing: 74 of 82 images claimed, 3 096 volumes,
- **Ensoniq and Kurzweil backends.** The archives are full of these discs and the containers already open them; each needs a module in `fs/` and nothing else ([ADR-0003](adr/0003-brand-neutral-pluggable-backends.md)).
- **No root key for E-mu.** It is not in the sample record: no byte of the 92 tracks the note in the sample's own name above chance, over 1 741 named records of `esi32-gm` and 917 of `eiiix-1`. The E3 keeps it in the preset, and presets are not read — see the `E4P1` entry below. E-mu WAVs carry their loop with the RIFF neutral root key of 60 ([ADR-0025](adr/0025-the-loop-is-decoded-the-root-key-is-not.md)).
- **`E4P1` presets are not read.** The three E-IV discs carry 916, 901 and 284 of them. On `eiv-studio` 100 of 230 banks have no sample directory and are listed with a note; preset-only banks are the likely explanation, and it is not established.
- **A duplicate directory name double-lists on EIII/ESI.** `located` is keyed by bank name, so where a directory lists one name twice both entries resolve to a single header and report the same records — `Harpsichord X` on `Vol. 10 – Elements of Sound 1MB`, `HvyGtr Maj.Open` on `Vol. 17 – Heavy Guitars`. One arm of the `heavy` pair points at a real but **blank-named** 6.3 MB header, which is a second mystery. It is a different mechanism from D24's mistyped-header recovery — it reads the same audio twice rather than nothing — and needs per-entry header resolution; the D24 pins for those two discs include the double-count until it is fixed ([#47](https://github.com/bmxcode/samplerdisc/issues/47)).
- **The `.mds` track table is unread.** One real pair now reads end to end, but geometry is sniffed from the `.mdf` rather than taken from the descriptor, so a multi-track or offset image would be read from byte 0. What the one specimen's descriptor holds is written down in [formats/mdx.md](formats/mdx.md) without being relied on.
- **CUES chunks in NRG.** Only `CUEX` is parsed; `CUES` encodes position as MSF and no disc using it was available to check the layout against.
- **EXS24 and HALion instruments are kept, not read.** `--keep-originals` writes the `.exs` and `.fxp` files out byte for byte, because they hold the key ranges and envelopes a WAV cannot. Nothing parses them, and nothing should: turning them into a playable instrument is [ConvertWithMoss](https://github.com/git-moss/ConvertWithMoss)'s job ([ADR-0011](adr/0011-the-deliverable-is-daw-ready-wav.md)).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# ADR-0031 · A bank binds the near-named header its placement predicts

**Status:** accepted · 2026-08-24

## Context

[ADR-0015](0015-locate-banks-by-signature.md) settled that an EIII/ESI bank is *found* by its own `EMULATOR` header, which repeats the directory's bank name, and [ADR-0021](0021-a-bank-owns-the-run-its-header-declares.md) settled how a name written twice is disambiguated: fit `header address == unit × start + bias` from the headers already located by signature, and use it *only* to say which of two same-named headers a directory entry meant. `_bank_offsets` implements both by keying `located` on the exact bank name.

[Issue #43](https://github.com/bmxcode/samplerdisc/issues/43) is the case that keying on the exact name cannot reach. Five named banks across three discs claimed a volume, found no header for their directory name and read nothing — `Electric Grand X` on `Vol. 10 – Elements of Sound 1MB`, `PERCUSSION#1 X` on `Vol. 16 – Ditto Drums`, and `HvyGtr FX5 X`, `Misc Gtr FX 2MbX`, `HvGtrFdBkTxtr2Mb` on `Vol. 17 – Heavy Guitars`. Each carried the ADR-0012 note `no bank header found for this bank; listed only`, so the invariant held — but unlike the 28 `E3 Main Code`/`E3X Main Code` operating-system slots that share it, these are named like ordinary sample banks.

Measured against the discs, every one has a real header sitting at *exactly* the address ADR-0021's placement fit predicts, carrying real audio named after the bank — but its own 16-byte name field at `+16` is a corrupted copy of the directory name:

| Disc | Directory name | Header `+16` name | Records | First record |
|---|---|---|---:|---|
| `elements1mb` | `Electric Grand X` | `Eelectric GrandX` | 9 | `ELEC GRAND _000` |
| `ditto-drums` | `PERCUSSION#1 X` | `PERCUSSION #1 X` | 31 | `TAMB BRASS` |
| `heavy` | `HvyGtr FX5 X` | `HvyGtr FX5 XX` | 2 | `Gtr FX 11` |
| `heavy` | `Misc Gtr FX 2MbX` | `Misc Gtr FX 2mbX` | 6 | `Gtr Feedback Shr` |
| `heavy` | `HvGtrFdBkTxtr2Mb` | `HvGtrFdBkTxtr2M ` | 1 | `Gtr FeedbackLoop` |

The corruptions are a shifted space, a case change, or a single doubled or dropped character — an edit of at most one once the name is lowercased and its spaces stripped. `_bank_headers` finds these headers (their `+16` names are plausible), but `_bank_offsets` never binds them to a directory entry, because the directory says `Electric Grand X` and the header says `Eelectric GrandX`.

They are not the operating-system slots and not index banks. An OS slot has no header at all where it points; an index bank declares a zero-length run. These declare a run, hold audio, and name it for the bank.

## Decision

**A directory entry that no header names exactly binds the header sitting at the address its placement predicts, when that header carries a near-copy of the entry's name and no other entry already claims it.**

Three gates, each measured:

**The header must sit exactly at the predicted address.** `want = unit × start + bias`, the same fit ADR-0021 already computes, requiring the same three agreeing single-header banks before it says anything. A disc that has shown no placement rule recovers nothing and keeps the note. Nothing is *placed*: a bank whose predicted address holds no header binds nothing.

**The header's name must be a near-copy.** Normalise both names — lowercase, strip spaces — and require a Levenshtein distance of at most one. Every one of the five is a distance of zero or one; the OS-slot collisions below are a dozen. The name is what confirms the hit, exactly as in ADR-0015 — the placement only says which header, and the name says it is the right one.

**The header must be unclaimed.** A bank may never bind a header that a name-matched entry already owns. This is the gate that decides `ditto-drums`: `E3 Main Code`'s predicted address lands on the `Ditto Drums X` index bank's header and `E3X Main Code`'s lands on `DAVE W KIT1 X`'s — both real banks the directory names elsewhere. The name gate rejects them (`e3maincode` is nothing like `dittodrumsx`), and the unclaimed gate is the belt to that suspenders.

This is [ADR-0021](0021-a-bank-owns-the-run-its-header-declares.md)'s instrument used one step wider: from *which of two headers wearing this exact name* to *the near-named header this entry points at*. Downstream nothing changes — a recovered entry flows through the same `_declared_run` and record walk, bounded by the same next-header cap, and a recovered bank with records gets no note while one that recovers nothing keeps it, so [ADR-0012](0012-a-probe-must-confirm-a-file.md) still holds.

## Alternatives rejected

**Bind by the predicted address alone, without a name gate.** The smallest change, and it recovers all five. Rejected on `ditto-drums`, measured: `E3 Main Code` and `E3X Main Code` are real slots on every EIII disc, and their predicted addresses fall on the `Ditto Drums X` and `DAVE W KIT1 X` headers. Address alone would hand each OS slot another bank's audio under the wrong name — the exact failure ADR-0015 exists to prevent, one layer in. The unclaimed gate catches these two, but a future disc whose OS slot points at an *orphan* stale header would slip through it; the name gate is what makes the rule safe rather than lucky.

**Widen the exact-name match to a fuzzy match everywhere in `_bank_offsets`.** Tempting, because it would need no placement. Rejected: it reopens the whole of ADR-0015. A fuzzy match between a directory name and *any* header on the disc would, on a disc full of `Gtr FX 11`-style near-identical names, bind a bank to a neighbour that is one edit away and holds different audio. The placement is what makes the near-name safe — it is only ever consulted at the one address the disc itself points the entry at.

**Leave them listed with the note.** The conservative reading, and the state issue #43 filed. Rejected on the evidence: the header is there, at the predicted address, carrying `ELEC GRAND` and `TAMB BRASS` and `Gtr Feedback`. Listing them empty withholds 49 samples the disc plainly offers, on the strength of a one-character typo the mastering left in a name field.

**Correct the header name to the directory's and match.** Would also work. Rejected as the wrong shape: it invents an authority the disc does not grant. The directory name and the header name disagree, and this project does not get to decide the header's is wrong — only that the two are the same bank. Binding by address with a near-name gate says exactly that and no more.

## Consequences

**Good.** Five banks that read nothing now read their records: `elements1mb` gains 9, `ditto-drums` 31, `heavy` 9 — 49 samples across three discs, each named for its bank.

**Good.** No reference disc moves. Simulated across all ten EMU3 reference discs and the three size-twins in the collection, the recovery binds nothing: every bank on those discs either matches a header exactly or has no header at its predicted address. The change fires only where a name was mistyped.

**Good.** The OS-code slots stay noted. `E3 Main Code` and `E3X Main Code` are refused by the name gate and the unclaimed gate together, so the note still names exactly the slots that hold an operating system and no audio.

**Watch for.** A disc that mistypes a header name by *more* than one normalised edit. It will list empty with the note, which is the honest floor — visible, and recoverable if a later disc shows the corruption is systematic enough to widen the threshold against.

**Deliberately not claimed.** Two discs here list a bank name **twice** in the directory — `Harpsichord X` on `elements1mb`, `HvyGtr Maj.Open` on `heavy` — and because `located` is keyed by name both entries resolve to one header and double-list the same records. That is a different mechanism from the five (it reads the same audio twice, not nothing), one arm of it points at a real but blank-named header, and it is left to [#47](https://github.com/bmxcode/samplerdisc/issues/47). This record recovers a header the directory names once and mistypes; it does not touch a name the directory writes twice.
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ If you find yourself writing an ADR with no rejected alternative, you are writin
| [0028](0028-a-displaced-partition-is-anchored-quantised-and-floored.md) | A displaced AKAI partition is searched for from its declared position, in the container's unit, and never inside one already read | Leaving them unread; scanning for the signature; filtering a scan by file yield; searching in AKAI blocks; truncating an overlapping recovery; moving the present partition instead; refusing a whole disc for one clash; relaxing the floor for `Alpha Dance I`; searching forward; per-file provenance; fixing #35 here |
| [0029](0029-a-record-is-closed-by-the-channel-it-declares.md) | An E-mu record is closed by the channel it declares, and found by either one | Falling back to `end_L` only where `end_R` fails; `max(end_L, end_R)` unconditionally; widening the signature alone; bounding the extent by the next record found; fixing the whole-extent loop guard here |
| [0030](0030-the-whole-extent-no-loop-is-refused-at-both-ends.md) | The whole-extent "no loop" is refused within the slack of both ends, not only frame 0 | Leaving the guard at `a == 0`; a content guard on end silence; matching each disc's fixed inset; widening the end slack instead |
| [0031](0031-a-bank-binds-the-near-named-header-its-placement-predicts.md) | A bank whose name no header matches binds the near-named header its placement predicts, when it is unclaimed | Binding by address alone; fuzzy-matching names everywhere; leaving them noted; rewriting the header name; fixing the twice-written-name double-listing here |
Loading
Loading