Put sound on the wire, and find three LSBs are worth 25 dB
ROADMAP P6, everything in the item except the bus half session 20 closed. tools/encoder/adpcm.py is an MSM6258 codec, tools/encoder/extract_audio.py takes the same seconds of the same stream the frames come from, tools/bench/verify_adpcm.py is the gate, tools/analysis/32_audio_wire.py the container arithmetic. There is no reference encoder -- ffmpeg has a decoder for this format and none the other way -- so what is gated is the decoder the encoder runs INSIDE its own nibble search, sample-exact against ffmpeg's over 4,268 nibbles. An encoder that agrees with its own wrong decoder is what that catches. The Singe window: 156,250 samples -> 78,125 B at 21.97 dB, which is 7,812.5 B/s to the byte. Normalising the disc's -13.4 dBFS level moves the SNR 21.97 -> 21.97, so the level is not a lever. And the two published delta formulas are not the same codec. They differ by at most 3 in 12-bit units; encode for one and decode on the other and the SNR goes 21.97 -> -2.88 dB, the noise louder than the signal, because ADPCM is recursive the way the video codec is temporally recursive. Which one the chip runs is now P6a and it is a precondition on shipping any audio. And audio is the first thing the packed branch's simplification has cost anything for. A record has no index BY DESIGN, so audio cannot be per-record without making records variable; it rides a fixed cadence (F, A), the obvious F=1 wastes 57.3% of every audio sector, and the pick is F=11 A=14 -- 0.09% padding, 14,336 B held, wire 582.0 -> 589.6 KB/s. The codec container, which kept its index, pays zero. The MAME experiment did not work and 65.5 says so: :okim6258 is there at $E92001/$E92003, read out of the machine's own program map, and feeding it from Lua recorded silence across control 0..3 x port C 0..15. The register semantics were not guessed at further. FINDINGS 65. check.sh ALL GREEN before and after, with a new stage. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -6768,3 +6768,184 @@ throughout) — and no MAME source tree was available on this machine to name it
|
||||
third** that decides whether a DMAC-direct packed player shows a picture.
|
||||
- **Whether B is buildable as described.** It is priced off a measured blit and
|
||||
a measured ladder, and no line of it has been written.
|
||||
|
||||
---
|
||||
|
||||
## 65. Audio has an encoder, and the packed container's best property is what makes it cost (session 33)
|
||||
|
||||
**ROADMAP P6**, everything in the item except the bus half session 20 closed.
|
||||
`tools/encoder/adpcm.py` is the codec, `tools/encoder/extract_audio.py` the
|
||||
extraction, `tools/bench/verify_adpcm.py` the gate, `tools/analysis/32_audio_wire.py`
|
||||
the container arithmetic. All of it is **host arithmetic and one emulator
|
||||
introspection**; no board ran, and the one MAME experiment that was attempted
|
||||
did not work — 65.5 says so rather than leaving it out.
|
||||
|
||||
### 65.1 There is no reference encoder, so the gate had to be built sideways
|
||||
|
||||
The X68000's ADPCM is an OKI MSM6258V: 4 bits a sample, two to a byte, 12-bit
|
||||
signal word, and three rates that are 8 MHz over 512, 768 and 1024. **ffmpeg has
|
||||
a decoder for the format (`adpcm_ima_oki`) and no encoder**, so there is nothing
|
||||
to diff an encoder against.
|
||||
|
||||
What `verify_adpcm.py` gates instead is the thing that can actually be wrong:
|
||||
the encoder runs a decoder **inside its own loop** to choose each nibble, and
|
||||
that decoder is checked **sample-exact against ffmpeg's** over 4,268 nibbles.
|
||||
An encoder that agrees with its own wrong decoder is exactly the failure mode a
|
||||
round-trip test cannot see.
|
||||
|
||||
Two facts fell out of building it, and both were **measured rather than assumed**:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| nibble order | **HIGH NIBBLE FIRST** — reading low-first disagrees with ffmpeg on 3,285 of 4,268 samples, and that mismatch is carried as the gate's negative control |
|
||||
| the step table | 49 entries, **BUILT** as `floor(16 * 1.1**k)` and checked against the published list, so a transcription slip is not one of the things that can be wrong |
|
||||
|
||||
On the window this project gates everything on (00223 @539.4 s, 10.000 s, the
|
||||
same seconds as `tmp/fr_singe`): **156,250 samples → 78,125 B, SNR 21.97 dB**,
|
||||
and 78,125 B / 10.000 s is **7,812.5 B/s to the byte**, which is 52's figure
|
||||
arriving from the other direction.
|
||||
|
||||
**A negative worth having: the level is not a lever.** The disc's window peaks
|
||||
at −13.4 dBFS, using 435 of the 12-bit word's 2,048. Normalising it — gain ×4.7,
|
||||
one sample clipped — moves the SNR from **21.97 dB to 21.97 dB**. The step
|
||||
table's adaptation covers the range, so there is no headroom win to collect and
|
||||
no reason to touch the disc's level.
|
||||
|
||||
### 65.2 The two available references DISAGREE, and it costs 25 dB
|
||||
|
||||
The delta a nibble contributes has two forms in circulation:
|
||||
|
||||
'shift' delta = ((2*(n&7) + 1) * step) >> 3
|
||||
'terms' delta = step/8 + (n&4)*step + (n&2)*step/2 + (n&1)*step/4,
|
||||
each term truncated independently
|
||||
|
||||
`'shift'` is what ffmpeg computes — **verified sample-exact here, so that is a
|
||||
measurement of the decoder that ships, not a reading of its source.** `'terms'`
|
||||
is the OKI datasheet's own form, the one a chip builds out of shifts and adds,
|
||||
and it is what MAME's `okim6258` is understood to compute. **That last clause is
|
||||
NOT verified**: no MAME source tree is on this machine (64.4).
|
||||
|
||||
They differ **on 1,000 of 4,268 sampled nibbles, by at most 3 in 12-bit units**,
|
||||
which reads like something nobody could hear. **It is not.**
|
||||
|
||||
| encoded | decoded | SNR |
|
||||
|---|---|---:|
|
||||
| `shift` | `shift` | **21.97 dB** |
|
||||
| `shift` | `terms` | **−2.88 dB** |
|
||||
| `terms` | `terms` | 21.99 dB |
|
||||
| `terms` | `shift` | −3.38 dB |
|
||||
|
||||
**The noise is louder than the signal.** Per-sample disagreement over the real
|
||||
window: **max 257, mean 78.2**, against a source whose RMS is 74.4. A 3-LSB
|
||||
formula difference becomes a 25 dB loss because **ADPCM is RECURSIVE** — the
|
||||
delta is added to a running predictor and the nibble also moves the step index,
|
||||
so a disagreement does not stay where it happens. It is the same shape as the
|
||||
codec's temporal recursion, one dimension down: 64.1 used that recursion to make
|
||||
one frame audit 120, and here the same property turns a rounding difference into
|
||||
a broken stream.
|
||||
|
||||
**So "which formula does the MSM6258 run" is not a footnote. It is a
|
||||
precondition on shipping any audio at all**, and it has to be answered before an
|
||||
encoder's output is committed to a container.
|
||||
|
||||
### 65.3 The interleave, and why the obvious cadence is the wrong one
|
||||
|
||||
**A packed record is 49,664 B = 97 sectors and its address is `LBA0 + i*97`.
|
||||
There is no index and none can be needed** — that is the format's whole claim
|
||||
(63, 64.1). Audio is a stream at a rate with no arithmetic relationship to the
|
||||
frame rate: at 15,625 Hz a 12 fps slot is **651.0417 B**, and the `.0417` is the
|
||||
same remainder the frame clock carries (54), because 8 MHz / 512 / 2 / 12 has a
|
||||
3 in the denominator that no power of two clears.
|
||||
|
||||
Give record *i* the audio belonging to slot *i* and the records become variable
|
||||
length — and the moment records are variable length the format needs an index
|
||||
and stops being the format. So audio rides a **fixed cadence**: every `F`
|
||||
frames, `A` whole sectors, placed between records, leaving
|
||||
|
||||
LBA(i) = LBA0 + i*97 + floor(i/F)*A
|
||||
|
||||
which is still arithmetic. Choosing `(F, A)` is a rational approximation to
|
||||
`15625/12288 = 1.271565755` from above, and **the obvious cadence is the worst
|
||||
point in the space**:
|
||||
|
||||
| F | A | lump | needs | padding | wire adds | held (2 lumps) |
|
||||
|---:|---:|---:|---:|---:|---:|---:|
|
||||
| **1** — one lump a record | 2 | 1,024 B | 651.0 B | **57.29%** | **12.00 KB/s** | 2,048 B |
|
||||
| 3 | 4 | 2,048 B | 1,953.1 B | 4.86% | 8.00 KB/s | 4,096 B |
|
||||
| 7 | 9 | 4,608 B | 4,557.3 B | 1.11% | 7.71 KB/s | 9,216 B |
|
||||
| **11** | **14** | **7,168 B** | 7,161.5 B | **0.09%** | **7.64 KB/s** | **14,336 B** |
|
||||
| 81 | 103 | 52,736 B | 52,734.4 B | 0.003% | 7.63 KB/s | 105,472 B |
|
||||
|
||||
**F=11 is the pick.** It buys 57.2 points of padding for 12,288 B of RAM over
|
||||
the naive cadence; the floor of the sweep buys the last 0.09 of a point for
|
||||
91,136 B more, and on a machine where K4 already wants 99,328 B for two record
|
||||
buffers that second trade is not one.
|
||||
|
||||
**The wire, then:** the packed container's sustained requirement was **582.0 KB/s
|
||||
silent** and is **589.6 KB/s with sound** (+1.31%). A literal frame's bitrate is
|
||||
geometry and cannot be talked down; the audio on top of it is 7.63 KB/s of
|
||||
payload and can only be talked down by choosing a worse chip rate.
|
||||
|
||||
### 65.4 And this is the first price anyone has found for the packed branch's own simplification
|
||||
|
||||
The codec container **pays none of it**. `rc_fr_singe_scsi_span.dlx` already
|
||||
carries an index and already has variable records (4,096..41,472 B, sector-aligned
|
||||
since DLX5), so it can put exactly 651.0417 B of audio in record *i* and pad only
|
||||
to the sector it was going to pad to anyway: **zero audio padding**, wire
|
||||
440.4 → 448.1 KB/s.
|
||||
|
||||
"A record's length is geometry, so there is no index and none can be needed" is
|
||||
what makes the packed player a page of arithmetic instead of a parser — and it
|
||||
is **exactly** the property that makes a second stream at an unrelated rate cost
|
||||
a cadence, a padding fraction and a 14,336 B buffer. It is a small cost and it
|
||||
is not zero, and **nothing in FINDINGS 61-64 predicted it**. 64's risk list said
|
||||
"a simplification that large usually hides something"; this is the first thing
|
||||
it hid.
|
||||
|
||||
The bus half reproduces 52 exactly, which is why the tool prints it: 651.0 B a
|
||||
slot at the IPL ROM's own channel-3 cost of 16..19 clk/B is **10,417..12,370
|
||||
clocks = 1.25%..1.48% of a slot**. **The interaction 52 could not have had** is
|
||||
with 64.2's write window: a DMAC-direct packed player holds the GVRAM window
|
||||
open for the whole data phase, so an audio channel stealing the bus during that
|
||||
phase makes the phase longer — audio costs **darkness**, not just clocks. It is
|
||||
negligible against a dark fraction that is already 1.0, and it is not negligible
|
||||
against K4's 27.3% paint. That is the third time this session the two players
|
||||
have ranked differently on a column that is not clocks.
|
||||
|
||||
### 65.5 The experiment that did not work, stated rather than omitted
|
||||
|
||||
65.2's open question has an obvious apparatus: **MAME's x68000 has the chip**,
|
||||
so it can be asked. Introspection got as far as fact and no further, and the
|
||||
facts are worth keeping because the next attempt starts from them rather than
|
||||
from folklore:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| device | **`:okim6258`**, shortname `okim6258` — it is there |
|
||||
| registers | **`$E92001`** and **`$E92003`**, each one byte, read out of the maincpu program map by `tools/bench/probe_adpcm.lua` |
|
||||
| also on the map | `$E9A000-$E9BFFF`, the PPI that carries ADPCM pan and the clock divider |
|
||||
|
||||
**Feeding the chip from Lua produced no audio.** `tools/bench/probe_adpcm2.lua`
|
||||
writes a control byte to `$E92001` and nibble pairs to `$E92003`;
|
||||
`probe_adpcm3.lua` sweeps PPI port C at `$E9A005` over all sixteen low-nibble
|
||||
values with a loud burst under each. **Control 0..3 × port C 0..15: MAME's
|
||||
`-wavwrite` capture is silent throughout, 0 of 567,360 samples non-zero.**
|
||||
|
||||
The register *semantics* are the gap — which control value starts playback,
|
||||
whether port C needs the PPI's mode word set first, and whether the chip has a
|
||||
clock at all until the divider is written. **None of that was guessed at further,
|
||||
because guessing at it is how a rig produces a confident wrong answer.** The way
|
||||
to do this is from 68000 code with the IPL ROM's own channel-3 DMAC
|
||||
configuration, which `tools/analysis/21_iplrom_dmac.py` already reads out of the
|
||||
ROM — the real design, and the one path in the machine that is known to be
|
||||
correct because Sharp wrote it.
|
||||
|
||||
### 65.6 What this does not settle
|
||||
|
||||
- **Which delta formula the chip runs.** 65.2, and it is worth 25 dB.
|
||||
- **Anything on a board.** No hardware ran. The `-wavwrite` silence is a
|
||||
statement about an apparatus, not about a chip.
|
||||
- **The container.** DLXP1 has no audio section; 65.3 is the arithmetic a
|
||||
DLXP2 would be built from, and no byte of one has been written.
|
||||
- **What audio does to a scene change.** The slack table is here, but 51.3's
|
||||
refill climb with a second consumer through a real branch point is not.
|
||||
|
||||
Reference in New Issue
Block a user