;============================================================================== ; spectrum.s — Spectrum analyzer: render + hot-loop helpers (Z80 ASM) ; ; Hand-optimised replacements for C functions in main.c / vgm.c: ; spectrum_render() — 4-row bar graph, direct VRAM write ; spectrum_opl_b0(r,v) — OPL B0-B8 key-on + 0xBD percussion ; spectrum_opl_b1(r,v) — OPL Bank1 B0-B6 key-on ; spectrum_ay(r,v) — AY period shadow + vol trigger + YM2203 FM (chip1) ; spectrum_ay2(r,v) — same for chip2 (base offset = 3) ; spectrum_saa(r,v) — SAA octave shadow + amp trigger ; ; sdcccall(1) conventions: ; 1st u8 → A, 2nd u8 → L, 3rd u8 → stack+2 ; 1st u16 → HL, return u8 → A, return u16 → HL ; ; Port 0x13AF = Page3 window (TSConfig) ;============================================================================== .module spectrum ; ── Exports ────────────────────────────────────────────────────────── .globl _spectrum_render .globl _spectrum_decay .globl _spectrum_decay_reset .globl _spectrum_opl_b0 .globl _spectrum_opl_b1 .globl _spectrum_ay .globl _spectrum_ay2 .globl _spectrum_saa .globl _spectrum_saa2 .globl _spectrum_wave .globl _spec_period_to_bar .globl _spectrum_font_init .globl _spectrum_font_restore ; ── Imports ────────────────────────────────────────────────────────── .globl _spectrum_levels ; uint8_t[16] in isr.s _DATA .globl _isr_tick_ctr ; volatile uint8_t in isr.s _DATA .globl _s_pb_text_pg ; uint8_t in main.c _DATA .globl _s_spec_char_ofs ; uint16_t[4] in main.c _DATA .globl _spec_mask ; uint16_t in vgm.c _DATA .globl _spec_ay_period ; uint16_t[6] in vgm.c _DATA .globl _spec_saa_oct ; uint8_t[6] in vgm.c _DATA .globl _spec_saa_oct2 ; uint8_t[6] in vgm.c _DATA (chip 2) .globl _spec_saa_dual ; uint8_t in vgm.c _DATA .globl _spec_opl_bd ; uint8_t in vgm.c _DATA .globl _spec_fm_block ; uint8_t[6] in vgm.c _DATA .globl _spec_wave_active ; uint8_t[3]: 24-bit KeyOn mask ; ── Constants ──────────────────────────────────────────────────────── PORT_PAGE3 = 0x13AF SPEC_BARS = 16 SPEC_LEFT_PAD = 5 SPEC_BAR_W = 3 ; 2 filled + 1 space CHAR_FULL = 0x02 ; custom glyph: full-height striped bar CHAR_HALF = 0x01 ; custom glyph: half-height striped bar CLR_WIN = 0x70 ; WC_COLOR(WC_WHITE, WC_BLACK) WC_PAGE_FONT0 = 0x01 FONT_BASE = 0xC000 ; ── Timing: must match variant_cfg.h ──────────────────────────────── ISR_TICKS_PER_FRAME = 14 ; = ISR_TICKS_PER_FRAME in variant_cfg.h ; ── Decay: must match DECAY_FRAMES_* in variant_cfg.h ──────────────── DECAY_FAST = 1 ; = DECAY_FRAMES_FAST DECAY_MED = 2 ; = DECAY_FRAMES_MED DECAY_SLOW = 4 ; = DECAY_FRAMES_SLOW ;====================================================================== ; Scratch variables in _DATA (kept minimal) ;====================================================================== .area _DATA say_base: .db 0 ; temp: chip base index (0 or 3) sr_row_ctr: .db 0 ; temp: row iteration counter sr_rp_ptr: .dw 0 ; temp: current row-params pointer sd_last_tick: .db 0 ; isr_tick_ctr snapshot for frame gate sd_fast_ctr: .db DECAY_FAST ; countdown: decay levels >= 6 sd_med_ctr: .db DECAY_MED ; countdown: decay levels 3-5 sd_slow_ctr: .db DECAY_SLOW ; countdown: decay levels 1-2 sr_prev_hash: .db 0 ; XOR-rotate hash of spectrum_levels from previous render .area _CODE ; Row parameter table: [thresh_full, thresh_half, color] × 4 rows ; Row 0 (top=peak): thresholds 8,7 color=BRIGHT_RED ; Row 1: thresholds 6,5 color=BRIGHT_YELLOW ; Row 2: thresholds 4,3 color=BRIGHT_GREEN ; Row 3 (bottom): thresholds 2,1 color=GREEN sr_row_params: .db 8, 7, 0x7A ; row 0: white bg + bright red .db 6, 5, 0x7E ; row 1: white bg + bright yellow .db 4, 3, 0x7C ; row 2: white bg + bright green .db 2, 1, 0x74 ; row 3: white bg + green ;====================================================================== ; spectrum_render(void) ; ; Optimised Z80 register layout — zero push/pop in inner bar loop: ; IX = char_ptr (stride +3 per bar via: inc ix x3) ; IY = attr_ptr (stride +3 per bar via: inc iy x3) ; HL = spectrum_levels + bar_index ; B = bar counter (DJNZ) ; C = thresh_full (row-constant, set ONCE before bar loop) ; D = thresh_half (row-constant, set ONCE before bar loop) ; E = color (row-constant, set ONCE before bar loop) ; ; Old inner loop loaded 0(ix)/1(ix)/2(ix) on every bar = 3x19=57 T ; per bar x 16 bars x 4 rows = 3648 wasted T-states per frame. ; New inner loop: C/D/E loaded once per row, never reloaded per bar. ; Total savings: ~3500 T-states per spectrum_render call. ;====================================================================== _spectrum_render:: ; XOR-rotate hash over spectrum_levels[16] (~100T). ; Detects ANY change versus previous frame, not just zero/non-zero. ; Hash = seed, then for each byte: RLCA + XOR (hl). ld hl, #_spectrum_levels xor a .rept 16 rlca xor (hl) inc hl .endm ; A = rolling hash of current levels ld hl, #sr_prev_hash cp (hl) ; same as last frame? ret z ; unchanged → skip render (~7000T saved) ld (hl), a ; store new hash push ix push iy ld bc, #PORT_PAGE3 in a, (c) push af push bc ld a, (_s_pb_text_pg) out (c), a ld a, #4 ld (sr_row_ctr), a ld hl, #sr_row_params ld (sr_rp_ptr), hl sr_row_loop: ;-------------------------------------------------------------- ; row_index = 4 - row_ctr (0..3) ; Compute IX = char_ptr, IY = attr_ptr for this row. ;-------------------------------------------------------------- ld a, (sr_row_ctr) ld e, a ld a, #4 sub e add a, a ; A = row_index * 2 ld e, a ld d, #0 ; IX = 0xC000 + s_spec_char_ofs[row_index] + SPEC_LEFT_PAD ld hl, #_s_spec_char_ofs add hl, de ld a, (hl) inc hl ld h, (hl) ld l, a ; HL = s_spec_char_ofs[row_index] ld bc, #(0xC000 + SPEC_LEFT_PAD) add hl, bc push hl pop ix ; IX = char_ptr ; IY = IX + 0x0080 (attr_ofs = char_ofs | 0x0080 in TSConf) push ix pop hl ld de, #0x0080 add hl, de push hl pop iy ; IY = attr_ptr ;-------------------------------------------------------------- ; Load row params ONCE into C/D/E: all three are row-constants. ; C, D, E survive the bar loop untouched (djnz only changes B). ;-------------------------------------------------------------- ld hl, (sr_rp_ptr) ld c, (hl) ; C = thresh_full inc hl ld d, (hl) ; D = thresh_half inc hl ld e, (hl) ; E = color inc hl ld (sr_rp_ptr), hl ; advance for next row ;-------------------------------------------------------------- ; Bar loop: 16 bars — all comparisons use stable C,D,E regs. ; No push/pop needed: all state lives in IX, IY, HL, B, C, D, E. ;-------------------------------------------------------------- ld hl, #_spectrum_levels ld b, #SPEC_BARS sr_bar_loop: ld a, (hl) cp c ; NC if level >= thresh_full jr nc, sr_full cp d ; NC if level >= thresh_half jr nc, sr_half sr_empty: ld a, #' ' ld 0(ix), a ld 1(ix), a ld a, #CLR_WIN ld 0(iy), a ld 1(iy), a jr sr_next sr_half: ld a, #CHAR_HALF ld 0(ix), a ld 1(ix), a ld 0(iy), e ; E = row color (stable per row) ld 1(iy), e jr sr_next sr_full: ld a, #CHAR_FULL ld 0(ix), a ld 1(ix), a ld 0(iy), e ; E = row color (stable per row) ld 1(iy), e sr_next: inc hl ; next spectrum level inc ix ; char_ptr += SPEC_BAR_W (3) inc ix inc ix inc iy ; attr_ptr += SPEC_BAR_W (3) inc iy inc iy djnz sr_bar_loop ld a, (sr_row_ctr) dec a ld (sr_row_ctr), a jp nz, sr_row_loop pop bc pop af out (c), a pop iy pop ix ret ;====================================================================== ; spectrum_decay_reset() — initialize decay timers at song start ;====================================================================== _spectrum_decay_reset:: ld a, (_isr_tick_ctr) ld (sd_last_tick), a ld a, #DECAY_FAST ld (sd_fast_ctr), a ld a, #DECAY_MED ld (sd_med_ctr), a ld a, #DECAY_SLOW ld (sd_slow_ctr), a ; Reset render hash so first frame always renders ld a, #0xFF ld (sr_prev_hash), a ret ;====================================================================== ; spectrum_decay() — frame-gated non-linear level decay ; ; Called every main-loop iteration; returns immediately if less than ; ISR_TICKS_PER_FRAME ticks have elapsed since the last frame. ; ; Three countdown timers control decay speed per level range: ; Levels >= 6 : -1 every 3 frames (~60 ms) ; Levels 3-5 : -1 every 6 frames (~180 ms) ; Levels 1-2 : -1 every 10 frames (~200 ms) ; ; Register layout: ; C = decay-flags bitmask (bit2=fast, bit1=med, bit0=slow) ; B = bar loop counter ; HL = pointer into spectrum_levels[] ;====================================================================== _spectrum_decay:: ; ── Frame gate ────────────────────────────────────────────── ld a, (_isr_tick_ctr) ld hl, #sd_last_tick sub (hl) ; elapsed = now - last cp #ISR_TICKS_PER_FRAME ret c ; < 1 frame, skip ; Advance by one frame ld a, (hl) add a, #ISR_TICKS_PER_FRAME ld (hl), a ; ── Build decay-flags in C ────────────────────────────────── ld c, #0 ld hl, #sd_fast_ctr ; 5-frame timer (levels >= 6) dec (hl) jr nz, sd_nf ld (hl), #DECAY_FAST set 2, c sd_nf: ld hl, #sd_med_ctr ; med-frame timer (levels 3-5) dec (hl) jr nz, sd_nm ld (hl), #DECAY_MED set 1, c sd_nm: ld hl, #sd_slow_ctr ; slow-frame timer (levels 1-2) dec (hl) jr nz, sd_ns ld (hl), #DECAY_SLOW set 0, c sd_ns: ld a, c or a ret z ; no tier expired → nothing to decay ; ── Single loop over 16 bars ──────────────────────────────── ld hl, #_spectrum_levels ; Quick all-zero check: if all bars are 0, skip decay loop ld a, (hl) inc hl .rept 15 or (hl) inc hl .endm ret z ; all bars zero → nothing to decay ld hl, #_spectrum_levels ld b, #SPEC_BARS sd_bar_loop: ld a, (hl) or a jr z, sd_next ; level 0 → skip cp #6 jr nc, sd_high ; level >= 6 → fast tier cp #3 jr nc, sd_mid ; level >= 3 → medium tier ; level 1-2: slow tier bit 0, c jr z, sd_next dec (hl) jr sd_next sd_mid: bit 1, c jr z, sd_next dec (hl) jr sd_next sd_high: bit 2, c jr z, sd_next dec (hl) sd_next: inc hl djnz sd_bar_loop ret ;====================================================================== ; spectrum_opl_b0(reg, val) — A=reg, L=val ; ; OPL Bank0: B0-B8 key-on + 0xBD percussion (rising edge). ; Sets bits in spec_mask. ;====================================================================== _spectrum_opl_b0:: ; Save reg and val ld d, a ; D = reg ld e, l ; E = val ; ── Check B0-B8 key-on ── cp a, #0xB0 jr c, sob0_check_bd ; reg < 0xB0 cp a, #0xB9 jr nc, sob0_check_bd ; reg > 0xB8 bit 5, e ; KeyOn bit in val jr z, sob0_check_bd ; not keyed on ; bar = (val >> 1) & 0x0F ld a, e srl a and a, #0x0F ; spec_mask |= (1 << bar) call _set_spec_bit ; Fall through to check BD sob0_check_bd: ld a, d cp a, #0xBD ret nz ; not reg 0xBD, done ; ── 0xBD percussion handling ── bit 5, e ; rhythm mode ON? jr z, sob0_save_bd ; no → just save shadow ; rising = val & ~old & 0x1F ld a, (_spec_opl_bd) cpl ; ~old and a, e ; val & ~old and a, #0x1F ; mask lower 5 bits jr z, sob0_save_bd ; no rising edges ; Check each percussion bit ld b, a ; B = rising edges mask bit 4, b ; BD? jr z, sob0_no_bd ld a, #2 ; BD → bar 2 call _set_spec_bit sob0_no_bd: bit 3, b ; SD? jr z, sob0_no_sd ld a, #8 ; SD → bar 8 call _set_spec_bit sob0_no_sd: bit 2, b ; TOM? jr z, sob0_no_tom ld a, #5 ; TOM → bar 5 call _set_spec_bit sob0_no_tom: bit 1, b ; CY? jr z, sob0_no_cy ld a, #12 ; CY → bar 12 call _set_spec_bit sob0_no_cy: bit 0, b ; HH? jr z, sob0_save_bd ld a, #14 ; HH → bar 14 call _set_spec_bit sob0_save_bd: ld a, e ld (_spec_opl_bd), a ret ;====================================================================== ; spectrum_opl_b1(reg, val) — A=reg, L=val ; ; OPL Bank1: B0-B6 key-on only. Same logic, smaller range. ;====================================================================== _spectrum_opl_b1:: cp a, #0xB0 ret c ; reg < 0xB0 cp a, #0xB7 ret nc ; reg > 0xB6 bit 5, l ; KeyOn bit ret z ; bar = (val >> 1) & 0x0F ld a, l srl a and a, #0x0F jp _set_spec_bit ; tail-call ;====================================================================== ; spectrum_wave(reg, val) — A=reg, L=val ; ; YMF278B wave activity tracker. Key registers 0x68-0x7F maintain a ; 24-bit KeyOn mask. asm_emit_skip_spec folds channels 16-23 onto bars ; 0-7 and transports the current activity together with every wait. ;====================================================================== _spectrum_wave:: ld e, l ; E = key register value cp #0x68 ret c cp #0x80 ret nc sub #0x68 ; A = channel 0..23 ld d, a and #0x07 ld hl, #sw_bit_mask add a, l ld l, a jr nc, sw_mask_nc inc h sw_mask_nc: ld c, (hl) ; C = bit within mask byte ld a, d rrca rrca rrca and #0x03 ; byte index 0..2 ld hl, #_spec_wave_active add a, l ld l, a jr nc, sw_ptr_nc inc h sw_ptr_nc: bit 7, e jr z, sw_key_off ld a, (hl) or c ld (hl), a ret sw_key_off: ld a, c cpl and (hl) ld (hl), a ret sw_bit_mask: .db 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 ;====================================================================== ; _set_spec_bit — set bit A (0..15) in spec_mask ; Input: A = bit number (0..15) ; Trashes: A, D, E, HL ; Preserves: B, C (no push/pop needed) ; Reuses the 8-byte wave-channel bit table; bit3 selects low/high byte. ;====================================================================== _set_spec_bit: ld d, a ; preserve bar number and #0x07 ld hl, #sw_bit_mask add a, l ld l, a jr nc, ssb_ptr_ok inc h ssb_ptr_ok: ld e, (hl) ; E = 8-bit mask bit 3, d jr z, ssb_low_byte ld d, e ; bars 8..15: mask in high byte ld e, #0 jr ssb_merge ssb_low_byte: ld d, #0 ; bars 0..7: mask in low byte ssb_merge: ld hl, (_spec_mask) ld a, l or e ld l, a ld a, h or d ld h, a ld (_spec_mask), hl ret ;====================================================================== ; spectrum_ay(reg, val) — A=reg, L=val (chip1, base=0) ; spectrum_ay2(reg, val) — A=reg, L=val (chip2, base=3) ; ; AY: shadow tone period (regs 0-5), trigger bar on volume (regs 8-10) ; YM2203 FM: shadow block (0xA4-0xA6), key-on (0x28) ;====================================================================== _spectrum_ay2:: ld d, a ; D = reg ld e, l ; E = val ld a, #3 jr say_base_set _spectrum_ay:: ld d, a ; D = reg ld e, l ; E = val xor a, a ; base = 0 say_base_set: ld (say_base), a ; save base ; ── Check reg <= 5 (tone period shadow) ── ld a, d ; A = reg cp a, #6 jr nc, say_check_vol ; ch = base + (reg >> 1) srl a ; A = reg >> 1 ld hl, #say_base add a, (hl) ; A = base + (reg>>1) = ch index ; Offset into spec_ay_period[ch] (uint16_t array) add a, a ; A = ch * 2 ld hl, #_spec_ay_period add a, l ld l, a jr nc, say_nc1 inc h say_nc1: ; HL → spec_ay_period[ch] bit 0, d ; reg & 1 ? jr nz, say_hi_nibble ; Low byte: period = (period & 0x0F00) | val ld a, e ; A = val ld (hl), a ; write low byte ret say_hi_nibble: ; High nibble: period = (period & 0x00FF) | ((val & 0x0F) << 8) inc hl ; point to high byte ld a, e and a, #0x0F ld (hl), a ret ; ── Check reg 8-10 (volume → trigger bar) ── say_check_vol: ld a, d cp a, #8 jr c, say_check_fm_block cp a, #11 jr nc, say_check_fm_block ; Check val & 0x0F != 0 ld a, e and a, #0x0F ret z ; volume=0, skip ; ch_index = base + (reg - 8) ld a, d sub a, #8 ld hl, #say_base add a, (hl) ; A = array index ; Load spec_ay_period[index] add a, a ; * 2 ld hl, #_spec_ay_period add a, l ld l, a jr nc, say_nc2 inc h say_nc2: ld a, (hl) inc hl ld h, (hl) ld l, a ; HL = period ; Fall through to spec_period_to_bar + set_spec_bit call _spec_period_to_bar ; A = bar index jp _set_spec_bit ; tail-call ; ── Check FM block shadow (regs 0xA4-0xA6) ── say_check_fm_block: ld a, d cp a, #0xA4 jr c, say_check_keyon cp a, #0xA7 jr nc, say_check_keyon ; spec_fm_block[base + (reg - 0xA4)] = (val >> 3) & 0x07 sub a, #0xA4 ld hl, #say_base add a, (hl) ; A = index ld hl, #_spec_fm_block add a, l ld l, a jr nc, say_nc3 inc h say_nc3: ld a, e srl a srl a srl a and a, #0x07 ld (hl), a ret ; ── Check FM key-on (reg 0x28) ── say_check_keyon: ld a, d cp a, #0x28 ret nz ; Check operators keyed on (val & 0xF0) ld a, e and a, #0xF0 ret z ; ch = val & 0x03 ld a, e and a, #0x03 cp a, #3 ret nc ; ch > 2 invalid ; bar = spec_fm_block[base + ch] << 1 ld hl, #say_base add a, (hl) ; A = index ld hl, #_spec_fm_block add a, l ld l, a jr nc, say_nc4 inc h say_nc4: ld a, (hl) ; A = block (0..7) add a, a ; A = block * 2 = bar jp _set_spec_bit ; tail-call ;====================================================================== ; spec_period_to_bar(period) — HL=period, returns A=bar (0..15) ; ; Cascaded compare on high byte first, then low byte. ; Optimised: single compare chain, no function calls. ;====================================================================== _spec_period_to_bar:: ld a, h ; A = period >> 8 cp a, #12 jr c, sptb_lt12 xor a, a ; return 0 ret sptb_lt12: cp a, #8 jr c, sptb_lt8 ld a, #1 ret sptb_lt8: cp a, #6 jr c, sptb_lt6 ld a, #2 ret sptb_lt6: cp a, #4 jr c, sptb_lt4 ld a, #3 ret sptb_lt4: cp a, #3 jr c, sptb_lt3 ld a, #4 ret sptb_lt3: cp a, #2 jr c, sptb_lt2 ld a, #5 ret sptb_lt2: cp a, #1 jr c, sptb_lt1 ; h == 1: check low byte ld a, l cp a, #128 ld a, #6 ret nc ; period >= 384 (1*256+128) ld a, #7 ret ; period >= 256 sptb_lt1: ; h == 0: use low byte ld a, l cp a, #192 jr c, sptb_p1 ld a, #8 ret sptb_p1: cp a, #128 jr c, sptb_p2 ld a, #9 ret sptb_p2: cp a, #96 jr c, sptb_p3 ld a, #10 ret sptb_p3: cp a, #64 jr c, sptb_p4 ld a, #11 ret sptb_p4: cp a, #48 jr c, sptb_p5 ld a, #12 ret sptb_p5: cp a, #32 jr c, sptb_p6 ld a, #13 ret sptb_p6: cp a, #16 jr c, sptb_p7 ld a, #14 ret sptb_p7: ld a, #15 ret ;====================================================================== ; spectrum_saa(reg, val) — A=reg, L=val ; ; SAA1099 chip 0: shadow octave (regs 0x10-0x12), trigger on amplitude (0-5). ; Bar mapping: if dual → bar=octave (0-7); if single → bar=octave*2 (0-14). ;====================================================================== _spectrum_saa:: ld d, a ; D = reg ld e, l ; E = val ; ── Check octave shadow (regs 0x10-0x12) ── cp a, #0x10 jr c, ssaa_check_amp cp a, #0x13 jr nc, ssaa_check_amp ; ch = (reg - 0x10) * 2 sub a, #0x10 add a, a ; A = (reg-0x10)*2 = ch_base ld hl, #_spec_saa_oct add a, l ld l, a jr nc, ssaa_nc1 inc h ssaa_nc1: ; spec_saa_oct[ch] = val & 0x07 ld a, e and a, #0x07 ld (hl), a ; spec_saa_oct[ch+1] = (val >> 4) & 0x07 inc hl ld a, e rrca rrca rrca rrca and a, #0x07 ld (hl), a ret ; ── Check amplitude (regs 0x00-0x05) ── ssaa_check_amp: ld a, d cp a, #0x06 ret nc ; reg >= 6, not amplitude ; Check val != 0 ld a, e or a, a ret z ; bar = spec_saa_oct[reg] ld a, d ; A = reg (0..5) ld hl, #_spec_saa_oct add a, l ld l, a jr nc, ssaa_nc2 inc h ssaa_nc2: ld a, (hl) ; A = octave (0..7) ; if dual: bar = octave; if single: bar = octave * 2 ld hl, #_spec_saa_dual bit 0, (hl) jr nz, ssaa_bar_done add a, a ; A = octave * 2 ssaa_bar_done: jp _set_spec_bit ; tail-call ;====================================================================== ; spectrum_saa2(reg, val) — A=reg, L=val ; ; SAA1099 chip 1: shadow octave to spec_saa_oct2[], ; trigger on amplitude with bar = octave + 8 (bars 8-15). ;====================================================================== _spectrum_saa2:: ld d, a ; D = reg ld e, l ; E = val ; ── Check octave shadow (regs 0x10-0x12) ── cp a, #0x10 jr c, ssaa2_check_amp cp a, #0x13 jr nc, ssaa2_check_amp ; ch = (reg - 0x10) * 2 sub a, #0x10 add a, a ld hl, #_spec_saa_oct2 add a, l ld l, a jr nc, ssaa2_nc1 inc h ssaa2_nc1: ld a, e and a, #0x07 ld (hl), a inc hl ld a, e rrca rrca rrca rrca and a, #0x07 ld (hl), a ret ; ── Check amplitude (regs 0x00-0x05) ── ssaa2_check_amp: ld a, d cp a, #0x06 ret nc ld a, e or a, a ret z ; bar = spec_saa_oct2[reg] + 8 ld a, d ld hl, #_spec_saa_oct2 add a, l ld l, a jr nc, ssaa2_nc2 inc h ssaa2_nc2: ld a, (hl) ; A = octave (0..7) add a, #8 ; bars 8-15 for chip 2 jp _set_spec_bit ; tail-call ;====================================================================== ; spectrum_font_init() — install custom glyphs for chars 0x01-0x02 ; ; Maps font page into page3 window, saves original 16 bytes (2 glyphs), ; writes striped-bar patterns from glyph_half / glyph_full. ; Must be called once before playback. Pairs with spectrum_font_restore. ;====================================================================== _spectrum_font_init:: ld bc, #PORT_PAGE3 in a, (c) push af push bc ld a, #WC_PAGE_FONT0 out (c), a ld hl, #(FONT_BASE + 8) ld de, #sf_font_save ld bc, #16 ldir ld hl, #sf_glyph_half ld de, #(FONT_BASE + 8) ld bc, #16 ldir pop bc pop af out (c), a ret ;====================================================================== ; spectrum_font_restore() — restore original glyphs for chars 0x01-0x02 ; ; Must be called on exit to avoid corrupting the system font. ;====================================================================== _spectrum_font_restore:: ld bc, #PORT_PAGE3 in a, (c) push af push bc ld a, #WC_PAGE_FONT0 out (c), a ld hl, #sf_font_save ld de, #(FONT_BASE + 8) ld bc, #16 ldir pop bc pop af out (c), a ret sf_glyph_half: .db 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00 sf_glyph_full: .db 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00 sf_font_save: .ds 16