Measure the level off the whole disc, and find the headroom is not worth buying
FINDINGS 69, ROADMAP P6 -- the item 66.3 reopened in session 34 and sessions 35 and 36 both deferred. The chip clamps its accumulator at 10 bits INSIDE the recursion, and the ten seconds every audio figure in this tree is quoted on peak at 435 of 511: it fits, and it fits by accident, because that window is a -13.4 dBFS passage. Nothing knew what the loudest passage of the game was. tools/analysis/35_audio_level.py reads every stream of the unique scene footage (00000-00201) through extract_audio.py's own chain -- 1,291.6 s, 201 of 202 streams -- and encodes windows of it with adpcm.CHIP. The disc peaks at 946 of 2048 = -6.71 dBFS (00200 @ 2.11 s), which is 5.35 dB over the clamp, and the census behind that peak is 687 samples of 20,182,000 (0.0034%) in 402 events, 44.0 ms, longest 0.90 ms. THE HEADLINE IS A NEGATIVE: THE LEVEL DOES NOT CHANGE. Forty 2 s windows drawn over the game's timeline at six gains -- the disc's own level (gain 1.0) has the best mean SNR (22.03 dB) and the best median, and loses the worst-window column to -3 dB by 0.04 dB. The gain that guarantees zero clamping disc-wide (0.5402) costs 0.85 dB of mean SNR across the whole game to buy back 1.90 dB on the 2.11 s that clamp, because the OKI step table's floor is a constant 16 and does not scale with the signal. AND 66.3's MECHANISM DOES NOT SURVIVE A CONTROL. Error after a clamp run is elevated ~5x -- and so is the same window at a gain that never clamps, read at the same indices, because those samples are simply loud. Worst ratio 1.28 over 64 offsets, and the clamped encode's whole-window mean |error| is the LOWER of the two (4.71 vs 5.05). adpcm.encode runs the chip's clamp inside its own sixteen-way search, so it never loses the chip's state. The worry was right about the mechanism and aimed one layer too late: an encoder clamping at 12 bits while the chip clamps at 10 is exactly that divergence, and 66 closed it. pack.py gains --audio-gain (default 1.0) so the level is a named parameter with a measurement behind it instead of a shift buried in a list comprehension, and prints the encoded window's peak against the clamp. tmp/packed_singe.dlxp rebuilds byte-identical, all 6,039,040 B. New check.sh stage, ~18 s. Three rig facts in 69.4, because a shipping encoder meets all three: 00176 has no audio track at all; 00199 is 61.31 s of video with 1.25 s of audio; and 18 stream pairs share duration, peak and RMS, 7 of them byte-identical. The 10-bit clamp is a DRIVER SETTING, not a chip constant -- x68k.cpp:1089 sets OUTPUT_10BITS -- so it is MAME's reading of the board, and hardware item 5 is what settles it. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -7368,3 +7368,164 @@ not of the format. On silicon the chip latches a byte and consumes both nibbles.
|
||||
* **The silicon is untouched.** All four decoder axes are still MAME's, and
|
||||
session 34's fifth hardware item — play a known nibble stream on a real
|
||||
MSM6258V and record the line out — is still the cheap way to settle them.
|
||||
|
||||
## 69. The audio level is measured off the whole disc — and the headroom is not worth buying (session 37)
|
||||
|
||||
**ROADMAP P6, the item 66.3 reopened and two sessions deferred.**
|
||||
`tools/analysis/35_audio_level.py` (new), `tools/encoder/pack.py`
|
||||
(`--audio-gain`), `tools/bench/check.sh`.
|
||||
|
||||
**NAME THE LAYER.** This is **host arithmetic over the source disc**, encoded
|
||||
with `adpcm.CHIP` — the four axes session 34 measured off MAME's MSM6258. No
|
||||
68000 ran, no chip was fed. What it settles is what the *encoder* should hand
|
||||
the chip; what it cannot settle is whether MAME's chip is the chip, which is
|
||||
still hardware item 5.
|
||||
|
||||
66.3 left this open in as many words: the MSM6258's accumulator clamps at **10
|
||||
bits inside the recursion**, the ten seconds every audio figure in this tree is
|
||||
quoted on peaks at **435 of 511**, and that window is a −13.4 dBFS passage — so
|
||||
it fits with 1.4 dB to spare and it fits *by accident*. The level could not be
|
||||
chosen from it. It had to be chosen against the loudest thing the game can play.
|
||||
|
||||
### 69.1 The disc, all of it
|
||||
|
||||
Every stream of the unique scene footage — `00000`-`00201`, FINDINGS 32.1 —
|
||||
through `extract_audio.py`'s own chain (AC-3 5.1, ffmpeg's default downmix
|
||||
matrix, mono, 15,625 Hz), then `s16 >> 4`, which is exactly what `pack.py`
|
||||
hands the encoder.
|
||||
|
||||
| | |
|
||||
|---|---:|
|
||||
| streams with an audio track | **201 of 202** |
|
||||
| audio | **1,291.6 s = 21.5 min** |
|
||||
| **DISC PEAK** | **946 of 2048 = −6.71 dBFS** (`00200` @ 2.11 s) |
|
||||
| the chip's clamp, same units | **511 = −12.06 dBFS** |
|
||||
| so the disc is over the clamp by | **5.35 dB** |
|
||||
| samples above it | **687 of 20,182,000 = 0.0034%** |
|
||||
| ...in | **402 events, 44.0 ms**, longest run **14 samples = 0.90 ms**, median run **1** |
|
||||
|
||||
**So the answer to "does the disc clip the chip" is yes, and the number behind
|
||||
the yes is 44 milliseconds in 21.5 minutes.** A peak alone could not have said
|
||||
that: 687 samples as one sustained 44 ms burst and 687 samples as 402
|
||||
sub-millisecond events are the same census row and completely different sounds,
|
||||
which is why the tool reports the run lengths and not just the count.
|
||||
|
||||
### 69.2 THE HEADLINE. Every attenuation that buys the headroom costs more than the clamping does
|
||||
|
||||
Forty 2-second windows drawn over the game's own timeline — weighted by stream
|
||||
length, so a 24 s stream gets twenty times the draws of a 1.2 s one — encoded at
|
||||
every gain with `adpcm.CHIP`, reported against the *scaled* source, which is the
|
||||
honest comparison because the listener's amplifier is not this project's problem:
|
||||
|
||||
| gain | dB | mean SNR | median | worst window | windows that clamp |
|
||||
|---:|---:|---:|---:|---:|---:|
|
||||
| **1.0000** | **0.00** | **22.03** | **21.55** | 16.83 | 5 of 40 (20 samples) |
|
||||
| 0.7071 | −3.01 | 21.63 | 21.46 | **16.87** | 0 |
|
||||
| 0.5402 | −5.35 | 21.18 | 20.74 | 15.26 | 0 |
|
||||
| 0.5000 | −6.02 | 20.95 | 20.60 | 13.91 | 0 |
|
||||
| 0.3536 | −9.03 | 20.13 | 19.99 | 11.48 | 0 |
|
||||
| 0.2500 | −12.04 | 18.81 | 19.12 | 8.23 | 0 |
|
||||
|
||||
**The disc's own level wins every column but one, and it loses that one by 0.04
|
||||
dB.** 0.5402 is not a round number and is not meant to be: it is 511/946, the
|
||||
gain at which the loudest sample on the disc lands exactly on the clamp with
|
||||
nothing to spare, and buying that guarantee costs **0.85 dB of mean SNR across
|
||||
the whole game**.
|
||||
|
||||
The mechanism is the OKI step table. Its floor is a constant **16 in 12-bit
|
||||
units** and does not scale with the signal, so attenuation moves the programme
|
||||
down toward a quantiser that stays where it is. The three named passages show
|
||||
both ends of it:
|
||||
|
||||
| passage | gain 1.0 | −3.01 dB | −5.35 dB | −12.04 dB |
|
||||
|---|---:|---:|---:|---:|
|
||||
| the disc peak itself (`00200`, 2.11 s, 133 samples clamp) | 17.94 | 19.59 | **19.84** | 19.07 |
|
||||
| the loudest sustained window (`00037`) | **24.67** | 24.64 | 24.55 | 21.89 |
|
||||
| a quiet passage (`00001`) | **20.98** | 20.44 | 19.78 | 15.05 |
|
||||
|
||||
**Clamping costs the worst 2 seconds on the disc 1.90 dB. Avoiding it costs the
|
||||
other 21.5 minutes 0.85.** That is the trade, and it is not close.
|
||||
|
||||
**USER-FACING DECISION, and it is a NEGATIVE: the level does not change.**
|
||||
`pack.py` still hands the encoder `s16 >> 4`, and the container the gate builds
|
||||
is byte-identical under the new code — which is checked rather than asserted:
|
||||
`tmp/packed_singe.dlxp` was rebuilt and `cmp`-ed against the one session 36 ran
|
||||
off a volume, all 6,039,040 bytes. What is new is
|
||||
that the shift is now a named parameter, `--audio-gain`, with a measurement
|
||||
behind its default instead of an accident, and `pack.py` prints the encoded
|
||||
window's peak against the clamp and the count of samples above it. On the gate
|
||||
window that line reads **435 of 511, 0 clamped**, which is 66.3 reproduced from
|
||||
inside the encoder.
|
||||
|
||||
### 69.3 And 66.3's actual worry — that a clamp OUTLIVES its sample — does not survive a control
|
||||
|
||||
66.3's reasoning was exactly right for a recursive codec and is the reason this
|
||||
was not settled by a peak meter: a clamped accumulator is a **wrong state**, the
|
||||
next nibble is applied to it, so the damage should persist after the loud sample
|
||||
has gone. Measure the error after a clamp run and it *is* elevated — about 5x
|
||||
the off-clamp mean for at least 64 samples.
|
||||
|
||||
That is not evidence. The samples after a clamp run are **loud** samples, where
|
||||
the step index is high and the error is large whether or not anything clamped.
|
||||
So the control is the same window at the gain that never clamps, rescaled into
|
||||
the same units and read at the **same sample indices**:
|
||||
|
||||
| after a clamp run ends | +0 | +1 | +2 | +8 | +32 | +63 |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| gain 1.0 (133 samples clamp) | 25.05 | 35.05 | 34.34 | 22.19 | 22.27 | 20.36 |
|
||||
| control at 0.5402, rescaled | 27.02 | 32.13 | 29.89 | 21.37 | 19.41 | 21.33 |
|
||||
| ratio | 0.93 | 1.09 | 1.15 | 1.04 | **1.15** | 0.95 |
|
||||
|
||||
**Worst ratio over 64 offsets: 1.28. Whole-window mean |error|: 4.71 clamped
|
||||
against 5.05 for the control** — the clamped encode is the *better* one overall,
|
||||
which is 69.2 arriving from the other direction.
|
||||
|
||||
The reason is that **the encoder's in-loop decoder models the clamp exactly**
|
||||
(`adpcm.encode` clamps inside its own sixteen-way search, `adpcm.CHIP["bits"]`
|
||||
= 10). It always knows the state the chip is in, including a saturated one, and
|
||||
re-chooses the next nibble from there. A clamp costs the samples it happens on
|
||||
and does not propagate.
|
||||
|
||||
**This is a property of a CHIP-MATCHED encoder and not of the format**, and it
|
||||
is the sharpest argument yet for session 34's work: an encoder that clamped at
|
||||
12 bits while the chip clamped at 10 — which is what this tree shipped until
|
||||
session 34 — would have had the encoder and the chip in *different states*, and
|
||||
that is the divergence 66.3 described. The worry was correct about the mechanism
|
||||
and was aimed one layer too late.
|
||||
|
||||
### 69.4 Three rig facts the scan turned up, recorded because a shipping encoder meets all three
|
||||
|
||||
* **`00176` has NO AUDIO TRACK.** 3.0 s of mpeg2video, no stream to decode.
|
||||
ffmpeg exits non-zero on it, so an encoder that shells out per scene has to
|
||||
tell "this clip is silent" apart from "the extract failed", and emit silence
|
||||
of the right length rather than a short lump.
|
||||
* **`00199` is 61.31 s of video with 1.25 s of audio**, which is 60 s of the
|
||||
75 s gap between the disc's 1,366.6 s of footage and its 1,291.6 s of audio.
|
||||
The remaining ~15 s is spread over 200 streams as sub-half-second differences
|
||||
— AC-3 frame alignment, not missing content. **A cadence-driven container
|
||||
computes its lump count from the FRAME count**, so a stream whose audio is
|
||||
shorter than its video is a case with a right answer (pad) and a wrong one
|
||||
(run out mid-scene), and nothing has been built that makes that choice yet.
|
||||
* **18 stream pairs share duration, peak and RMS to three decimals, and 7 of
|
||||
those pairs are byte-identical audio** (25.0 s, every one of them a 3.5 s
|
||||
clip — the reused death animations). It is a redundancy, it is small, and no
|
||||
claim is made here about the *video* of those pairs; Dragon's Lair reuses
|
||||
footage mirrored, which would leave the audio identical and the picture not.
|
||||
|
||||
### 69.5 What this does NOT settle
|
||||
|
||||
* **SNR is not a listener.** Every number above is a power ratio. 402 clamp
|
||||
events of a millisecond each are 0.85 dB cheaper than avoiding them *by that
|
||||
measure*; whether they are audible as a tick on a −6.7 dBFS transient is a
|
||||
question this project has no instrument for and has never claimed one.
|
||||
* **The 10-bit clamp is a DRIVER SETTING, not a chip constant.** MAME's
|
||||
`okim6258` takes the width as a parameter and `x68k.cpp:1089` sets
|
||||
`OUTPUT_10BITS` for this machine specifically. If a real MSM6258V on a real
|
||||
X68000 runs 12-bit, the disc clears the clamp by 6.7 dB, the whole of 69.1 is
|
||||
moot and the answer — do not attenuate — is unchanged and merely cheaper.
|
||||
**The measurement that settles it is still hardware item 5.**
|
||||
* **The level was measured, not the loudness.** No perceptual weighting, no
|
||||
gate, no LUFS: the statistic is peak and RMS of the same samples the encoder
|
||||
sees, which is the right unit for a clamp question and the wrong one for a
|
||||
mixing question.
|
||||
* **Nothing was listened to.** Unchanged since 68.6.
|
||||
|
||||
@@ -150,6 +150,28 @@ sound as well as a picture. 67.2's drift is measured rather than derived —
|
||||
predicted 1.25 s, played **1.26 s**. **What is left of P6 is the level (66.3)
|
||||
and the refill climb with a second consumer through a real branch point.**
|
||||
|
||||
Amended end of session 37: **THE AUDIO LEVEL IS MEASURED, AND THE ANSWER IS
|
||||
THAT IT DOES NOT CHANGE (FINDINGS 69).** Every stream of the game's own footage
|
||||
(`00000`-`00201`) through `extract_audio.py`'s chain says the disc peaks at
|
||||
**946 of 2048 = −6.71 dBFS**, which is **5.35 dB over** the chip's 10-bit clamp
|
||||
— and the census behind that peak is **687 samples of 20.2 million, 402 events,
|
||||
44.0 ms in 21.5 minutes**. Forty windows drawn over the game and encoded at six
|
||||
gains then price the choice: the disc's own level has the best mean SNR
|
||||
(**22.03 dB**) and the gain that guarantees zero clamping costs **0.85 dB across
|
||||
the whole game** to buy back **1.90 dB on the 2.11 s that clamp**, because the
|
||||
OKI step floor is a constant 16 and does not scale with the signal. **And
|
||||
66.3's mechanism does not survive a control**: error after a clamp run is
|
||||
elevated ~5x, and so is the same window at a gain that never clamps, read at the
|
||||
same indices — worst ratio **1.28** — because `adpcm.encode` runs the chip's
|
||||
clamp inside its own search and therefore never loses the chip's state. The
|
||||
worry was right about the mechanism and aimed one layer too late; session 34
|
||||
had already closed it. `--audio-gain` exists so the level is a parameter with a
|
||||
measurement behind it, its default is 1.0, and the gate container is
|
||||
byte-identical. **What is left of P6 is the refill climb with a second consumer
|
||||
through a real branch point** — and 69.4 adds two small unbuilt cases, a scene
|
||||
with no audio track (`00176`) and a scene whose audio is shorter than its video
|
||||
(`00199`).
|
||||
|
||||
**THE COMPLETION TARGET IS M3, THE VERTICAL SLICE** (USER DECISION): one scene
|
||||
tree — a decision point, two outcomes, a death clip — with audio, streaming from
|
||||
a real SCSI volume on a stock 2 MB machine, playable. That is the point at which
|
||||
|
||||
+113
@@ -1,3 +1,116 @@
|
||||
# Status & next-session handoff — end of session 37 (2026-08-25)
|
||||
|
||||
## Session 37: the audio level is measured off the whole disc, and the headroom is not worth buying
|
||||
|
||||
**FINDINGS 69.** ROADMAP P6, the item 66.3 reopened in session 34 and sessions
|
||||
35 and 36 both deferred. It needed no new machinery on the machine and it did
|
||||
need the whole disc: `tools/analysis/35_audio_level.py` reads every stream of
|
||||
the unique scene footage (`00000`-`00201`, FINDINGS 32.1) through
|
||||
`extract_audio.py`'s own chain and encodes windows of it with `adpcm.CHIP`.
|
||||
|
||||
**What was open.** The chip clamps its accumulator at 10 bits INSIDE the
|
||||
recursion, so anything the encoder is handed above 511 (12-bit units) is
|
||||
unreachable. The ten seconds this tree gates everything on peak at 435 of 511 —
|
||||
it fits, and it fits by accident, because that window is −13.4 dBFS. Nothing
|
||||
knew what the loudest passage of the game was.
|
||||
|
||||
**What it measures.**
|
||||
|
||||
| | |
|
||||
|---|---:|
|
||||
| audio on the game's own footage | 1,291.6 s = 21.5 min, 201 of 202 streams |
|
||||
| **disc peak** | **946 of 2048 = −6.71 dBFS** (`00200` @ 2.11 s) |
|
||||
| the clamp | 511 = −12.06 dBFS — the disc is **5.35 dB over** |
|
||||
| samples above it | 687 of 20,182,000 = **0.0034%** |
|
||||
| ...in | 402 events, **44.0 ms**, longest 0.90 ms, median run 1 |
|
||||
|
||||
**THE HEADLINE, and it is a negative: the level does not change.** Forty 2 s
|
||||
windows drawn over the game's timeline, encoded at six gains: the disc's own
|
||||
level (`s16 >> 4`, gain 1.0) has the best mean SNR (**22.03 dB**) and the best
|
||||
median, and loses the worst-window column to −3 dB by **0.04 dB**. The gain that
|
||||
guarantees zero clamping disc-wide (0.5402 = 511/946) costs **0.85 dB of mean
|
||||
SNR across the whole game** to buy back **1.90 dB on the 2.11 s that clamp**.
|
||||
The OKI step table's floor is a constant 16 and does not scale with the signal,
|
||||
so attenuating walks the programme down toward a quantiser that stays put.
|
||||
|
||||
**And 66.3's mechanism does not survive a control.** The worry was that a
|
||||
clamped accumulator is a wrong state the next nibble is applied to. Error after
|
||||
a clamp run IS elevated ~5x — and so is the same window at a gain that never
|
||||
clamps, read at the same indices, because those samples are simply LOUD. Worst
|
||||
ratio against the control over 64 offsets: **1.28**, and the clamped encode's
|
||||
whole-window mean |error| is **lower** (4.71 vs 5.05). The reason is that
|
||||
`adpcm.encode` runs the chip's clamp inside its own sixteen-way search, so it
|
||||
always knows the state the chip is in, saturated included. **The worry was
|
||||
correct about the mechanism and aimed one layer too late**: an encoder clamping
|
||||
at 12 bits while the chip clamps at 10 — what this tree shipped until session
|
||||
34 — is exactly the divergence described, and 66 already closed it.
|
||||
|
||||
**What changed in the tree.** `--audio-gain` on `pack.py` (default 1.0), which
|
||||
makes the level a named parameter with a measurement behind it instead of a
|
||||
shift buried in a list comprehension; `pack.py` now prints the encoded window's
|
||||
peak against the clamp and the count above it (gate window: **435 of 511, 0
|
||||
clamped**). `tmp/packed_singe.dlxp` was rebuilt and is **byte-identical** to the
|
||||
one session 36 ran off a volume, all 6,039,040 B. A new check.sh stage (~18 s)
|
||||
gates the disc peak and the clamp census.
|
||||
|
||||
**Three rig facts recorded in 69.4**, because a shipping encoder meets all
|
||||
three: `00176` has **no audio track at all** (ffmpeg exits non-zero — "silent
|
||||
clip" has to be distinguishable from "extract failed"); `00199` is 61.31 s of
|
||||
video with 1.25 s of audio, which is 60 s of the 75 s gap between the disc's
|
||||
1,366.6 s of footage and its 1,291.6 s of audio, and a cadence-driven container
|
||||
computes its lump count from the FRAME count; and 18 stream pairs share
|
||||
duration, peak and RMS, of which 7 are byte-identical audio (25.0 s, all of them
|
||||
3.5 s clips — the reused death animations).
|
||||
|
||||
## HANDOFF — start here
|
||||
|
||||
**THE TREE IS ALL GREEN**, session 37's stage included.
|
||||
|
||||
### The work, in the order it should be done
|
||||
|
||||
**1. THE REFILL CLIMB WITH A SECOND CONSUMER, through a real branch point**
|
||||
(51.3, 55.4). Now the oldest open item in P6 and unchanged by this session: the
|
||||
slack table is in `32_audio_wire.py`, nothing has been run, and session 36 gave
|
||||
it the player that holds two streams at once. Pointing it at a scene change is
|
||||
the work; nothing has to be built.
|
||||
|
||||
**2. THE BUFFERING DEPTH, which is cheap.** `PG_ANBUF` is 3 and `PG_APRE` is a
|
||||
mailbox. Two slots is one constant and one run, and it is 7,168 B of a machine
|
||||
that has 99,328 B of record buffer to find elsewhere (68.6).
|
||||
|
||||
**3. THE SILENT-CLIP AND SHORT-AUDIO CASES (69.4).** Neither is hard and
|
||||
neither has an answer in the tree: a scene whose audio is shorter than its
|
||||
frames, and a scene with no audio track. Both are decided by what the lump
|
||||
count is computed from, and both are cheap to get wrong quietly, which is this
|
||||
format's whole failure mode (67.4).
|
||||
|
||||
### What is still BLOCKED, so it is not picked up by mistake
|
||||
|
||||
**K4 — the packed player that is on screen — is conditional on B2**, a board
|
||||
question. **E7, E4 and C1** are parked (61.8), and **P4a's wiring** is parked
|
||||
with the ring K3 deleted.
|
||||
|
||||
**The hardware list is unchanged and is the user's**: B1 (sustained AND the
|
||||
data-phase BURST rate, 64.2 — acceptance is **589.6 KB/s**), B2 (blanking), B3
|
||||
(`#EXREQ`), B4 (a byte write to a palette register), and session 34's fifth,
|
||||
play a known nibble stream on a real MSM6258V and record the line out. **Session
|
||||
37 sharpens the fifth**: the 10-bit clamp this whole session is about is a
|
||||
DRIVER SETTING — `x68k.cpp:1089` calls `set_outbits(OUTPUT_10BITS)` — not a chip
|
||||
constant, so it is MAME's reading of the board. If real silicon runs 12-bit the
|
||||
disc clears the clamp by 6.7 dB and the answer here is unchanged and cheaper.
|
||||
|
||||
### Reproducing this session
|
||||
|
||||
python3 tools/analysis/35_audio_level.py --gate # ~18 s
|
||||
python3 tools/analysis/35_audio_level.py --survey 40 --ladder --recover
|
||||
# ~7 min: the ladder, the disc-wide survey and the clamp-recovery control
|
||||
./tools/bench/check.sh # ALL GREEN
|
||||
|
||||
**WHAT IS NEXT.** The refill climb with the second consumer, through a real
|
||||
branch point.
|
||||
|
||||
---
|
||||
|
||||
# Status & next-session handoff — end of session 36 (2026-08-25)
|
||||
|
||||
## Session 36: the container is played, and holding the bus costs the audio
|
||||
|
||||
Reference in New Issue
Block a user