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.
|
||||
|
||||
+67
-4
@@ -74,6 +74,28 @@ the CPU opens the window only for the measured 27.3% blit, so it is on screen
|
||||
the clock cannot tell** — 46.9% of V-DISP edges lost, zero late frames reported,
|
||||
the player believing 12 fps while the screen ran at 6.37. **The open item is now
|
||||
K4.**
|
||||
Amended end of session 33: **P6 HAS AN ENCODER AND A PRICE (FINDINGS 65).**
|
||||
`tools/encoder/adpcm.py` is an MSM6258 codec whose in-loop decoder is gated
|
||||
SAMPLE-EXACT against ffmpeg's `adpcm_ima_oki` — there is no reference encoder
|
||||
for this format, so that is the only check available and it is the one that
|
||||
catches an encoder agreeing with its own wrong decoder. The Singe window encodes
|
||||
to **78,125 B at 21.97 dB**, which is 7,812.5 B/s to the byte and 52's figure
|
||||
arriving from the other direction, and **normalising the disc's −13.4 dBFS level
|
||||
buys 0.00 dB**, so the level is not a lever. Two things came with it. **(1) The
|
||||
two published delta formulas disagree by at most 3 in 12-bit units and that is
|
||||
worth 25 dB** — encode with one and decode with the other and the SNR goes from
|
||||
21.97 dB to **−2.88 dB**, because ADPCM is recursive the way the codec is
|
||||
temporally recursive (64.1). Which one the chip runs is now **P6a** and it is a
|
||||
precondition on shipping any audio. **(2) The packed container's own best
|
||||
property is what makes audio cost.** 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 cadence 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 already has an index and variable records, pays
|
||||
**zero** padding. That is the first cost anyone has found for the packed
|
||||
branch's simplification, and 64's risk list predicted there would be one without
|
||||
knowing what.
|
||||
|
||||
**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
|
||||
@@ -664,10 +686,46 @@ charging audio the disk's 5. Both worries above resolve:
|
||||
7.8 was decimal kB being multiplied by 1024; 2.4% high, now derived from the
|
||||
sample rate in `buscost.ADPCM_BYTES_PER_S`.
|
||||
|
||||
**What is still open in P6 is everything except the bus:** extraction, encode,
|
||||
container interleave, and what a second stream does to `wire` — and therefore to
|
||||
`pipe - wire`, and therefore to 51.3's refill climb. That last one is the
|
||||
interaction to price next, and it is E2's question with a second consumer in it.
|
||||
~~**What is still open in P6 is everything except the bus:** extraction, encode,
|
||||
container interleave, and what a second stream does to `wire`.~~ **THREE OF THE
|
||||
FOUR ARE DONE, session 33 — FINDINGS 65.** `tools/encoder/extract_audio.py`
|
||||
takes the same seconds of the same stream the frames come from;
|
||||
`tools/encoder/adpcm.py` encodes them (**78,125 B, 21.97 dB**, gated against
|
||||
ffmpeg's decoder sample-exact by `tools/bench/verify_adpcm.py`);
|
||||
`tools/analysis/32_audio_wire.py` is the interleave and the wire. **The packed
|
||||
container's cadence is `F=11, A=14`** — 0.09% padding, 14,336 B held, 582.0 →
|
||||
**589.6 KB/s** — and the naive one-lump-per-record cadence would have wasted
|
||||
57.3% of every audio sector. **The codec container pays zero padding**, because
|
||||
it already has the index the packed one deleted (65.4).
|
||||
|
||||
**What is left in P6 is the fourth: the refill climb with a second consumer
|
||||
through a real branch point** (51.3, 55.4). The slack table is in
|
||||
`32_audio_wire.py` — at 582.0 KB/s exactly, silent breaks even and sounded
|
||||
starves — but a branch point has not been run with audio on the wire.
|
||||
|
||||
**P6a. WHICH DELTA FORMULA DOES THE MSM6258 RUN? (new, session 33, FINDINGS
|
||||
65.2 — and it is a precondition, not a refinement.)** ffmpeg computes
|
||||
`((2*(n&7)+1) * step) >> 3`; the OKI datasheet's form truncates per term. They
|
||||
differ by **at most 3 in 12-bit units per sample** and, because ADPCM is
|
||||
recursive, **encoding for one and decoding on the other costs 25 dB — the noise
|
||||
comes out louder than the signal.** No encoder output can be committed to a
|
||||
container before this is answered.
|
||||
|
||||
**It does not need a board.** MAME's x68000 HAS the chip: `:okim6258`, with the
|
||||
68000 reaching it at **`$E92001` and `$E92003`** — both read out of the machine's
|
||||
own program map by `tools/bench/probe_adpcm.lua`, not from folklore. What did
|
||||
NOT work is feeding it from Lua: `probe_adpcm2.lua` / `probe_adpcm3.lua` swept
|
||||
control 0..3 against PPI port C 0..15 and `-wavwrite` recorded silence
|
||||
throughout (65.5). The gap is the register semantics, and the way to close it is
|
||||
**from 68000 code with the IPL ROM's own channel-3 DMAC configuration**, which
|
||||
`21_iplrom_dmac.py` already reads out of the ROM — the one ADPCM path in this
|
||||
machine that is known-correct because Sharp wrote it. That is also the real
|
||||
design, so it is not scaffolding.
|
||||
|
||||
**P6b. A CONTAINER WITH SOUND IN IT.** DLXP1 has no audio section. 65.3 is the
|
||||
arithmetic a DLXP2 is built from and no byte of one is written. It waits on P6a,
|
||||
because a container full of audio encoded against the wrong formula is 25 dB of
|
||||
work to redo.
|
||||
|
||||
**E6. Container v2** — audio interleave, per-record index, scene table. Depends
|
||||
on P6's answer and on P5's index.
|
||||
@@ -794,6 +852,11 @@ B2 blanking ─┬─ NOT blanked ─> K3's DMAC-DIRECT player is the one: 54.9%
|
||||
(64.2). Neither answer kills the branch and each
|
||||
picks a different player.
|
||||
B1 BURST rate (NEW, 64.2) ──> which of the two K3/K4 wins, if B2 blanks
|
||||
|
||||
P6 audio DONE bar one (65): encoder gated, cadence F=11/A=14, 589.6 KB/s
|
||||
└─> P6a WHICH DELTA FORMULA? ─┬─ needs no board: MAME has :okim6258
|
||||
worth 25 dB, so it is a │ at $E92001/$E92003 (65.5)
|
||||
PRECONDITION not a tweak └─> then P6b, a container with sound in it
|
||||
```
|
||||
|
||||
**Read that top-left branch as the project's live question.** Everything else
|
||||
|
||||
+171
-1
@@ -1,4 +1,174 @@
|
||||
# Status & next-session handoff — end of session 32 (2026-08-25)
|
||||
# Status & next-session handoff — end of session 33 (2026-08-25)
|
||||
|
||||
## Session 33: audio gets an encoder, and the packed container's best property gets a bill
|
||||
|
||||
**Green light first and last: `./tools/bench/check.sh` was ALL GREEN before any
|
||||
of this (`tmp/check_s33_start.log`) and ALL GREEN after** — the same stages, plus
|
||||
one new one.
|
||||
|
||||
**FINDINGS 65. ROADMAP P6 is three-quarters done and has grown two sub-items.**
|
||||
`tools/encoder/adpcm.py`, `tools/encoder/extract_audio.py`,
|
||||
`tools/bench/verify_adpcm.py`, `tools/analysis/32_audio_wire.py`, and three
|
||||
throwaway MAME probes kept because their failure is a finding
|
||||
(`tools/bench/probe_adpcm*.lua`).
|
||||
|
||||
**Everything below is HOST ARITHMETIC plus one MAME introspection.** No board
|
||||
ran, and the one MAME *experiment* attempted did not work — 65.5 says so.
|
||||
|
||||
**0. SESSION 32'S WORK WAS UNCOMMITTED AND ITS STATUS BLOCK HAD NO HANDOFF.**
|
||||
Both fixed before anything new was written: the tree was re-gated ALL GREEN,
|
||||
the handoff written, and the whole of session 32 committed as one change.
|
||||
|
||||
**1. THE ENCODER, AND THE GATE IT NEEDED INSTEAD.** ffmpeg has a DECODER for
|
||||
this format (`adpcm_ima_oki`) and **no encoder**, so there is nothing to diff
|
||||
against. What is gated instead 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 window | 00223 @539.4 s, 10.000 s — **the same seconds as `tmp/fr_singe`** |
|
||||
| encoded | 156,250 samples → **78,125 B, SNR 21.97 dB** |
|
||||
| the rate | 78,125 B / 10 s = **7,812.5 B/s to the byte** — 52's figure, from the other end |
|
||||
| nibble order | **HIGH FIRST, measured** — low-first mismatches ffmpeg on 3,285 of 4,268, and that is the gate's negative control |
|
||||
| the level | disc peaks at **−13.4 dBFS**; normalising ×4.7 moves the SNR **21.97 → 21.97**. Not a lever |
|
||||
|
||||
**2. THE HEADLINE, AND IT IS A THREE-LSB DIFFERENCE THAT COSTS 25 dB.** The two
|
||||
published delta formulas — ffmpeg's `((2*(n&7)+1)*step)>>3` and the OKI
|
||||
datasheet's per-term truncation — differ **by at most 3 in 12-bit units**.
|
||||
Encode for one, decode on the other:
|
||||
|
||||
| encoded | decoded | SNR |
|
||||
|---|---|---:|
|
||||
| `shift` | `shift` | **21.97 dB** |
|
||||
| `shift` | `terms` | **−2.88 dB** |
|
||||
|
||||
**The noise comes out louder than the signal** (mean disagreement 78.2 against a
|
||||
source RMS of 74.4). **ADPCM is recursive**, so a rounding difference does not
|
||||
stay where it happens — the same property, one dimension down, that let 64.1's
|
||||
gate audit 120 frames by comparing one. **Which formula the chip runs is
|
||||
therefore a PRECONDITION on shipping any audio**, and it is ROADMAP P6a.
|
||||
|
||||
**3. AND THE PACKED CONTAINER'S BEST PROPERTY IS WHAT MAKES AUDIO COST.** A
|
||||
packed record is 97 sectors and its address is `LBA0 + i*97` because a literal
|
||||
frame's length is geometry — **no index, and none can be needed** (63, 64.1).
|
||||
Audio at 15,625 Hz is **651.0417 B a slot**, and the `.0417` is the same
|
||||
remainder the frame clock carries (54). Per-record audio makes records variable
|
||||
length, which needs an index, which ends the format. So audio rides a **fixed
|
||||
cadence** — every `F` frames, `A` whole sectors — and `(F, A)` is a rational
|
||||
approximation to 15625/12288 from above:
|
||||
|
||||
| F | A | lump | padding | wire adds | held |
|
||||
|---:|---:|---:|---:|---:|---:|
|
||||
| **1 — the obvious one** | 2 | 1,024 B | **57.29%** | 12.00 KB/s | 2,048 B |
|
||||
| **11 — the pick** | **14** | **7,168 B** | **0.09%** | **7.64 KB/s** | **14,336 B** |
|
||||
| 81 — the floor | 103 | 52,736 B | 0.003% | 7.63 KB/s | 105,472 B |
|
||||
|
||||
**Wire: 582.0 KB/s silent → 589.6 KB/s with sound (+1.31%).** F=11 buys 57.2
|
||||
points of padding for 12,288 B of RAM; the floor buys the last 0.09 of a point
|
||||
for 91,136 B more, and K4 already wants 99,328 B.
|
||||
|
||||
**4. THE CODEC CONTAINER PAYS NONE OF IT.** It already has an index and already
|
||||
has variable records, so it puts exactly 651.0417 B in record *i* and pads to
|
||||
the sector it was padding to anyway: **zero audio padding**, 440.4 → 448.1 KB/s.
|
||||
**That is the first price anyone has found for the packed branch's own
|
||||
simplification.** 64's risk list said a deletion that large usually hides
|
||||
something; this is the first thing it hid, and it is small — a cadence, a
|
||||
padding fraction and 14,336 B.
|
||||
|
||||
**5. THE EXPERIMENT THAT DID NOT WORK, and the facts it did leave.** MAME's
|
||||
x68000 **has the chip** — `:okim6258`, reached at **`$E92001` and `$E92003`**,
|
||||
both read out of the machine's own program map rather than from folklore, with
|
||||
the PPI at `$E9A000-$E9BFFF`. **Feeding it from Lua produced silence**: control
|
||||
0..3 against port C 0..15, `-wavwrite` capture **0 of 567,360 samples non-zero**.
|
||||
The gap is register semantics and it was **not guessed at further**. The way to
|
||||
close it is from 68000 code with the IPL ROM's own channel-3 configuration,
|
||||
which `21_iplrom_dmac.py` already reads out of the ROM.
|
||||
|
||||
**RISKS IN THIS SESSION'S RESULT, stated rather than left to be found:**
|
||||
|
||||
- **The encoder is greedy, not optimal.** Each nibble is chosen by exhaustive
|
||||
search over all sixteen minimising THIS sample's error; a nibble also moves
|
||||
the step index, so a locally worse choice can pay later. 21.97 dB is a floor
|
||||
for this format, not its ceiling, and no lookahead was tried.
|
||||
- **21.97 dB is against the 12-bit word**, not against the disc's 16-bit PCM,
|
||||
and the source was already resampled and downmixed to mono by ffmpeg. The
|
||||
downmix matrix is ffmpeg's default and was not chosen.
|
||||
- **P6a is open and it is worth 25 dB**, so every byte `adpcm.py` has produced
|
||||
is provisional.
|
||||
- **Nothing has played.** No audio has left an emulated machine, let alone a
|
||||
board.
|
||||
|
||||
## HANDOFF — start here
|
||||
|
||||
**THE TREE IS ALL GREEN**, session 33's stage included.
|
||||
|
||||
### The work, in the order it should be done
|
||||
|
||||
**1. P6a — WHICH DELTA FORMULA, and it needs no board.** It is worth **25 dB**
|
||||
(65.2), so every byte the encoder has produced is provisional until it is
|
||||
answered, and it is the cheapest open item in the project by a distance. MAME's
|
||||
x68000 has the chip and this session found where it lives — `:okim6258` at
|
||||
**`$E92001`/`$E92003`**, PPI at `$E9A000-$E9BFFF`, read out of the machine's own
|
||||
program map (65.5).
|
||||
|
||||
**Do NOT retry the Lua feed.** It was swept — control 0..3 × port C 0..15 —
|
||||
and recorded silence, and the gap is register semantics that nobody here should
|
||||
be guessing at. **Do it 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: dual address, 8-bit port, cycle steal without hold, external request.
|
||||
That is the one ADPCM path in this machine that is known-correct because Sharp
|
||||
wrote it, and it is also the real design — so it is not scaffolding, it is P6b's
|
||||
transport arriving early.
|
||||
|
||||
The discriminating stream is already worked out: **twelve loud nibbles to climb
|
||||
the step index, then every nibble in turn**, whose two reconstructions differ
|
||||
first at sample 3 (523 against 522). Capture with `-wavwrite` and compare against
|
||||
`adpcm.decode(nibs, 'shift')` and `adpcm.decode(nibs, 'terms')`. **Watch the
|
||||
clipping** — the sequence above saturates at 2,047 within six samples, which
|
||||
destroys discrimination; build one that alternates sign to hold the signal
|
||||
mid-range while the step index climbs.
|
||||
|
||||
**2. THEN P6b — DLXP2, a container with sound in it.** 65.3 is all the
|
||||
arithmetic: cadence **F=11, A=14**, `LBA(i) = LBA0 + i*97 + floor(i/11)*14`,
|
||||
14,336 B held, wire 589.6 KB/s. It waits on P6a, because a container full of
|
||||
audio encoded against the wrong formula is 25 dB of work to redo.
|
||||
|
||||
**3. WHAT IS LEFT OF P6 AFTER THAT** is the fourth quarter: 51.3's refill climb
|
||||
with a second consumer through a real branch point. The slack table is in
|
||||
`32_audio_wire.py`; nothing has been run.
|
||||
|
||||
### 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. If buffer mode does NOT blank, K3's player is already on screen the
|
||||
whole slot and K4's paint is 27.3% of a frame spent on nothing. **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), B2 (blanking — the five-minute half), B3 (`#EXREQ`),
|
||||
B4 (a byte write to a palette register).
|
||||
|
||||
### Risks that are OURS, not hardware
|
||||
|
||||
1. **P6a is open and it is worth 25 dB.** Everything in `adpcm.py`'s output is
|
||||
provisional.
|
||||
2. **The encoder is greedy.** Exhaustive per-sample search, no lookahead;
|
||||
21.97 dB is this format's floor here, not its ceiling.
|
||||
3. **Nothing has played.** No audio has left an emulated machine.
|
||||
4. **The packed branch's simplification has now cost something once** (65.4).
|
||||
It was predicted in the abstract and it was small. It may not be the only one.
|
||||
|
||||
### Reproducing this session
|
||||
|
||||
./tools/bench/check.sh # ALL GREEN
|
||||
python3 tools/encoder/extract_audio.py 00223 tmp/au_singe.raw 15625 539.4 10.0
|
||||
python3 tools/bench/verify_adpcm.py tmp/au_singe.raw
|
||||
python3 tools/analysis/32_audio_wire.py tmp/packed_singe.dlxp
|
||||
|
||||
**WHAT IS NEXT.** P6a: ask the chip which formula it runs, from 68000 code.
|
||||
|
||||
---
|
||||
|
||||
## Session 32: the packed player runs, and the write window turns out to be the frame
|
||||
|
||||
|
||||
Reference in New Issue
Block a user