Prev: EC01 Up: Map Next: EFAF
EC71: Per-frame music service: advances tracker patterns and refreshes the AY registers
If no tune is active, does nothing (falls straight through to tms_output_registers which returns immediately). Otherwise: decrements the tempo counter ($EC70) and, once it reaches zero, re-processes all 3 channel-tracker records (advance_channel_pattern) one tracker "row" further and resets the counter; then (every frame, tick or not) recomputes the AY tone-period/volume register values for all 3 channels from their current tracker state (compute_channel_ay_registers) into the register cache at EFAF-$EFB9; finally outputs the full cached register block ($EFBA onward, registers 11 down to 0) to the AY chip via ports $FFFD/$BFFD.
Used by the routine at titlescr_music.
ts_music_service EC71 LD A,($F223) Tune-active flag
EC74 AND A
EC75 JP Z,tms_output_registers
EC78 LD A,$00 Clear a driver-internal flag (consumed elsewhere
EC7A LD ($ECC6),A in the pattern processing, not traced here)
EC7D LD HL,$EC70 Decrement the tempo counter; only re-process the
EC80 DEC (HL) tracker patterns when it reaches zero
EC81 JR NZ,tms_refresh_registers
EC83 LD B,(HL) B = 0 (post-decrement)
EC84 LD IX,$EC01 Advance channel 1's pattern by one tracker row
EC88 CALL advance_channel_pattern
EC8B LD IX,$EC26 Channel 2
EC8F CALL advance_channel_pattern
EC92 LD IX,$EC4B Channel 3
EC96 CALL advance_channel_pattern
Self modified by EBB3 (start_tune) and EE71 (in-song tempo command); the operand byte at $EC9A is tune_tempo. Confirmed against a real-hardware trace: tune 0's tempo byte (2) gives a period-2 fire/skip alternation.
EC99 LD A,$01 A = <self modified> -- reset the tempo counter to this tune's tempo
EC9B LD ($EC70),A Store the new tempo counter
tms_refresh_registers EC9E LD IX,$EC01 Recompute the AY register values for all 3
ECA2 CALL compute_channel_ay_registers channels from their current tracker state
ECA5 LD ($EFAF),HL
ECA8 LD ($EFB7),A
ECAB LD IX,$EC26
ECAF CALL compute_channel_ay_registers
ECB2 LD ($EFB1),HL
ECB5 LD ($EFB8),A
ECB8 LD IX,$EC4B
ECBC CALL compute_channel_ay_registers
ECBF LD ($EFB3),HL
ECC2 LD ($EFB9),A
ECC5 LD A,$00
ECC7 LD ($EFB5),A Clear the cached mixer/noise register byte
tms_output_registers ECCA LD A,($F223) Re-check the tune-active flag: if no tune is
ECCD AND A playing, do nothing further (registers are only
ECCE RET Z ever written while a tune is active)
ECCF LD HL,$EFBA Output the cached register block: 12 registers
ECD2 LD A,$0B (11 downto 0), each selected via port $FFFD then
ECD4 LD C,$FD its data byte written via port $BFFD
tms_output_loop ECD6 LD B,$FF
ECD8 OUT (C),A Select AY register A
ECDA LD B,$BF
ECDC OUTD Write (HL) to the data port, HL--
ECDE DEC A Next (lower) register
ECDF JP P,tms_output_loop
ECE2 RET
Helper: write a single AY register (D = register number, E = value). Not directly referenced elsewhere in this bank -- may be dead code or called from another bank.
ECE3 LD BC,$FFFD
ECE6 OUT (C),D
ECE8 LD B,$BF
ECEA OUT (C),E
ECEC RET
Silence the AY chip: write 0 to every register 13 downto 0 (in particular the 3 volume registers 8-10), then write register 7 (mixer) a second time for good measure.
ay_silence_all ECED LD C,$FD
ECEF LD DE,$0D00 D = register 13 (downto 0), E = 0 (dummy)
ECF2 SUB A A = 0 (the value written to every register)
asa_reg_loop ECF3 LD B,$FF
ECF5 OUT (C),D Select register D
ECF7 LD B,$BF
ECF9 OUT (C),A Write 0
ECFB DEC D
ECFC JP P,asa_reg_loop
ECFF LD DE,$0700 Belt-and-braces: rewrite register 7 (mixer) = 0
ED02 LD B,$FF
ED04 OUT (C),D
ED06 LD B,$BF
ED08 OUT (C),E
ED0A RET
Stop any playing tune and silence the AY chip. This entry point is used by the routines at name_entry_input, run_title_screen, titlescr_start_tune and run_title_tune.
stop_music_and_silence ED0B XOR A
ED0C LD ($F223),A Clear the tune-active flag
ED0F CALL ay_silence_all
ED12 XOR A Clear the per-channel mixer/noise register cache
ED13 LD ($EFB7),A
ED16 LD ($EFB8),A
ED19 LD ($EFB9),A
ED1C RET
Not traced (structural pass) -- nothing in this bank jumps or calls into $ED1D-$ED32; the instruction stream here does not obviously cohere (an SBC/ADD/ADC/SUB chain feeding register loads with no clear purpose before falling into a POP HL/JP). Likely either dead code or a misdisassembled data table. Ruled out as related to advance_channel_phrase: that routine is a self-contained phrase-pointer table walker, fully traced, and does not fall through from or into this range.
ED1D SBC A,A
ED1E ADD A,A
ED1F ADC A,E
ED20 SUB B
ED21 LD C,(HL)
ED22 LD A,L
ED23 LD A,B
ED24 RRCA
ED25 LD H,A
ED26 LD E,A
ED27 INC H
ED28 LD C,$36
ED2A ADC A,H
ED2B INC B
ED2C SBC A,C
ED2D SBC A,(HL)
ED2E AND E
ED2F POP HL
ED30 JP stop_music_and_silence
ED33 JP advance_channel_phrase
Pattern command/effect opcode handlers ($ED36-$EDD1), reached only via the computed jump at dispatch_pattern_command (never by a direct CALL/JP visible to the disassembler, hence no entry-point markers below). Each handler consumes a fixed number of further bytes from the pattern stream (DE) and stores them into fields of the current channel's 37-byte tracker record (see EC01), then rejoins the main interpreter at acp_read_pattern_byte or acp_reset_row_counter. Traced mechanically below; the AY-level meaning of $EF7A and +$1D/+$1F is inferred (marked "likely"), not confirmed against a working build. Handlers 1-3 all bit-merge P = (IX+$24) into the shared byte $EF7A using the "A XOR M; A AND mask; A XOR M" replace-bits-under-mask idiom; $EF7A is likely a per-channel AY mixer (tone/noise enable) bit cache, given $EEA0 onward outputs a mixer byte to the AY.
pcmd_set_mixer_bits_low3 ED36 LD A,(IX+$24) Merge (P AND $07) into $EF7A under mask P; likely
ED39 LD C,A sets this channel's tone-enable bit(s)
ED3A AND $07
ED3C LD HL,$EF7A
ED3F XOR (HL)
ED40 AND C
ED41 XOR (HL)
ED42 LD (HL),A
ED43 LD A,$01 Set +$1E (slide/portamento-active flag) = 1
ED45 LD (IX+$1E),A
ED48 JP acp_read_pattern_byte
pcmd_set_mixer_bits_high3 ED4B LD A,(IX+$24) Merge (P AND $38) into $EF7A under mask P; likely
ED4E LD C,A sets this channel's noise-enable bit(s)
ED4F AND $38
ED51 LD HL,$EF7A
ED54 XOR (HL)
ED55 AND C
ED56 XOR (HL)
ED57 LD (HL),A
ED58 XOR A Clear +$1E (slide/portamento-active flag) = 0
ED59 LD (IX+$1E),A
ED5C JP acp_read_pattern_byte
pcmd_clear_mixer_bits ED5F LD HL,$EF7A Clear the bits of $EF7A that are set in P (AND
ED62 LD A,(IX+$24) with the complement of P)
ED65 CPL
ED66 AND (HL)
ED67 LD (HL),A
ED68 LD A,$01 Set +$1E (slide/portamento-active flag) = 1
ED6A LD (IX+$1E),A
ED6D JR acp_read_pattern_byte
pcmd_set_slide_target ED6F LD A,(DE) Read 2 operand bytes: clear +$07/+$08 (the
ED70 INC DE per-frame X/Y-style step accumulators used
ED71 LD (IX+$07),B elsewhere for object motion, reused here as
ED74 LD (IX+$08),B slide step accumulators), store operand 1 into
ED77 LD (IX+$0D),A +$0D (slide target) and operand 2 into +$0E
ED7A SET 2,(IX+$00) (slide speed), then set status bit 2 (slide
ED7E LD A,(DE) active)
ED7F LD (IX+$0E),A
ED82 INC DE
ED83 JR acp_read_pattern_byte
pcmd_set_driver_flag ED85 LD A,(DE) Read 1 operand byte and store it to the
ED86 INC DE driver-internal flag at $EED1 (cleared to 0 by
ED87 LD ($EED1),A start_tune on tune start)
ED8A JR acp_read_pattern_byte
pcmd_set_envelope_params ED8C LD A,(DE) Read 2 operand bytes: operand 1 -> +$1B, operand
ED8D LD (IX+$1B),A 2 -> both +$1A and +$1C (likely an envelope/pitch
ED90 INC DE curve's current and target value sharing one
ED91 LD A,(DE) initial value)
ED92 LD (IX+$1A),A
ED95 INC DE
ED96 LD (IX+$1C),A
ED99 JR acp_read_pattern_byte
pcmd_set_status_bits_3_7 ED9B SET 7,(IX+$00) Set status bits 7 and 3 of +$00 (no operand
ED9F SET 3,(IX+$00) bytes); likely a note-release/end-of-note marker
EDA3 JR acp_read_pattern_byte
pcmd_vibrato_off EDA5 LD (IX+$1D),B +$1D = 0 (B is 0 here): clears bit 6 (vibrato
EDA8 JR acp_read_pattern_byte enable, tested at EEFF) and bit 7/5 (mode/ direction, tested at EF09/ts_music_service_8)
pcmd_vibrato_on EDAA LD (IX+$1D),$40 +$1D = $40: sets bit 6 only -- enables vibrato
EDAE JR acp_read_pattern_byte (see EEFC phase 3), direction/mode bits clear
pcmd_vibrato_on_mode2 EDB0 LD (IX+$1D),$C0 +$1D = $C0: sets bits 6 and 7 -- enables vibrato in the alternate mode selected at EF09
EDB4 JR acp_read_pattern_byte
pcmd_set_status_bit1 EDB6 SET 1,(IX+$00) Set status bit 1 of +$00 (no operand bytes)
EDBA JR acp_read_pattern_byte
pcmd_reset_row_counter_clear13 EDBC LD (IX+$13),B +$13 = 0 (B is 0 here), clear status bit 5, then
EDBF RES 5,(IX+$00) rejoin at acp_reset_row_counter (acp_reset_row_counter) rather
EDC3 JR acp_reset_row_counter than acp_read_pattern_byte -- likely a pattern-loop/jump effect
pcmd_reset_row_counter EDC5 RES 5,(IX+$00) Clear status bit 5 only, then rejoin at acp_reset_row_counter
EDC9 JR acp_reset_row_counter -- likely the no-parameter variant of the above
pcmd_mute_channel EDCB LD (IX+$1F),$FF +$1F = $FF; likely mutes this channel
EDCF JR acp_read_pattern_byte
pcmd_unmute_channel EDD1 LD (IX+$1F),B +$1F = 0 (B is 0 here); likely unmutes this
EDD4 JR acp_read_pattern_byte channel
Advance one channel's pattern by one tracker row. Decrements the per-row wait counter (+$10); while it is still counting down, only channel_slide_upkeep (envelope/portamento upkeep) runs. Once it reaches zero, reads and processes the next byte(s) from the pattern stream via acp_read_pattern_byte.
advance_channel_pattern EDD6 DEC (IX+$10)
EDD9 JR NZ,channel_slide_upkeep
EDDB LD (IX+$00),B
EDDE LD E,(IX+$01)
EDE1 LD D,(IX+$02)
This entry point is used by the routine at advance_channel_phrase.
acp_read_pattern_byte EDE4 LD A,(DE)
EDE5 INC DE
EDE6 AND A
EDE7 JP M,decode_pattern_command Bit 7 set = command/effect byte, decode via decode_pattern_command; bit 7 clear = a note value, handled below
EDEA ADD A,(IX+$20)
EDED LD (IX+$12),A
EDF0 BIT 0,(IX+$1E)
EDF4 JR Z,ts_music_service_0
EDF6 LD ($EC79),A
ts_music_service_0 EDF9 LD L,(IX+$09)
EDFC LD H,(IX+$0A)
EDFF LD (IX+$0B),L
EE02 LD (IX+$0C),H
EE05 LD L,(IX+$14)
EE08 LD (IX+$16),L
EE0B LD H,(IX+$15)
EE0E LD (IX+$17),H
EE11 LD A,(HL)
EE12 LD (IX+$18),A
EE15 LD (IX+$13),A
EE18 LD A,(IX+$0F)
EE1B LD (IX+$19),A
EE1E SET 5,(IX+$00)
acp_reset_row_counter EE22 LD A,(IX+$11)
EE25 LD (IX+$10),A
EE28 LD (IX+$02),D
EE2B LD (IX+$01),E
EE2E LD A,(IX+$1F)
EE31 AND A
EE32 RET Z
EE33 LD (IX+$1F),$FF
EE37 RET
Between pattern rows: nudge the current note (+$12) up or down by one per frame if a portamento/slide effect (channel flag bit 3) is active.
channel_slide_upkeep EE38 LD A,(IX+$00)
EE3B BIT 3,A
EE3D RET Z
EE3E RLA
EE3F JR NC,ts_music_service_1
EE41 INC (IX+$12)
EE44 RET
ts_music_service_1 EE45 DEC (IX+$12)
EE48 RET
Command/effect byte decode: range-tests the byte against a cascade of thresholds ($B0, then +$20, +$10, +$18) to find which opcode group it falls in, then either handles it directly (note-table/pointer effects) or falls through to the jump table at dispatch_pattern_command for the fixed-length opcode handlers at pcmd_set_mixer_bits_low3-$EDD1.
decode_pattern_command EE49 CP $B0
EE4B JR C,dispatch_pattern_command
EE4D ADD A,$20
EE4F JR C,ts_music_service_3
EE51 ADD A,$10
EE53 JR C,ts_music_service_4
EE55 ADD A,$18
EE57 JR NC,ts_music_service_2
EE59 LD C,A
EE5A LD HL,$F07C
EE5D ADD HL,BC
EE5E LD C,(HL)
EE5F ADD HL,BC
EE60 LD (IX+$0B),L
EE63 LD (IX+$09),L
EE66 LD (IX+$0C),H
EE69 LD (IX+$0A),H
EE6C JP acp_read_pattern_byte
ts_music_service_2 EE6F ADD A,$09
In-song tempo command: self modifies 'LD A,x' at EC99 to set the tempo-counter reload value
EE71 LD ($EC9A),A Self modify 'LD A,x' at EC99
EE74 JP acp_read_pattern_byte
ts_music_service_3 EE77 INC A
EE78 LD (IX+$11),A
EE7B JP acp_read_pattern_byte
ts_music_service_4 EE7E LD HL,$F123
EE81 ADD A,A
EE82 LD C,A
EE83 ADD HL,BC
EE84 LD A,(HL)
EE85 INC HL
EE86 LD H,(HL)
EE87 LD L,A
EE88 LD (IX+$14),L
EE8B LD (IX+$15),H
EE8E DEC HL
EE8F LD A,(HL)
EE90 LD (IX+$0F),A
EE93 JP acp_read_pattern_byte
Jump-table dispatch into the fixed-length opcode handlers at pcmd_set_mixer_bits_low3-$EDD1, indexed by the remaining low bits of the command byte (A, with B assumed 0 as throughout this driver). Rather than storing a full 2-byte address per entry, the table at $EC9D stores a 1-byte displacement from the table slot itself to the handler: HL is first set to the table slot ($EC9D + A), then the displacement byte at that slot is added back onto HL, giving the handler's address. This works because every handler lies within 256 bytes of its table slot.
dispatch_pattern_command EE96 LD HL,$EC9D HL = table slot for this command
EE99 LD C,A
EE9A ADD HL,BC
EE9B LD C,(HL) C = displacement to the handler
EE9C ADD HL,BC HL = table slot + displacement = handler address
EE9D JP (HL)
Recompute this channel's AY tone-period and volume/envelope register values from its current tracker state (note, envelope index, effect flags) for the per-frame register cache write at ts_music_service. Returns the tone period in HL and the volume/mixer byte in A (see the callers at ECA2/$ECAF/$ECBC). Five phases, in order: 1. EEA1-$EED0: envelope amplitude advance. If status bit 5 (+$00) is set, decrements the envelope-step counter (+$19); on underflow, reloads it from the speed value (+$0F) and reads the next byte from the envelope-shape table pointer (+$16/+$17, advanced by 1 unless the byte read has bit 7 set, an end-of-table marker that halts the pointer), storing that byte as the current amplitude (+$18). Either way, +$18 is copied into +$13, the volume value this call returns. 2. ts_music_service_6-$EEFC: tone-period lookup. Note index = (+$12) [set by EDEA from the pattern's raw note + transpose (+$20)] plus a pitch-offset byte read from a per-channel offset sequence at +$0B/+$0C (default/loop start +$09/+$0A): if the byte's bit 7 is clear it is used as-is and the pointer advances; if set, it is a 7-bit end-of-sequence delta and the pointer resets to +$09/+$0A for next time. (note + offset) indexes the 2-byte-per-note tone-period table at EFBC, giving the base tone period in DE. 3. EEFC-$EF4B: vibrato. Only runs if +$1D bit 6 is set (skips to phase 4 otherwise). +$1C is a triangle-wave phase counter that ping-pongs between 0 and (+$1A)*2 by +/-(+$1B) each call, direction tracked in +$1D bit 5 (bit 7 and status bit 0 gate whether the phase updates this call at all, at EF09-$EF12 -- not fully resolved). The phase is centred (-depth..+depth), sign-extended, then left-shifted a note-dependent number of times (the $EF3D-$EF47 loop, incrementing a counter seeded from the note index by $18 until it overflows) to scale a linear vibrato depth into the tone-period domain, where pitch and period are inversely related. The scaled offset is added onto the phase-2 tone period. 4. ts_music_service_15-$EF76: portamento/slide. Status bit 0 (+$00) is unconditionally toggled every call. If status bit 2 was set (slide active, pcmd_set_slide_target), a countdown in +$0E ticks down; when it reaches zero it reloads and the signed step (+$0D) is sign-extended into BC and accumulated into +$07/+$08, which is then added onto the tone period. 5. ts_music_service_18-$EFAE: mixer/volume finalisation. Every 4th call (via the bit-0 toggle from phase 4), XORs bit 3 of a global byte ($EC79) into $ECC6 (purpose not established elsewhere in this bank). Merges this channel's tone-enable bits into the shared mixer cache at $EFB6 using the same replace-bits-under-mask idiom as pcmd_set_mixer_bits_low3 (mask = +$24). If +$1F was just set to $FF (pcmd_mute_channel, "mute"), handles the one-shot mute transition: clears bit 7 of +$1F (so this only fires once), clears this channel's noise-enable bits in the $EFB6 cache, and forces $ECC6 to $41. Returns the phase-3/4 tone period in HL and the phase-1 volume (+$13) in A.
compute_channel_ay_registers EE9E LD C,(IX+$00)
EEA1 BIT 5,C
EEA3 JR Z,ts_music_service_6
EEA5 LD A,(IX+$19)
EEA8 SUB $01
EEAA LD (IX+$19),A
EEAD JR NC,ts_music_service_5
EEAF LD A,(IX+$0F)
EEB2 LD (IX+$19),A
EEB5 LD L,(IX+$16)
EEB8 LD H,(IX+$17)
EEBB INC HL
EEBC LD A,(HL)
EEBD AND A
EEBE JP M,ts_music_service_5
EEC1 LD (IX+$16),L
EEC4 LD (IX+$17),H
EEC7 LD (IX+$18),A
ts_music_service_5 EECA LD A,(IX+$18)
EECD LD (IX+$13),A
ts_music_service_6 EED0 LD A,$00
EED2 ADD A,(IX+$12)
EED5 LD B,A
EED6 LD L,(IX+$0B)
EED9 LD H,(IX+$0C)
EEDC LD A,(HL)
EEDD INC HL
EEDE AND A
EEDF JP P,ts_music_service_7
EEE2 LD L,(IX+$09)
EEE5 LD H,(IX+$0A)
EEE8 AND $7F
ts_music_service_7 EEEA LD (IX+$0B),L
EEED LD (IX+$0C),H
EEF0 ADD A,B
EEF1 LD HL,$EFBC
EEF4 LD D,$00
EEF6 ADD A,A
EEF7 LD E,A
EEF8 ADD HL,DE
EEF9 LD E,(HL)
EEFA INC HL
EEFB LD D,(HL)
EEFC LD L,(IX+$1D)
EEFF BIT 6,L
EF01 JR Z,ts_music_service_15
EF03 LD H,A
EF04 LD B,(IX+$1A)
EF07 SLA B
EF09 BIT 7,L
EF0B LD A,(IX+$1C)
EF0E JR Z,ts_music_service_8
EF10 BIT 0,C
EF12 JR NZ,ts_music_service_11
ts_music_service_8 EF14 BIT 5,L
EF16 JR NZ,ts_music_service_9
EF18 SUB (IX+$1B)
EF1B JR NC,ts_music_service_10
EF1D SET 5,(IX+$1D)
EF21 SUB A
EF22 JR ts_music_service_10
ts_music_service_9 EF24 ADD A,(IX+$1B)
EF27 CP B
EF28 JR C,ts_music_service_10
EF2A RES 5,(IX+$1D)
EF2E LD A,B
ts_music_service_10 EF2F LD (IX+$1C),A
ts_music_service_11 EF32 EX DE,HL
EF33 SRL B
EF35 SUB B
EF36 LD E,A
EF37 LD A,D
EF38 LD D,$00
EF3A JR NC,ts_music_service_12
EF3C DEC D
ts_music_service_12 EF3D ADD A,$A0
EF3F JR C,ts_music_service_14
ts_music_service_13 EF41 SLA E
EF43 RL D
EF45 ADD A,$18
EF47 JR NC,ts_music_service_13
ts_music_service_14 EF49 ADD HL,DE
EF4A EX DE,HL
ts_music_service_15 EF4B LD A,C
EF4C XOR $01
EF4E LD (IX+$00),A
EF51 BIT 2,C
EF53 JR Z,ts_music_service_18
EF55 LD B,(IX+$0E)
EF58 DJNZ ts_music_service_17
EF5A LD C,(IX+$0D)
EF5D BIT 7,C
EF5F JR Z,ts_music_service_16
EF61 DEC B
ts_music_service_16 EF62 LD L,(IX+$07)
EF65 LD H,(IX+$08)
EF68 ADD HL,BC
EF69 LD (IX+$07),L
EF6C LD (IX+$08),H
EF6F ADD HL,DE
EF70 EX DE,HL
EF71 JR ts_music_service_18
ts_music_service_17 EF73 LD (IX+$0E),B
ts_music_service_18 EF76 CPL
EF77 AND $03
EF79 LD A,$00
EF7B JR NZ,ts_music_service_19
EF7D LD A,($EC79)
EF80 XOR $08
EF82 LD ($ECC6),A
EF85 LD A,$07
ts_music_service_19 EF87 LD HL,$EFB6
EF8A XOR (HL)
EF8B AND (IX+$24)
EF8E XOR (HL)
EF8F LD (HL),A
EF90 LD A,(IX+$1F)
EF93 AND A
EF94 JP P,ts_music_service_20
EF97 AND $7F
EF99 LD (IX+$1F),A
EF9C LD A,(IX+$24)
EF9F AND $38
EFA1 XOR $FF
EFA3 AND (HL)
EFA4 LD (HL),A
EFA5 LD A,$41
EFA7 LD ($ECC6),A
ts_music_service_20 EFAA EX DE,HL
EFAB LD A,(IX+$13)
EFAE RET
Prev: EC01 Up: Map Next: EFAF