Prev: F7D6 Up: Map Next: F8EB
F82F: Per-frame SFX/music service: drives the AY driver and the three sample players
Runs the AY music driver (ts_music_service), then dispatches the current sound effect's parameter byte (set up by load_drum_script/load_drum_op) to one of three 1-bit "digitised sample" bit-bang players: two fixed 8-row sample tables (F8F2, F95A, played via the shared loop at $F8CD which pulses port $FE from bitmap data clocked out with RLC (HL)/DJNZ) or the procedural routine at play_drum_noise_burst. $F8AD (installed as the IM2 handler by setup_im2_interrupt_table) just sets a "frame occurred" flag ($F8A8) consumed here and re-enables interrupts.
All the "LD A,$00" / "LD HL,$0000" instructions below are self-modified: the operand byte(s) immediately following each opcode double as a persistent state variable, written directly (not via the instruction) by this routine and by titlescr_start_tune/$F7DB/$F7FE. Three such state bytes are the SFX "busy" flags named at titlescr_start_tune: $F837 (SFX slot 1), $F895 (SFX slot 2), $F8A2 (1-bit sample playback active).
Slot 1 ($F836-$F894): if $F837 = 0 (idle), arms it (=1) and reloads the sample-selector-stream read pointer ($F853) from the reload source at $F85E (set by load_drum_script/$F7FE) before falling into the stream-reading loop at sfx_read_stream_byte. If already armed, $F842 (a countdown, also the selector byte copied by load_drum_op) is decremented; only when it reaches 1 does sfx1_reenter_stream re-enter the stream-reading loop -- otherwise slot 1 is skipped this frame and control falls to slot 2.
Stream-reading loop (sfx_read_stream_byte): reads a byte from the selector stream; a byte of exactly 1 calls titlescr_drum_advance (titlescr_drum_advance's own re-entry, throttled by its own $F7F5 countdown) to pull in a fresh selector byte and restarts the loop with the reload pointer. Any other byte is the entry to act on this frame: bit 7 marks it as also arming slot 2 ($F842 and $895 both set to 1, banked via AF' so it doesn't disturb the byte being decoded); the low 3 bits (1/2/3) select which of the three 1-bit-sample engines to trigger this frame (play_fixed_sample_1 = sample table F8F2, play_fixed_sample_2 = sample table F95A, or play_drum_noise_burst the procedural generator), with the byte's upper 5 bits stashed via $F8CE as a playback-rate/pitch parameter for the two fixed samples. A low-3-bits value of 0 triggers nothing and falls through to slot 2. Slot 2 ($F894-$F8A1): if $F895 is set, decrements both $F842 and $F895 (companion countdowns for whatever slot 1 armed via the bit-7 path above); purpose of the parallel countdown not established further. Tail ($F8A1-$F8A6): if $F8A2 (sample-playback-active) is exactly 1, falls into titlescr_music_1/play_sample_row to pulse out the next row of whichever fixed sample was armed above; otherwise returns without playing anything this frame. Used by the routines at insert_high_score_entry, name_entry_input, run_title_screen, play_success_music and run_title_tune.
titlescr_music F82F CALL ts_music_service Run the AY music driver for this frame
F832 XOR A Clear the "frame occurred" flag consumed by
F833 LD ($F8A8),A wait_for_frame_flag
sfx1_check_busy F836 LD A,$00 A = SFX slot 1 busy flag ($F837)
F838 AND A
F839 JR NZ,sfx1_tick_countdown Busy -> $F841 to tick the countdown
F83B INC A Idle -> arm slot 1 (busy = 1)
F83C LD ($F837),A
F83F JR sfx1_reload_pointer Reload the read pointer and enter the loop
sfx1_tick_countdown F841 LD A,$00 A = slot 1 countdown/selector byte ($F842)
F843 DEC A
F844 JP Z,sfx1_reenter_stream Reached 1 -> re-enter the stream-reading loop
F847 LD ($F842),A Else store the decremented countdown and skip
F84A JP sfx2_tick_countdown slot 1 entirely this frame
sfx1_reenter_stream F84D LD A,$00 Reset the countdown to 0 (about to be replaced by
F84F LD ($F842),A the next entry read from the stream)
F852 LD HL,$0000 HL = current stream read pointer ($F853)
sfx_read_stream_byte F855 LD A,(HL) A = next byte from the selector stream
F856 DEC A
F857 JP NZ,sfx_dispatch_entry Not "1" -> $F866, this is an entry to act on
F85A CALL titlescr_drum_advance "1" -> pull in a fresh selector byte
sfx1_reload_pointer F85D LD HL,$0000 Reload the stream pointer from the source set by
F860 LD ($F853),HL load_drum_script/$F7FE ($F85E) and retry
F863 JP sfx_read_stream_byte
sfx_dispatch_entry F866 INC HL Advance and persist the stream pointer
F867 LD ($F853),HL
F86A INC A Restore the original entry byte (undo the DEC)
F86B BIT 7,A Bit 7 set -> also arm slot 2
F86D JR Z,titlescr_music_0
F86F AND $7F Mask off bit 7 (banked via AF' so the low bits
F871 EX AF,AF' used for dispatch below are undisturbed)
F872 LD A,$01
F874 LD ($F842),A Arm slot 1's countdown/selector and slot 2's busy
F877 LD ($F895),A flag together
F87A EX AF,AF'
titlescr_music_0 F87B LD D,A D = entry byte (masked if bit 7 was set)
F87C AND $07 Low 3 bits select which 1-bit-sample engine
F87E JR Z,sfx2_tick_countdown 0 -> nothing to trigger this frame
F880 LD B,A
F881 LD A,D A = upper 5 bits: playback-rate/pitch parameter
F882 SRL A for the two fixed samples (stashed via $F8CE at
F884 SRL A play_fixed_sample_start)
F886 SRL A
F888 DEC B
F889 JP Z,play_fixed_sample_1 Selector 1 -> fixed sample table F8F2
F88C DEC B
F88D JP Z,play_fixed_sample_2 Selector 2 -> fixed sample table F95A
F890 DEC B
F891 JP Z,play_drum_noise_burst Selector 3 -> procedural drum noise
sfx2_tick_countdown F894 LD A,$00 A = SFX slot 2 busy flag ($F895)
F896 AND A
F897 JR Z,sfx_tail_check_active Idle -> nothing to tick
F899 LD HL,$F842 Busy -> decrement both companion countdowns
F89C DEC (HL)
F89D LD HL,$F895
F8A0 DEC (HL)
sfx_tail_check_active F8A1 LD A,$00 A = 1-bit sample playback active flag ($F8A2)
F8A3 DEC A
F8A4 JP Z,titlescr_music_1 Active -> play the next row of the armed sample
This entry point is used by the routines at finish_sample_playback and play_drum_noise_burst.
wait_for_frame_flag F8A7 LD A,$00
F8A9 AND A
F8AA JR Z,wait_for_frame_flag
F8AC RET
IM2 interrupt handler (installed by setup_im2_interrupt_table): just sets the "frame occurred" flag ($F8A8) polled by wait_for_frame_flag and re-enables interrupts. The actual per-frame work happens synchronously from the main loop at psm_loop, not here.
frame_interrupt_handler F8AD PUSH AF
F8AE LD A,$FF
F8B0 LD ($F8A8),A
F8B3 POP AF
F8B4 EI
F8B5 RET
play_fixed_sample_1 F8B6 LD HL,$F8F2
F8B9 LD D,$68
F8BB JR play_fixed_sample_start
play_fixed_sample_2 F8BD LD HL,$F95A
F8C0 LD D,$E0
play_fixed_sample_start F8C2 LD ($F8CE),A
F8C5 LD A,$01
F8C7 LD ($F8A2),A
F8CA JR play_sample_row
titlescr_music_1 F8CC EXX
1-bit sample bit-bang loop: pulses port $FE (border/speaker) from bitmap data at HL, one row of 8 bits per iteration, RLC (HL) rotating the next bit into carry-adjacent position; D counts rows remaining.
play_sample_row F8CD LD B,$08
titlescr_music_2 F8CF LD A,$10
F8D1 NOP
F8D2 BIT 7,(HL)
F8D4 JR NZ,titlescr_music_3
F8D6 RES 4,A
titlescr_music_3 F8D8 OUT ($FE),A
F8DA RLC (HL)
F8DC DJNZ titlescr_music_2
F8DE INC HL
F8DF DEC D
F8E0 JR Z,finish_sample_playback
F8E2 LD A,($F8A8)
F8E5 AND A
F8E6 JP Z,play_sample_row
F8E9 EXX
F8EA RET
Prev: F7D6 Up: Map Next: F8EB