Take the player through a branch with sound, and find the predictor does not seek
FINDINGS 71, ROADMAP P6d. 70.3 named exactly what was missing -- packed.s starts PG_AK/PG_AKF at lump 0 and has no audio seek path -- and priced its absence at a mean 416.5 ms of silence over the arcade's 409 within-container seek targets. pg_aseek is that path: the lump index, the stream position, the remainder accumulator and the byte offset into the group, then the second READ(10) at the lump's own LBA and a re-arm part way into the buffer. Measured off a real volume: 132,162 B of spliced stream accounted for byte by byte in MAME's own capture, across a branch at frame 37 -- four frames into lump 3, deliberately NOT on a group boundary -- in both chip configurations. Skip computed 2,604 B, cadence says 2,604. THE PREDICTOR DOES NOT SEEK, AND THE ERROR IS DC. The MSM6258's accumulator is a pure integrator with no leakage term, so a branch that hands the chip bytes chosen for a state it is not in produces an offset that does not decay. Playing through: DC -355 of 511 with AC 0.00 -- the right shape from the wrong ground -- still -108 four seconds later. STOP and re-PLAY: all 62,500 post-seek samples are EXACTLY a decode from the container's own init, and the whole error is the single constant -65. A re-PLAY is 5.5x better and neither is zero, so PG_ARST is a mailbox with a number under it. The host computes -65 out of the container's bytes and the gate asserts the equality rather than printing both. AND THE ONLY FIX THAT REACHES ZERO IS THE ENCODER'S. A player cannot set the chip's accumulator, only reset it. Resetting the encoder's predictor every frame makes all 119 of the container's branch points exact for 0.33 dB (21.99 -> 21.66), because the step table's floor is a constant 16. That is a DLXP3 and it is deliberately not in tools/encoder. TWO SILENT BUGS, BOTH CAUGHT BY THE CAPTURE. pg_udiv32 trashes d4 and pg_aseek held hz there, so the offset came out 1 byte instead of 2,604 -- 166 ms of the wrong part of the scene at exactly the right rate, every counter agreeing. And one already in the tree that had passed this gate three times: pg_ainit waited on a READ-BACK MTC before PLAY, which is the same test as "a byte has left RAM" only if no byte leaves in between. One does, and the chip then plays the scene one byte in, forever. Found by locating the capture's opening samples in the container image: sector 1 + 1. The witness is now the count that was written. ALL GREEN, two new stages included. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -106,3 +106,26 @@ ad_play:
|
||||
ad_halt:
|
||||
move.b #AD_STOP,AD_CTRLR
|
||||
rts
|
||||
|
||||
; --------------------------------------------------------------- ad_abort
|
||||
; STOP channel 3 where it stands, and clear what stopping it posts.
|
||||
;
|
||||
; A SEEK IS THE ONLY THING IN THIS PLAYER THAT NEEDS THIS. Every other re-arm
|
||||
; happens at COC, where the channel has already counted itself out and there is
|
||||
; nothing to stop; a seek arrives MID-LUMP, because a branch is a frame index
|
||||
; and a frame does not know about the cadence (FINDINGS 70.3).
|
||||
;
|
||||
; SAB is CCR bit 4. CSR is then written $FF -- write-one-to-clear -- to take
|
||||
; down COC and ERR together, because the abort posts a channel error (CER $11)
|
||||
; and pg_aserv's whole test is "did the channel count out": a stale COC would
|
||||
; make the very next service call arm a lump that is already playing.
|
||||
;
|
||||
; NAME THE LAYER. MAME's hd63450 does not NEED the abort -- its
|
||||
; dma_transfer_start reloads MAR/MTC and restarts the timer whatever the channel
|
||||
; was doing. The MC68450 does: STR written to an active channel is an operation
|
||||
; timing error, and the transfer that is running is not the one that was asked
|
||||
; for. The abort is here for the silicon, and the run below cannot tell.
|
||||
ad_abort:
|
||||
move.b #$10,A3_CCR ; SAB -- software abort
|
||||
move.b #$FF,A3_CSR ; ...and the COC/ERR it posts
|
||||
rts
|
||||
|
||||
+334
-22
@@ -116,6 +116,26 @@ PG_APRE = $18974 ; lumps fetched before frame 0. A NUMBER and
|
||||
; not a constant: what the audio needs in hand
|
||||
; is set by how far the transport runs behind
|
||||
; real time, which is a property of the medium.
|
||||
PG_ARST = $1897C ; 1 = STOP and re-PLAY the chip at a branch,
|
||||
; 0 = leave it playing and re-arm underneath it.
|
||||
; A DESIGN CHOICE AND NOT A TIDINESS ONE: the
|
||||
; MSM6258's accumulator is a pure integrator
|
||||
; with no leak, so whatever state mismatch a
|
||||
; branch creates is a DC offset that DOES NOT
|
||||
; DECAY. PLAY resets the accumulator to the
|
||||
; container's own `init` and the step index to
|
||||
; 0, which is the state the encoder's first byte
|
||||
; was chosen for -- but not the state it chose
|
||||
; byte B(f) for. Neither is zero and the two
|
||||
; are 5.5x apart; FINDINGS 71.3 is the run.
|
||||
PG_SEEKF = $18978 ; THE BRANCH POINT. Passes after the first
|
||||
; start HERE rather than at frame 0, which is
|
||||
; what makes PG_ITER a SEEK rather than a
|
||||
; replay. On the video path that is the whole
|
||||
; of it -- a packed record is found by
|
||||
; arithmetic from the frame index, so seeking is
|
||||
; an assignment (ROADMAP K3). On the audio path
|
||||
; it is pg_aseek, and FINDINGS 70.3 is why.
|
||||
|
||||
; ---- outputs
|
||||
PG_SHOWN = $18930 ; frames displayed. Bumped AFTER bit 11 is
|
||||
@@ -178,6 +198,40 @@ PG_ABUSY = $189BC ; 1 = a lump fetch is in flight; the hook must
|
||||
; not re-enter the transport it is running under
|
||||
PG_ASLOTA = $189C0 ; slot the next ARM reads from
|
||||
PG_ASLOTF = $189C4 ; slot the next FETCH writes to
|
||||
; ---- and the four cells the SEEK adds (FINDINGS 70.3, 71).
|
||||
PG_APOS = $189C8 ; the STREAM POSITION in bytes -- what pg_apay
|
||||
; measures the last lump against. It is NOT
|
||||
; PG_ABYT and the difference is the seek: a
|
||||
; seek moves the position without handing the
|
||||
; chip anything, and a seek into the middle of a
|
||||
; lump advances the position by the whole lump
|
||||
; while the chip is fed only the tail of it.
|
||||
; One cell for two facts is how a counter that
|
||||
; is right becomes a stream that is wrong.
|
||||
PG_ASKIP = $189CC ; bytes to drop from the head of the NEXT arm,
|
||||
; because a branch lands `f mod F` frames into
|
||||
; its group. Consumed by the arm that honours
|
||||
; it -- a skip left set is a second lump
|
||||
; entered part way through for no reason.
|
||||
PG_ASKN = $189D0 ; audio seeks performed
|
||||
PG_ASKB = $189D4 ; ...and the bytes they skipped, summed. The
|
||||
; only witness in the player that the offset
|
||||
; arithmetic did anything: a seek path that
|
||||
; always landed on a group boundary would be
|
||||
; indistinguishable from one that had no offset
|
||||
; term at all, and 36 of the arcade's 409
|
||||
; targets DO land on one (70.3).
|
||||
PG_ASKA = $189D8 ; 1 = the next arm is a SEEK arm, so the MTC it
|
||||
; finds left over is not a dropped byte and must
|
||||
; not be charged to PG_ALATE
|
||||
PG_AMTCA = $189E0 ; the MTC the channel was last ARMED with. A
|
||||
; WITNESS THAT WAS WRITTEN RATHER THAN READ --
|
||||
; see pg_ainit, and it is worth a cell because
|
||||
; the read-back version of it silently ate byte
|
||||
; 0 of the scene.
|
||||
PG_TSEQ = $189DC ; frames played in the RUN, which after a seek
|
||||
; is not the frame index. The pace gate counts
|
||||
; ticks and a tick is a slot, not a picture.
|
||||
|
||||
; ---- the lump buffers. 65.3 charged 14,336 B for double-buffering the cadence
|
||||
; and FINDINGS 67.6 recorded that no player held it; this is where it is held.
|
||||
@@ -286,6 +340,13 @@ start:
|
||||
clr.l PG_ABUSY.l
|
||||
clr.l PG_ASLOTA.l
|
||||
clr.l PG_ASLOTF.l
|
||||
clr.l PG_APOS.l
|
||||
clr.l PG_ASKIP.l
|
||||
clr.l PG_ASKN.l
|
||||
clr.l PG_ASKB.l
|
||||
clr.l PG_ASKA.l
|
||||
clr.l PG_TSEQ.l
|
||||
clr.l PG_AMTCA.l
|
||||
|
||||
; ---- 1. the display. THE PLAYER OWNS IT, and that is a change from every
|
||||
; other rig in this tree: tools/bench/stream.lua and decode.lua call
|
||||
@@ -340,8 +401,8 @@ start:
|
||||
bne.s .it
|
||||
moveq #1,d6
|
||||
.it:
|
||||
moveq #0,d7 ; the FIRST pass starts at frame 0
|
||||
pg_pass:
|
||||
moveq #0,d7 ; d7 = frame index within the pass
|
||||
pg_loop:
|
||||
bsr pg_gate ; wait for tick d7
|
||||
; THE AUDIO IS SERVICED THREE TIMES A FRAME AND THAT IS NOT BELT AND BRACES.
|
||||
@@ -359,18 +420,31 @@ pg_loop:
|
||||
bsr pg_afill ; ...and fetch at most ONE lump, so the
|
||||
move.l (sp)+,d0 ; audio's share of the frame is bounded
|
||||
tst.l d0
|
||||
bmi.s pg_failed
|
||||
bmi pg_failed
|
||||
addq.l #1,d7
|
||||
addq.l #1,PG_TSEQ.l ; ...and a TICK, which after a seek is
|
||||
; not the same number as the frame
|
||||
move.l PG_NFR.l,d0
|
||||
cmp.l d0,d7
|
||||
bcs.s pg_loop
|
||||
; ---- a pass boundary. For this container a SEEK IS ARITHMETIC: there is no
|
||||
; ring to discard, no index to walk and no prefill to climb, so the next pass's
|
||||
; first record is simply LBA0 again. That is the whole of what K3 deletes, and
|
||||
; it is worth one line of code and four of comment because it is the largest
|
||||
; simplification in the project (ROADMAP K3) and it does not look like one.
|
||||
bcs pg_loop
|
||||
; ---- A BRANCH POINT. On the video path a seek IS ARITHMETIC and that is the
|
||||
; largest simplification in the project (ROADMAP K3): there is no ring to
|
||||
; discard, no index to walk and no prefill to climb, so the next pass's first
|
||||
; record is LBA0 + f*97 + (f//F)*A and the seek is the assignment below.
|
||||
;
|
||||
; THE AUDIO IS NOT ARITHMETIC AND THAT IS FINDINGS 70.3. A DLXP2 group is
|
||||
; `lump k, then F records`, so lump k lies at a LOWER address than every record
|
||||
; of its group but the first: read forward from record f and the next lump to
|
||||
; arrive is k+1, which carries frame (k+1)*F, and the frames from f to
|
||||
; (k+1)*F-1 have picture and no sound. On the arcade's own 409 within-container
|
||||
; seek targets that is a mean of 416.5 ms and a worst of 833.3. pg_aseek is the
|
||||
; second read that removes it.
|
||||
subq.l #1,d6
|
||||
bne.s pg_pass
|
||||
beq.s .fin
|
||||
move.l PG_SEEKF.l,d7 ; the video seek, entire
|
||||
bsr pg_aseek ; ...and the audio, which is not
|
||||
bra pg_pass
|
||||
.fin:
|
||||
|
||||
move.l CLK_VDISP.l,PG_VDISP.l
|
||||
bsr clk_stop
|
||||
@@ -392,27 +466,36 @@ pg_hold:
|
||||
; tick ALREADY past did not idle for a single poll, which means the previous
|
||||
; frame used its whole slot and then some -- that is the underrun this player
|
||||
; can have, and it is counted rather than absorbed.
|
||||
; AND IT COUNTS TICKS, NOT FRAMES. Until session 39 the comparison was against
|
||||
; d7 directly, which is the same number only while the run plays frame 0 first
|
||||
; and never goes back. A seek makes them different: pass 2 restarts at frame f
|
||||
; while the clock is at tick n, and a gate that compared f against n would call
|
||||
; every frame after a backwards branch LATE and every frame after a forwards one
|
||||
; free. PG_TSEQ is the slot count and a slot is a slot whatever picture is in
|
||||
; it.
|
||||
pg_gate:
|
||||
movem.l d0-d1,-(sp)
|
||||
tst.l PG_PACEON.l
|
||||
beq.s .out
|
||||
move.l PG_TSEQ.l,d1
|
||||
move.l CLK_PACE.l,d0
|
||||
cmp.l d7,d0
|
||||
bcs.s .wait ; PACE < d7: early, the common case
|
||||
tst.l d7
|
||||
beq.s .out ; frame 0 starts AT tick 0 by definition
|
||||
cmp.l d1,d0
|
||||
bcs.s .wait ; PACE < tick: early, the common case
|
||||
tst.l d1
|
||||
beq.s .out ; tick 0 starts AT tick 0 by definition
|
||||
tst.l PG_LATE.l
|
||||
bne.s .nf
|
||||
move.l d7,PG_LATE1.l
|
||||
move.l d7,PG_LATE1.l ; the FRAME, because that is what a
|
||||
; reader can look at in the container
|
||||
.nf: addq.l #1,PG_LATE.l
|
||||
sub.l d7,d0 ; whole ticks overrun
|
||||
sub.l d1,d0 ; whole ticks overrun
|
||||
cmp.l PG_LATEM.l,d0
|
||||
bls.s .out
|
||||
move.l d0,PG_LATEM.l
|
||||
bra.s .out
|
||||
.wait: addq.l #1,PG_GSPIN.l
|
||||
move.l CLK_PACE.l,d0
|
||||
cmp.l d7,d0
|
||||
cmp.l d1,d0
|
||||
bcs.s .wait
|
||||
.out: movem.l (sp)+,d0-d1
|
||||
rts
|
||||
@@ -725,11 +808,26 @@ pg_ainit:
|
||||
; ---- arm lump 0, then wait for the channel to actually take byte 0 before
|
||||
; PLAY. Not a delay loop: the condition is MTC having moved, which is the
|
||||
; channel's own account of a byte having left RAM.
|
||||
;
|
||||
; AND THE VALUE IT IS COMPARED AGAINST IS THE ONE THAT WAS WRITTEN, NOT ONE READ
|
||||
; BACK. Until session 39 this read MTC after the arm and waited for it to
|
||||
; differ from THAT -- which is the same test only if no byte leaves RAM between
|
||||
; the channel start and the read. One does, whenever #DRQ3's phase falls that
|
||||
; way: the read-back then already says 7,160, the loop waits for 7,159, and the
|
||||
; PLAY lands after the channel has fetched byte 1 ON TOP of byte 0. The chip
|
||||
; starts the scene one byte in.
|
||||
;
|
||||
; NOTHING IN THE PLAYER CAN SEE THAT. Every lump is armed, every byte is
|
||||
; delivered, the counters are exact and the sound is the whole stream shifted by
|
||||
; one byte -- 67.4's failure mode, on the one byte in the scene that a player
|
||||
; cannot check. It was found by locating the capture's opening samples in the
|
||||
; container image: sector 1 + 1. And it is a RACE, so it does not reproduce on
|
||||
; demand -- the tree passed this gate for three sessions with the bug in it,
|
||||
; because the arm happened to land in the other half of a 128 us window. What
|
||||
; makes it a bug rather than bad luck is that the witness was a read.
|
||||
bsr pg_aarm
|
||||
move.l #4000000,d3
|
||||
move.w A3_MTC,d0
|
||||
andi.l #$FFFF,d0
|
||||
move.l d0,d2 ; d2 = MTC as armed
|
||||
move.l PG_AMTCA.l,d2 ; d2 = MTC as the channel was ARMED
|
||||
.first: move.w A3_MTC,d0
|
||||
andi.l #$FFFF,d0
|
||||
cmp.l d2,d0
|
||||
@@ -785,6 +883,12 @@ pg_aserv:
|
||||
; Arm channel 3 on the lump in slot PG_ASLOTA for its PAYLOAD length, and
|
||||
; advance. Trashes d0-d2/a1, which pg_aserv and pg_ainit have saved.
|
||||
pg_aarm:
|
||||
tst.l PG_ASKA.l ; a SEEK arm. The previous lump was
|
||||
beq.s .prev ; abandoned on purpose and whatever MTC
|
||||
clr.l PG_ASKA.l ; it has left is not a dropped byte --
|
||||
bra.s .clean ; charging it to PG_ALATE would turn the
|
||||
.prev: ; assertion that this player never drops
|
||||
; a byte into a count of its own seeks
|
||||
tst.l PG_AARM.l
|
||||
beq.s .clean ; the FIRST arm has no previous lump and
|
||||
; MTC out of reset is not ours to read
|
||||
@@ -796,13 +900,37 @@ pg_aarm:
|
||||
bsr pg_apay ; d0 = this lump's payload
|
||||
tst.l d0
|
||||
beq.s .skip
|
||||
move.l d0,d1
|
||||
; ---- THE STREAM ADVANCES BY THE WHOLE LUMP AND THE CHIP MAY NOT. PG_APOS is
|
||||
; where the container's stream has got to, which is what pg_apay's tail limiter
|
||||
; measures against; PG_ABYT is what the chip was handed, which is what the
|
||||
; capture has to account for. They are equal until a seek lands part way into a
|
||||
; group, and keeping one cell for both is exactly the kind of bookkeeping this
|
||||
; format punishes silently: the last lump would come out long by the skip and
|
||||
; the player's own byte count would agree with it.
|
||||
add.l d0,PG_APOS.l
|
||||
move.l PG_ASKIP.l,d1
|
||||
beq.s .nosk
|
||||
clr.l PG_ASKIP.l ; consumed HERE and nowhere else
|
||||
cmp.l d0,d1
|
||||
bcc.s .skip ; the offset is inside the lump by
|
||||
; construction (f mod F < F). Refusing
|
||||
; rather than trusting is what makes
|
||||
; that a statement: MTC 0 is a 65,536
|
||||
; byte transfer on this part, not a
|
||||
; no-op, so an offset past the end would
|
||||
; play 64 KB of whatever follows.
|
||||
sub.l d1,d0
|
||||
.nosk:
|
||||
add.l d0,PG_ABYT.l
|
||||
move.l d0,d2 ; d2 = the MTC, while d0 builds the MAR
|
||||
move.l PG_ASLOTA.l,d0
|
||||
lsl.l #8,d0
|
||||
lsl.l #6,d0 ; slot * $4000
|
||||
add.l #PG_ABUF,d0
|
||||
add.l d1,d0 ; ...+ the seek's offset into the lump
|
||||
movea.l d0,a1
|
||||
move.l d2,d1
|
||||
move.l d1,PG_AMTCA.l ; ...and what we asked for, recorded
|
||||
bsr ad_arm ; d1 = MTC, a1 = MAR
|
||||
addq.l #1,PG_AARM.l
|
||||
.skip:
|
||||
@@ -846,8 +974,8 @@ pg_apay:
|
||||
; ---- and the LAST lump is short rather than padded: the stream ends where it
|
||||
; ends and the geometry would otherwise hand the chip whatever followed it.
|
||||
move.l PG_ABYTES.l,d1
|
||||
sub.l PG_ABYT.l,d1
|
||||
bcc.s .lim
|
||||
sub.l PG_APOS.l,d1 ; POSITION, not bytes fed -- a seek
|
||||
bcc.s .lim ; moves one and not the other
|
||||
moveq #0,d1
|
||||
.lim: cmp.l d1,d0
|
||||
bls.s .out
|
||||
@@ -855,6 +983,190 @@ pg_apay:
|
||||
.out: movem.l (sp)+,d1-d2
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- pg_aseek
|
||||
; TAKE THE AUDIO TO FRAME d7. FINDINGS 70.3, and this is the whole of what that
|
||||
; finding asked for.
|
||||
;
|
||||
; WHY THE VIDEO PATH NEEDS NOTHING AND THE AUDIO PATH NEEDS ALL OF IT. A packed
|
||||
; record is found by arithmetic from the frame index, so a video seek is the
|
||||
; assignment `d7 = f` -- ROADMAP K3's large simplification, and it survives
|
||||
; sound. Audio does not ride the frame; it rides a CADENCE. Lump k sits at a
|
||||
; LOWER address than every record of its group but the first, so a player that
|
||||
; carries on reading forward from record f picks up lump k+1, which carries
|
||||
; frame (k+1)*F: the frames from f to (k+1)*F-1 have picture and no sound.
|
||||
; Measured on the arcade's own 409 within-container seek targets at the shipped
|
||||
; F=11 that is a mean of 416.5 ms, a worst of 833.3, and only 36 of the 409 land
|
||||
; on a boundary and cost nothing. The fix is a SECOND READ -- lump k is up to
|
||||
; 503,808 B behind record f, so it cannot be picked up by reading early.
|
||||
;
|
||||
; THE STATE A SEEK HAS TO REBUILD IS FOUR NUMBERS AND NOT ONE:
|
||||
;
|
||||
; PG_AK / PG_AKF the lump index k = f/F, and the fetch that follows it
|
||||
; PG_APOS the stream POSITION, floor(k*F*hz/(2*fps)), which is what
|
||||
; pg_apay's tail limiter measures the last lump against
|
||||
; PG_AACC the remainder accumulator AT lump k -- the same division's
|
||||
; remainder. Seeding it wrong does not fail: it is a RATE,
|
||||
; and 67.2 is the finding about what a rate error costs
|
||||
; PG_ASKIP the byte offset of frame f inside lump k, because f is not
|
||||
; a multiple of F
|
||||
;
|
||||
; AND ONE IT CANNOT REBUILD: THE CHIP'S PREDICTOR. An MSM6258 has no seek. Its
|
||||
; accumulator and step index are the product of every nibble it has decoded
|
||||
; since PLAY, and a seek hands it bytes the encoder chose for a state it is not
|
||||
; in. That is not silence and it is not a wrong byte, so NOTHING IN THIS PLAYER
|
||||
; CAN SEE IT -- every counter below stays right. It is measured in the only
|
||||
; place it exists, which is the capture (tools/bench/verify_packed_audio.py
|
||||
; --seek), and priced in FINDINGS 71.
|
||||
;
|
||||
; ORDER: FETCH, THEN ABORT, THEN ARM. The other order is one line shorter and
|
||||
; it is 90 ms of held byte: the new lump is a READ(10) like any other and the
|
||||
; chip has no starvation state, so aborting first means the data register holds
|
||||
; one byte for the whole of the read. Fetching first leaves the OLD lump
|
||||
; playing across the branch -- the wrong content for a fraction of a slot, which
|
||||
; is a cut and not a stall. For the same reason the fetch goes into the slot
|
||||
; the ARM will read from and not into PG_ASLOTF: that slot is by construction
|
||||
; not the one channel 3 is reading out of, and writing into the one that is, is
|
||||
; the bug session 36 shipped (pg_afill).
|
||||
pg_aseek:
|
||||
movem.l d0-d5/a1,-(sp)
|
||||
tst.l PG_AUDON.l
|
||||
beq .out
|
||||
move.l PG_CADF.l,d1
|
||||
beq .out ; a silent container has no cadence to
|
||||
; enter off-boundary, which is F=1's
|
||||
; whole argument one limit further on
|
||||
; (70.4)
|
||||
addq.l #1,PG_ASKN.l
|
||||
|
||||
; ---- k = f/F, and r = f mod F, the frames into the group
|
||||
move.l d7,d0
|
||||
divu d1,d0
|
||||
move.l d0,d2
|
||||
clr.w d2
|
||||
swap d2 ; d2 = r (divu leaves the remainder in
|
||||
andi.l #$FFFF,d0 ; the high half -- the same shape
|
||||
move.l d0,d5 ; pg_frame's third term reads)
|
||||
|
||||
; ---- the stream position at the START of lump k, and the accumulator with it.
|
||||
; A 32-BIT QUOTIENT, and pg_udiv32 exists for this one call: at 12 fps and
|
||||
; 15,625 Hz floor(f*hz/(2*fps)) passes 65,535 after 101 seconds of scene, and
|
||||
; the 68000's `divu` does not FAIL on a quotient that does not fit -- it sets V,
|
||||
; leaves the destination alone and carries on. A seek past 101 s would have
|
||||
; returned the position of whatever the previous divide left in d0.
|
||||
mulu d1,d0 ; k*F -- the frames before the group
|
||||
move.l PG_AHZ.l,d4
|
||||
mulu d4,d0 ; ...times hz. 16x16 -> 32, and k*F is
|
||||
; a frame index: this design is one
|
||||
; container per SCENE and 65,535 frames
|
||||
; is 91 minutes at 12 fps, so the bound
|
||||
; on the multiply is the game
|
||||
move.l PG_AFPS.l,d1
|
||||
add.l d1,d1 ; 2*fps -- two samples to a byte
|
||||
bsr pg_udiv32 ; d0 = position, d3 = remainder
|
||||
move.l d0,PG_APOS.l
|
||||
move.l d3,PG_AACC.l
|
||||
|
||||
; ---- and the byte OFFSET of frame f inside lump k. Small on purpose, so the
|
||||
; 32-bit divide is needed once and not twice: acc < 2*fps and r < F, so
|
||||
; (acc + r*hz) is at most F*hz + 2*fps - 1 = the same 171,898 pg_apay bounds.
|
||||
move.l PG_AHZ.l,d4 ; RELOADED, because pg_udiv32 uses d4 as
|
||||
; its own work register and leaves the
|
||||
; remainder sitting in it. Carrying the
|
||||
; stale value gave skip = 1 where the
|
||||
; cadence says 2,604 -- which is not an
|
||||
; error and is not silence: it is 166 ms
|
||||
; of the wrong part of the scene, played
|
||||
; at exactly the right rate, with every
|
||||
; counter in this player agreeing with
|
||||
; it. The CAPTURE is what caught it,
|
||||
; which is 67.4's failure mode arriving
|
||||
; one more time.
|
||||
move.l d2,d0
|
||||
mulu d4,d0 ; r*hz
|
||||
add.l d3,d0 ; + the accumulator AT k, which is what
|
||||
divu d1,d0 ; makes this the exact byte and not a
|
||||
andi.l #$FFFF,d0 ; rounded one
|
||||
move.l d0,PG_ASKIP.l
|
||||
add.l d0,PG_ASKB.l
|
||||
|
||||
; ---- the second read. The old lump plays through it.
|
||||
move.l d5,PG_AK.l
|
||||
move.l d5,PG_AKF.l
|
||||
move.l PG_ASLOTA.l,PG_ASLOTF.l
|
||||
bsr pg_afetch
|
||||
tst.l PG_AFERR.l
|
||||
bne.s .out ; a failed lump fetch leaves the picture
|
||||
; perfect and the sound gone. Left
|
||||
; unarmed, pg_aserv finds the channel
|
||||
; counted out with nothing behind it and
|
||||
; counts a STARVE, which is the honest
|
||||
; report and is why the fetch has an
|
||||
; error word of its own
|
||||
|
||||
; ---- and only now stop the channel, so the seam is the arm and not the read.
|
||||
tst.l PG_ARST.l
|
||||
beq.s .noreset
|
||||
bsr ad_halt ; ...and the CHIP with it, if this run
|
||||
.noreset: ; is the one that resets the predictor
|
||||
bsr ad_abort
|
||||
move.l #1,PG_ASKA.l
|
||||
bsr pg_aarm
|
||||
|
||||
; ---- THE RE-PLAY, and its order is pg_ainit's for pg_ainit's reason. PLAY
|
||||
; resets the accumulator, the step index AND the nibble select, and the chip
|
||||
; begins decoding immediately out of whatever its data register holds -- so the
|
||||
; channel goes first and the CPU waits for the count it WROTE to move before the
|
||||
; PLAY. Get that wrong and the branch starts one byte in, which is the bug
|
||||
; session 39 found in pg_ainit and it does not announce itself.
|
||||
;
|
||||
; THE CHANNEL DOES NOT STALL WHILE THE CHIP IS STOPPED: on this machine #DRQ3
|
||||
; is the driver's own divider (x68k.cpp's adpcm_drq_tick, 29.6) and not a line
|
||||
; the chip drives out of its play state. That is a RIG fact and it is the one
|
||||
; thing here a real board could contradict -- an MSM6258V that stops requesting
|
||||
; when it is stopped would leave this wait spinning, and the bounded count below
|
||||
; is what turns that into a report rather than a hang.
|
||||
tst.l PG_ARST.l
|
||||
beq.s .out
|
||||
move.l #4000000,d3
|
||||
move.l PG_AMTCA.l,d2
|
||||
.rp: move.w A3_MTC,d0
|
||||
andi.l #$FFFF,d0
|
||||
cmp.l d2,d0
|
||||
bne.s .rgo
|
||||
subq.l #1,d3
|
||||
bne.s .rp
|
||||
.rgo: bsr ad_play
|
||||
.out: movem.l (sp)+,d0-d5/a1
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- pg_udiv32
|
||||
; d0 = d0 / d1.w with a 32-BIT QUOTIENT. Out: d0 = quotient, d3 = remainder.
|
||||
; TRASHES d4, and pg_aseek reloads hz out of RAM because of it. Divide the high half, carry its remainder into the low half --
|
||||
; the second quotient fits 16 bits because the remainder is less than the
|
||||
; divisor, so neither `divu` can overflow.
|
||||
;
|
||||
; EVERY OTHER DIVISION IN THIS PLAYER IS A BARE `divu` AND IS RIGHT TO BE.
|
||||
; pg_frame's f/F, pg_apay's acc/(2*fps) and pg_aseek's own f/F all have small
|
||||
; quotients by construction. This one does not, and the difference matters
|
||||
; because a `divu` overflow is silent: V set, destination untouched, execution
|
||||
; continuing with a stale answer.
|
||||
pg_udiv32:
|
||||
move.l d0,d4
|
||||
clr.w d4
|
||||
swap d4 ; d4 = the dividend's high word
|
||||
divu d1,d4 ; d4 = [remainder : quotient hi]
|
||||
moveq #0,d3
|
||||
move.w d4,d3 ; d3 = quotient hi
|
||||
move.w d0,d4 ; d4 = [remainder : dividend low]
|
||||
divu d1,d4 ; d4 = [remainder : quotient lo]
|
||||
swap d3
|
||||
move.w d4,d3 ; d3 = the 32-bit quotient
|
||||
clr.w d4
|
||||
swap d4 ; d4 = the remainder
|
||||
move.l d3,d0
|
||||
move.l d4,d3
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- pg_afill
|
||||
; At most ONE lump a frame, and only if a slot is free. One, because a lump is
|
||||
; A*512 = 7,168 B off the same disc the record came off and a frame that fetched
|
||||
|
||||
Reference in New Issue
Block a user