ROADMAP P6a, on the machine. 68000 code programs HD63450 channel 3 with the IPL ROM's own ADPCM bytes -- dual address, 8-bit port, cycle steal, external request -- and feeds the MSM6258 a designed 1,678-nibble stream at the chip's own pace: 839 B in 0.1074 s = 7,811.4 B/s against the format's 7,812.5, CER=$00. That transport is P6b's, not scaffolding. Sixteen candidate decoder models, three capture decimations and a searched prologue are fitted to MAME's capture. Exactly one reproduces it sample-exact over all 1,678 samples, and every axis carries a negative control: flip it alone and the closest survivor disagrees on 826, 1,504, 156 and 1,522 samples. The chip runs 'terms', takes the LOW nibble of a byte first, clamps the accumulator at 10 bits and starts it at -2. tools/encoder/adpcm.py defaulted to the opposite of all four, and 65.2 named the wrong axis as the risk: the delta formula is worth -2.88 dB and the NIBBLE ORDER is worth -25.74 dB. 65.1's "high first, measured" was a measurement of ffmpeg, i.e. of the VOX file convention, which is a different question from what a chip does with a byte in its data register. The 10-bit clamp is free on the Singe window and only because that window peaks at 435 of 511 -- 1.4 dB of headroom on a -13.4 dBFS passage, 12.1 dB below where the encoder was clamping, and inside the recursion. So the audio level is an open choice again, downward, and the loudest passage on the disc is unmeasured. Session 33's silence had two ordinary causes: the PPI's port C is an input until control word $92 says otherwise, and $01 is COMMAND_STOP. And a rig fact worth the space: the 8 MHz ADPCM clock is CT1 in the YM2151's $1B, delivered on the sound system's schedule rather than at the store, so a transfer started in the same breath as the setup plays its first ~17 ms at the old clock and no model fits a stream that changed rate part way through. Name the layer: this is MAME 0.277's okim6258 device model measured end to end through the machine's real transport. It settles the rig and not the silicon. Also struck: 64.4's "no MAME source tree is on this machine" -- there is none on disk, but the machine has network and the upstream tag fetches. check.sh ALL GREEN before (tmp/check_s34_start.log) and after (tmp/check_s34_end.log), with one new stage. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
89 lines
4.2 KiB
Lua
89 lines
4.2 KiB
Lua
-- Play one buffer of ADPCM nibbles on the emulated MSM6258V, from 68000 code
|
|
-- (ROADMAP P6a). The Lua here does what Lua is allowed to do in this tree:
|
|
-- push bytes in, start the CPU, read the mailbox out. It is NOT in the feed
|
|
-- path -- session 33's probe was, and a host that writes the data register at
|
|
-- host-frame rate is not feeding a chip that consumes at 15,625 Hz (65.5).
|
|
--
|
|
-- WHAT THE MEASUREMENT IS. MAME's -wavwrite capture, at a sample rate chosen
|
|
-- to EQUAL the chip's stream rate so nothing resamples it, is the chip's own
|
|
-- output. tools/bench/verify_adpcm_chip.py reads the four model axes out of it.
|
|
M = manager.machine
|
|
SP = M.devices[":maincpu"].spaces["program"]
|
|
|
|
local META = loadfile("adpcm_meta.lua")()
|
|
local AD_FLAG, AD_BUF, AD_LEN = 0x18600, 0x18604, 0x18608
|
|
local AD_MTC0, AD_CSRF, AD_CERF = 0x1860C, 0x18610, 0x18614
|
|
local AD_MTCF, AD_MARF, AD_SPIN, AD_STAT = 0x18618, 0x1861C, 0x18620, 0x18624
|
|
|
|
local code do local f=io.open("adpcmgate.bin","rb"); code=f:read("a"); f:close() end
|
|
local data do local f=io.open("adpcm_data.bin","rb"); data=f:read("a"); f:close() end
|
|
|
|
local function P(s) print("[ADP] "..s) end
|
|
local function T() local t=M.time; return t.seconds + t.attoseconds/1e18 end
|
|
|
|
local st, t0, tplay = "boot", nil, nil
|
|
SUB = emu.add_machine_frame_notifier(function()
|
|
local ok, err = pcall(function()
|
|
local t = T()
|
|
if st == "boot" then
|
|
if t < 3.0 then return end
|
|
for i = 1, #data do SP:write_u8(META.buf + i - 1, string.byte(data, i)) end
|
|
for i = 1, #code do SP:write_u8(0x10000 + i - 1, string.byte(code, i)) end
|
|
SP:write_u32(AD_FLAG, 0)
|
|
SP:write_u32(AD_BUF, META.buf)
|
|
SP:write_u32(AD_LEN, META.nbytes)
|
|
local cpu = M.devices[":maincpu"]
|
|
cpu.state["SR"].value = 0x2700 -- supervisor, ALL interrupts masked
|
|
cpu.state["SP"].value = 0x8000
|
|
cpu.state["PC"].value = 0x10000
|
|
P(string.format("pushed %d B of code and %d B of nibbles at 0x%X",
|
|
#code, META.nbytes, META.buf))
|
|
-- THE CAPTURE'S OWN CLOCK. The wav starts at t=0 of the run, so the host
|
|
-- has to know when PLAY happened to find the stream in it -- but it is
|
|
-- NOT used as the alignment: the verifier searches a small window around
|
|
-- it, because a host frame is 17.6 ms and a sample is 64 us.
|
|
st, t0 = "running", t
|
|
return
|
|
end
|
|
if st == "running" then
|
|
local fl = SP:read_u32(AD_FLAG)
|
|
if fl == 2 and not tplay then
|
|
tplay = t
|
|
P(string.format("PLAY at t=%.4f s, chip status $%02X (bit7 clear = playing), "
|
|
.."MTC then = %d of %d", t, SP:read_u32(AD_STAT),
|
|
SP:read_u32(AD_MTC0), META.nbytes))
|
|
end
|
|
if fl == 0xFF or fl == 0xEE then
|
|
P(string.format("channel finished: CSR=$%02X CER=$%02X MTC=%d MAR=$%06X "
|
|
.."spin=%d", SP:read_u32(AD_CSRF), SP:read_u32(AD_CERF),
|
|
SP:read_u32(AD_MTCF), SP:read_u32(AD_MARF),
|
|
SP:read_u32(AD_SPIN)))
|
|
local dt = t - (tplay or t)
|
|
P(string.format("%d bytes took %.4f s = %.1f B/s "
|
|
.."(15,625 nibbles/s wants 7,812.5)",
|
|
META.nbytes, dt, META.nbytes/dt))
|
|
if fl == 0xEE then P("ERROR: the gate flagged a channel error or a timeout") end
|
|
local f = io.open("adpcm_run.lua", "w")
|
|
f:write(string.format("return { tplay = %.9f, ok = %s, nbytes = %d,\n"
|
|
.." csr = %d, cer = %d, mtc = %d, spin = %d }\n",
|
|
tplay or -1, tostring(fl == 0xFF), META.nbytes,
|
|
SP:read_u32(AD_CSRF), SP:read_u32(AD_CERF),
|
|
SP:read_u32(AD_MTCF), SP:read_u32(AD_SPIN)))
|
|
f:close()
|
|
st = "drain"; t0 = t
|
|
return
|
|
end
|
|
if t - t0 > 60 then P("TIMEOUT flag="..string.format("%08X", fl)); M:exit() end
|
|
return
|
|
end
|
|
if st == "drain" then
|
|
-- let the capture run past the end of the stream, so a truncated wav is
|
|
-- never mistaken for a short stream
|
|
if t - t0 < 0.3 then return end
|
|
P("done")
|
|
M:exit()
|
|
end
|
|
end)
|
|
if not ok then print("[ADP] LUA ERROR: "..tostring(err)); M:exit() end
|
|
end)
|