Skip to content

EMU3: decode the record's channel count and write stereo as stereo (#32) - #34

Merged
bmxcode merged 3 commits into
mainfrom
d18-emu3-stereo
Aug 21, 2026
Merged

EMU3: decode the record's channel count and write stereo as stereo (#32)#34
bmxcode merged 3 commits into
mainfrom
d18-emu3-stereo

Conversation

@bmxcode

@bmxcode bmxcode commented Aug 21, 2026

Copy link
Copy Markdown
Owner

The EMU3 sample record's pointer block declares a channel count, and where it declares two the payload is a block split — all of the left channel, then all of the right. Read as one mono stream that is a file twice as long as the sound, with the right channel playing after the left rather than alongside it. 2 656 of the 14 738 E-mu samples on the reference shelf were coming out that way, and they now come out as one interleaved stereo WAV each.

ADR-0025 found the channel count in D17 and deliberately did not act on it, because acting on it moves audio and that deliverable was scoped to add metadata without touching a byte of payload. This is that deliverable. Closes #32.

Disc Samples Stereo
esi32-gm 2 265 28
eiiix-1 1 189 601
eiiix-2 1 333 592
protozoa 5 852 8
eiv-analogia 449 279
eiv-studio 2 822 320
eiv-vitous 828 828

Miroslav Vitous String Ensembles is the one to look at: 828 of 828, an orchestral string library that was entirely double-length mono.

A channel count is not enough on its own

2 721 records declare start_R == start_L + P/2, and 65 of them are not stereo. They declare the split and then close end_L somewhere else — 19 on protozoa, 40 on eiiix-1, 6 on eiiix-2. So the gate has a third condition, that the left block close exactly where the right one opens:

start_L == 92  and  P % 4 == 0
start_R == start_L + P / 2
end_L + 2 == start_R

The 65 rejects are not a judgement call, and six of protozoa's can be identified exactly. Trom B2, Trom E3 and Trom A3 are each written in two banks, and in all six the first half is byte for byte the whole of a one-channel record of the same name in Vintage PresetsX — 16 756 bytes of Trom B2 against a 33 512-byte payload — while nothing on the disc matches the second half. Those payloads are twice their sound, which is the only reason start_R lands on start_L + P/2 at all: arithmetic, not declaration. end_L says so, closing 8 bytes past the halfway point rather than on it. Without the third condition they ship with an unaccounted-for second sound in the right channel, and nothing reports a problem.

The third condition also keeps D17 whole. The only two records anywhere whose declared loop end lies past its own channel — Mbira A3 and Mbira F3 on eiiix-1 — are both rejects. They stay mono and keep their loops, so all seven loop counts survive by construction rather than by luck.

The obvious measurement is confounded, and that is the interesting part

The evidence in #32 was the RMS-envelope correlation between the two halves. Re-verifying it on the set this gate selects turned up the confound: a single decaying note's two halves both decay, and correlate at 0.94 without being two channels of anything. Every disc has a tail of those — Piano Db3, Glockenspiel D5, Snare 2. An envelope threshold set anywhere useful takes in hundreds of them.

So two sharper instruments, with a control on each side. Fine structure is the 64-frame RMS envelope divided by its own 1024-frame trend, so what is correlated is transients rather than the shape of the decay. Best lag is the peak normalised waveform cross-correlation over ±64 samples. The positive control is the twelve name-paired -L/-R records on eiv-analogia (ADR-0017) — known-true stereo established by a mechanism the pointer block knows nothing about. The negative control is halves taken from two different records.

records envelope r fine structure r best lag r
positive control — name-paired pairs 6 0.954 0.402 0.532
negative control — two different records 200 0.014 0.006 0.008
selected, esi32-gm 28 0.995 0.671 0.684
selected, eiiix-1 601 0.843 0.184 0.377
selected, eiiix-2 592 0.955 0.338 0.691
selected, protozoa 8 0.548 0.330 0.421
selected, eiv-analogia 279 0.908 0.343 0.430
selected, eiv-studio 320 0.983 0.667 0.768
selected, eiv-vitous 828 0.755 0.433 0.303
rejectedend_L past start_R 20 0.77 0.05 0.012
rejectedend_L short of the split 45 0.06 0.012 0.023

The selected set scores with the positive control on all seven discs; the rejects score with two unrelated records. protozoa's weak 0.55 in the #32 table was those false positives, 19 of its 27.

Channel order is structural, and asserted rather than assumed

The first block is the left channel: the pointer block is ordered (start_L, start_R) and start_L addresses the first block. That is the whole of the argument, and it is the kind of thing a swap would leave inaudible in isolation and wrong forever, so tests/test_emu3.py states it as a named claim rather than leaving it implicit in a slice index.

The only content evidence available is weak and agrees. Of eiv-analogia's twelve name-paired records, all six ending -L declare their single channel in the left-hand pointer set and three of the six ending -R declare theirs in the right-hand set — nine of twelve consistent, none contradicting, p ≈ 0.09. Recorded for exactly what it is worth.

The inverse error was looked for and is not there

A record that is stereo and declares one channel would leave a structural signature: an extent covering half its payload. Not one of the 12 017 records that do not declare the two-channel shape has it. The 439 whose halves correlate above 0.9 by envelope show a midpoint z-jump of −0.27 to −0.41 — there is no discontinuity where a block join would be — and they are the decaying single notes above.

eiiix-2 needed checking, because the format doc gave it the weakest separation in the table at 0.59. Under these instruments its high-envelope one-channel records score fine structure −0.021, the negative control, and its one-channel envelope median re-measures at 0.114 over 603 scored records. The 0.59 is not reproduced here; both figures are recorded and neither is relied on.

