Prev: F95A Up: Map Next: FA72
FA3A: Procedural drum-noise burst generator (variable duty-cycle square wave)
This is the title screen's synthesised drum sound, dispatched as selector 3 of the three 1-bit sample engines run from titlescr_music (the title music/SFX driver) -- not an in-game engine/tyre sound. Repeatedly reads and updates self-modifying state bytes at $FA72-$FA74 (a running counter/pitch value nudged each call) to derive the wave, toggling port $FE (speaker/border) through busy-wait delay loops (play_drum_noise_burst_1/play_drum_noise_burst_2) whose lengths are driven by that state. Loops D=$32 times per call, and E times overall (FA6C), polling the frame flag (wait_for_frame_flag's $F8A8) to bail out early if a new frame has started.
A (-> E) is the pitch/rate parameter from titlescr_music (the dispatch byte's upper 5 bits). Two nested loops: outer E times (FA6C/$FA6D), inner D = 50 times each (pen_inner_loop_tail/$FA65); each inner iteration advances the 3-byte self-modified state at $FA72-$FA74 (a phase counter, a wrapping accumulator subtracting a fixed constant, and a rotating byte mixed back into the accumulator) and tests bit 4 of the result to decide whether to emit a click this iteration -- a bit-4 test on a steadily-advancing counter behaves like a variable duty-cycle gate, the source of the drum's buzz/rattle. When a click fires, the ON delay is $18-E cycles and the OFF delay is E cycles (play_drum_noise_burst_1/play_drum_noise_burst_2): a larger E (higher dispatch parameter) shortens the ON wait but lengthens the OFF wait, lowering the effective pitch. After each inner-loop pass, the frame flag ($F8A8, set by frame_interrupt_handler) is checked; note that the "AND A" immediately before "RET C" always clears the carry flag, so that RET C can never actually fire -- an apparent dead check preserved as found in the original code, not a translation artifact. Once the outer loop completes, control falls into wait_for_frame_flag to wait for the next frame.
Used by the routine at titlescr_music.
Input
A The pitch/rate parameter (dispatch byte's upper 5 bits)
play_drum_noise_burst FA3A LD E,A E = pitch/rate parameter; outer loop counter
play_drum_noise_burst_0 FA3B LD D,$32 D = 50; inner loop counter
pen_inner_loop FA3D LD HL,$FA72 Advance the 3-byte phase/accumulator state
FA40 INC (HL)
FA41 INC (HL)
FA42 INC (HL)
FA43 LD B,(HL)
FA44 INC HL
FA45 LD A,(HL)
FA46 SUB $8D
FA48 LD (HL),A
FA49 ADD A,B
FA4A INC HL
FA4B RLCA
FA4C RRC (HL)
FA4E ADD A,(HL)
FA4F LD (HL),A
FA50 AND $10 Bit 4 of the result gates this iteration's click
FA52 JR Z,pen_inner_loop_tail
FA54 LD A,$18 ON delay = $18 - E
FA56 SUB E
FA57 LD B,A
play_drum_noise_burst_1 FA58 DJNZ play_drum_noise_burst_1
FA5A LD A,$18
FA5C OUT ($FE),A Speaker/border on
FA5E LD B,E OFF delay = E
play_drum_noise_burst_2 FA5F DJNZ play_drum_noise_burst_2
FA61 XOR A
FA62 OUT ($FE),A Speaker/border off
pen_inner_loop_tail FA64 DEC D
FA65 JR NZ,pen_inner_loop
FA67 LD A,($F8A8) Frame flag check (see comment above: RET C here
FA6A AND A never fires, AND A always clears carry)
FA6B RET C
FA6C DEC E Outer loop
FA6D JR NZ,play_drum_noise_burst_0
FA6F JP wait_for_frame_flag Done: wait for the next frame
Prev: F95A Up: Map Next: FA72