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.
|
||||
|
||||
Reference in New Issue
Block a user