What did not move, and how that is known

  • The payload digests. The SHA-256 of every sample payload per disc is unchanged on all seven. read_file is untouched and the offset arithmetic is untouched.
  • The audio. The suite de-interleaves each stereo sample and requires it to reproduce the disc's two blocks byte for byte. Audio that moved has to be shown to be the same audio, and a length check cannot say that.
  • The sample counts — 2 265, 5 852, 1 189, 1 333, 449, 2 822, 828. One record is one sample; only its shape changed.
  • The loop counts — 107, 1 157, 1 260, 1 689, 449, 2 551, 826. (pointer − start) / 2 is a per-channel frame index either way, so a loop that moved would be arithmetic that drifted. A stereo sample's loop is now bounded by its channel, which is a tighter check than before.

The per-disc stereo counts are pinned in tests/test_discs.py as tightly as the sample counts, because the gate's third condition is exactly the kind of thing a later simplification removes.

What this deliberately does not do

It does not write the mono halves alongside, the way ADR-0007 does for -L/-R joins. That rule exists because name pairing is a heuristic and the halves are how a user notices a bad guess. Neither half transfers: the disc states the channel count and two further fields of the same record confirm it, so there is nothing to hedge — and the "original" would be one leg of a concatenation, not a sound anyone recorded.

It does not file the result under <volume>/stereo/. That directory means rebuilt from two files whose names looked like a pair. A natively-stereo record was never two files, and putting it there would tell a user the pairing had been guessed at when it was read.

It does not decide stereo from the audio. Content beats declared text in this project — a filename, a name field — and here the declaration is structure: three fields of one record agreeing with the payload's own length. Measurement's job was to check the gate, not to be it.

Both mechanisms remain, and eiv-analogia exercises both at once: samplerdisc extract on it reports 449 files, 279 of them stereo samples, and 6 joined stereo pairs. They are counted apart in the CLI and in the batch manifest, the way duplicates are counted apart from damage — one is a channel count the disc declared, the other a pairing this tool inferred from two filenames.

Known limits after this

65 records declare two channels and are not stereo, and only protozoa accounts for what they hold. There the second half is another bank's record at the constant shift ADR-0021 describes. On eiiix-1's 40 and eiiix-2's 6 the halves are measurably not one performance and nothing says where the second one came from. Mono is the safe answer, not an explained one.

protozoa falls from 27 stereo samples to 8, and 8 is too few to establish anything alone. Its stereo rests on the rule the other six discs establish, as eiv-analogia's loops rest on the other six in ADR-0025.

Four records have byte-identical halves (two on eiv-analogia, two on eiv-studio) and are written as stereo. Dual mono is a real thing a sampler can hold, and second-guessing it would mean deciding channel counts by comparing audio.

A generation writing a third record shape — one where end_L means something else again — would be refused silently and come out double-length mono, as quiet a failure as the one this fixes. The pinned stereo counts are the tripwire.

Also in here

The format doc's claim that E-IV discs pair samples into stereo by name is corrected. That is true of 12 samples across all seven discs — six pairs, all on eiv-analogia — against 2 656 whose record declares two channels, with zero overlap. Both mechanisms are real; the doc gave the rare one as the answer.

The record-shape table is also corrected. Its "neither" column folded in the 550 records that declare a single channel in the right-hand set — 542 on eiv-studio, 8 on eiv-analogia — which are ordinary one-channel records the loop decoder already reads correctly. That is why the old table said 620 and 24 where the finer breakdown says 78 and 16.

Two stale CHANGELOG "Known limits" bullets go with it: "Loop points and root key are still absent from E-mu WAVs", which D17 contradicted in the same section, and "The E-mu sample record has no channel count", which ADR-0025 overturned and this settles.

Verification

uv run ruff check . && uv run ruff format --check . && uv run pytest -q     # green
SAMPLERDISC_TEST_DISCS=~/repo/projects/sample-images uv run pytest -q       # green, all seven discs
uv tool install --editable . && samplerdisc --version                       # 0.3.0

Spot-checked on the discs themselves: a eiv-vitous bank writes 21 of 21 samples as two-channel WAVs at the true duration with no stereo/ directory created, and eiiix-1's Mbira A3 — one of the 65 — still writes mono at 237 136 frames with its loop at (2, 121 476) intact.

🤖 Generated with Claude Code

bmxcode and others added 3 commits August 21, 2026 22:16
The sample record's pointer block declares two channels on 2 721 records
across the seven reference discs, storing the payload as a block split --
all of the left channel, then all of the right. Read as one mono stream
that is a file twice as long as the sound. 2 656 of them now come out as
one interleaved stereo WAV each.

The other 65 declare the split and then close end_L somewhere else. They
are not stereo -- their halves score at the negative control on fine
structure and best lag, and on protozoa the second half is another bank's
record -- so the gate requires end_L + 2 == start_R and they stay mono.

Sample counts, payload digests and all seven loop counts are unchanged,
and the disc-backed suite now pins the stereo count per disc and checks
that de-interleaving reproduces the disc's two blocks byte for byte.

Closes #32

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ecord, not the second

The measurement is sharper than the claim it replaces. On six of the 19,
the *first* half is byte for byte a complete one-channel record of the
same name in another bank and nothing matches the second half -- so the
payload is twice the sound and start_R lands on the halfway point by
arithmetic rather than by declaration, which is exactly what end_L
contradicts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bmxcode
bmxcode merged commit 2af9a02 into main Aug 21, 2026
1 check passed
@bmxcode
bmxcode deleted the d18-emu3-stereo branch August 21, 2026 13:00
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.

E-mu stereo samples are written as double-length mono

1 participant