Add OPL4 playback and analyzer support
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
; ============================================================================
|
||||
; cmdblocks.asm — VGM-format command blocks for chip init/silence
|
||||
;
|
||||
; Compiled with sjasmplus → raw binary 16KB, appended as plugin page 5.
|
||||
;
|
||||
; Each block contains standard VGM opcodes identical to those in .vgm files:
|
||||
; 0x5E rr vv — OPL3 bank 0 write
|
||||
; 0x5F rr vv — OPL3 bank 1 write
|
||||
; 0x55 rr vv — YM2203 write (also used for MultiSound control regs 0xF0+)
|
||||
; 0xA0 rr vv — AY8910 write (bit7 of rr = chip 2)
|
||||
; 0xBD rr vv — SAA1099 write (bit7 of rr = chip 2)
|
||||
; 0x66 — end of block
|
||||
;
|
||||
; The standard vgm_fill_buffer() parses these blocks in cmdblk_mode,
|
||||
; converting VGM opcodes to ISR commands exactly as it does for .vgm data.
|
||||
; This eliminates the need for a separate command copier — budget counting,
|
||||
; buffer overflow protection and command parsing all come for free.
|
||||
;
|
||||
; NOTE on SAA clock: MultiSound intercepts AY reg select 0xF0-0xFF on
|
||||
; port 0xFFFD for hardware control. VGM opcode 0xA0 strips bit7 of
|
||||
; the register byte for chip selection, so 0xA0 CANNOT send regs >= 0x80
|
||||
; to chip 1. We use YM2203 opcode 0x55 instead — it sends the register
|
||||
; byte raw to 0xFFFD, which MultiSound intercepts correctly.
|
||||
;
|
||||
; Layout:
|
||||
; 0xC000 : pointer table (uint16 × N) — absolute addresses of blocks
|
||||
; 0xC000+N*2.. : block data (variable-length VGM commands, 3 bytes each)
|
||||
;
|
||||
; Usage from C:
|
||||
; wc_mngc_pl(CMDBLK_PAGE);
|
||||
; vgm_read_ptr = ((uint16_t*)0xC000)[blk_idx];
|
||||
; vgm_cmdblk_mode = 1; vgm_song_ended = 0;
|
||||
; vgm_fill_buffer(buf_idx);
|
||||
; vgm_cmdblk_mode = 0;
|
||||
;
|
||||
; Assembled with: sjasmplus asm\cmdblocks.asm
|
||||
; → SAVEBIN "build/cmdblocks.bin", #C000, #4000
|
||||
; ============================================================================
|
||||
|
||||
DEVICE NOSLOT64K
|
||||
ORG #C000
|
||||
|
||||
; ── VGM opcodes ─────────────────────────────────────────────────────
|
||||
VGM_OPL_B0 EQU #5E ; OPL3 bank 0: 5E rr vv
|
||||
VGM_OPL_B1 EQU #5F ; OPL3 bank 1: 5F rr vv
|
||||
VGM_YM2203 EQU #55 ; YM2203 SSG+FM chip 1: 55 rr vv
|
||||
VGM_YM2203_2 EQU #A5 ; YM2203 SSG+FM chip 2: A5 rr vv
|
||||
VGM_AY EQU #A0 ; AY8910: A0 rr vv (bit7 = chip 2)
|
||||
VGM_SAA EQU #BD ; SAA1099: BD rr vv (bit7 = chip 2)
|
||||
VGM_OPL4 EQU #D0 ; YMF278B (OPL4): D0 pp rr vv (pp = port 0/1/2)
|
||||
VGM_END EQU #66 ; End of data
|
||||
|
||||
; ── Macros (each emits standard VGM bytes, 3 bytes per command) ─────
|
||||
|
||||
MACRO opl3_bank0 addr, val
|
||||
db VGM_OPL_B0, addr, val
|
||||
ENDM
|
||||
|
||||
MACRO opl3_bank1 addr, val
|
||||
db VGM_OPL_B1, addr, val
|
||||
ENDM
|
||||
|
||||
MACRO ym2203_write reg, val
|
||||
db VGM_YM2203, reg, val
|
||||
ENDM
|
||||
|
||||
MACRO ym2203_2_write reg, val
|
||||
db VGM_YM2203_2, reg, val
|
||||
ENDM
|
||||
|
||||
MACRO ay_write reg, val
|
||||
db VGM_AY, reg, val
|
||||
ENDM
|
||||
|
||||
MACRO ay2_write reg, val
|
||||
db VGM_AY, reg | #80, val
|
||||
ENDM
|
||||
|
||||
MACRO saa_write reg, val
|
||||
db VGM_SAA, reg, val
|
||||
ENDM
|
||||
|
||||
MACRO saa2_write reg, val
|
||||
db VGM_SAA, reg | #80, val
|
||||
ENDM
|
||||
|
||||
MACRO opl4_wave_write reg, val
|
||||
db VGM_OPL4, #02, reg, val
|
||||
ENDM
|
||||
|
||||
MACRO blk_end
|
||||
db VGM_END
|
||||
ENDM
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; Pointer table (indices = CMDBLK_xxx from vgm.h)
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
ptr_table:
|
||||
dw blk_init_opl3 ; 0 = CMDBLK_INIT_OPL3
|
||||
dw blk_init_opl2 ; 1 = CMDBLK_INIT_OPL2
|
||||
dw blk_silence_opl ; 2 = CMDBLK_SILENCE_OPL
|
||||
dw blk_silence_ay ; 3 = CMDBLK_SILENCE_AY
|
||||
dw blk_silence_ay2 ; 4 = CMDBLK_SILENCE_AY2
|
||||
dw blk_silence_saa ; 5 = CMDBLK_SILENCE_SAA
|
||||
dw blk_silence_saa2 ; 6 = CMDBLK_SILENCE_SAA2
|
||||
dw blk_saa_clock_on ; 7 = CMDBLK_SAA_CLK_ON
|
||||
dw blk_saa_clock_off ; 8 = CMDBLK_SAA_CLK_OFF
|
||||
dw blk_silence_ym2203 ; 9 = CMDBLK_SILENCE_YM2203
|
||||
dw blk_silence_ym2203_2 ; 10 = CMDBLK_SILENCE_YM2203_2
|
||||
dw blk_saa_init ; 11 = CMDBLK_SAA_INIT
|
||||
dw blk_saa2_init ; 12 = CMDBLK_SAA2_INIT
|
||||
dw blk_init_opl4 ; 13 = CMDBLK_INIT_OPL4
|
||||
dw blk_silence_wave ; 14 = CMDBLK_SILENCE_WAVE
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_INIT_OPL3 — OPL3 init (NEW=1, clean state)
|
||||
;
|
||||
; OPL3 NEW=1 enables full OPL3 register set. 4-op connections are
|
||||
; cleared (reg 0x104=0) to avoid leftover pairing from a previous
|
||||
; track. Test/WSE register 0x01 is zeroed — OPL3 does not use WSE;
|
||||
; waveform select is implicit via E0-F5 registers.
|
||||
;
|
||||
; VGM stream: 5F 05 01 5F 04 00 5E 01 00 66
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_init_opl3:
|
||||
opl3_bank1 #05, #01 ; VGM: 5F 05 01 — OPL3 NEW=1
|
||||
opl3_bank1 #04, #00 ; VGM: 5F 04 00 — 4-op OFF (clean)
|
||||
opl3_bank0 #01, #00 ; VGM: 5E 01 00 — Test/WSE=0
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_INIT_OPL4 — YMF278B (OPL4, ZXM-MoonSound) init: NEW=1 + NEW2=1
|
||||
;
|
||||
; Как CMDBLK_INIT_OPL3 (NEW=1, L/R через C0-C8), но дополнительно взводит
|
||||
; бит NEW2 (бит 1 регистра Bank1 #05). На реальном чипе YMF278B этот бит
|
||||
; обязателен — без него чип игнорирует любые записи в wave-часть (порты
|
||||
; #7E/#7F), т.е. VGM-команды 0xD0 с портом 2 останутся немы.
|
||||
;
|
||||
; VGM stream: 5F 05 03 5F 04 00 5E 01 00 66
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_init_opl4:
|
||||
opl3_bank1 #05, #03 ; VGM: 5F 05 03 — NEW=1 + NEW2=1 (разблокировать wave)
|
||||
opl3_bank1 #04, #00 ; VGM: 5F 04 00 — 4-op OFF (clean)
|
||||
opl3_bank0 #01, #00 ; VGM: 5E 01 00 — Test/WSE=0
|
||||
opl4_wave_write #02, #10 ; WT headers 384-511 from 0x200000,
|
||||
; memory access OFF (sound generation mode)
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_INIT_OPL2 — OPL2 compat init (NEW=0, clean state)
|
||||
;
|
||||
; Forces OPL2 compatibility mode. Critically: 0x104=0 clears any
|
||||
; residual 4-op state that would persist after an OPL3 track (the
|
||||
; most common cause of OPL2 artifacts). Test/WSE register = 0.
|
||||
;
|
||||
; VGM stream: 5F 05 00 5F 04 00 5E 01 00 66
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_init_opl2:
|
||||
opl3_bank1 #05, #00 ; VGM: 5F 05 00 — OPL3 NEW=0 (OPL2)
|
||||
opl3_bank1 #04, #00 ; VGM: 5F 04 00 — 4-op OFF (clean)
|
||||
opl3_bank0 #01, #00 ; VGM: 5E 01 00 — Test/WSE=0
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SILENCE_OPL — Full OPL3 silence (both banks)
|
||||
;
|
||||
; VGM stream (55 commands):
|
||||
; 5E B0..B8 00 × 9 Key Off Bank 0 (ch 0-8)
|
||||
; 5F B0..B8 00 × 9 Key Off Bank 1 (ch 0-8)
|
||||
; 5E 4x 3F ×18 TL max Bank 0 (18 operators)
|
||||
; 5F 4x 3F ×18 TL max Bank 1 (18 operators)
|
||||
; 5E BD 00 Percussion Off
|
||||
; 66 End
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_silence_opl:
|
||||
; ── Key Off: Bank 0 — VGM: 5E B0..B8 00 ──
|
||||
opl3_bank0 #B0, #00
|
||||
opl3_bank0 #B1, #00
|
||||
opl3_bank0 #B2, #00
|
||||
opl3_bank0 #B3, #00
|
||||
opl3_bank0 #B4, #00
|
||||
opl3_bank0 #B5, #00
|
||||
opl3_bank0 #B6, #00
|
||||
opl3_bank0 #B7, #00
|
||||
opl3_bank0 #B8, #00
|
||||
; ── Key Off: Bank 1 — VGM: 5F B0..B8 00 ──
|
||||
opl3_bank1 #B0, #00
|
||||
opl3_bank1 #B1, #00
|
||||
opl3_bank1 #B2, #00
|
||||
opl3_bank1 #B3, #00
|
||||
opl3_bank1 #B4, #00
|
||||
opl3_bank1 #B5, #00
|
||||
opl3_bank1 #B6, #00
|
||||
opl3_bank1 #B7, #00
|
||||
opl3_bank1 #B8, #00
|
||||
; ── TL max: Bank 0 — VGM: 5E {40-45,48-4D,50-55} 3F ──
|
||||
opl3_bank0 #40, #3F
|
||||
opl3_bank0 #41, #3F
|
||||
opl3_bank0 #42, #3F
|
||||
opl3_bank0 #43, #3F
|
||||
opl3_bank0 #44, #3F
|
||||
opl3_bank0 #45, #3F
|
||||
opl3_bank0 #48, #3F
|
||||
opl3_bank0 #49, #3F
|
||||
opl3_bank0 #4A, #3F
|
||||
opl3_bank0 #4B, #3F
|
||||
opl3_bank0 #4C, #3F
|
||||
opl3_bank0 #4D, #3F
|
||||
opl3_bank0 #50, #3F
|
||||
opl3_bank0 #51, #3F
|
||||
opl3_bank0 #52, #3F
|
||||
opl3_bank0 #53, #3F
|
||||
opl3_bank0 #54, #3F
|
||||
opl3_bank0 #55, #3F
|
||||
; ── TL max: Bank 1 — VGM: 5F {40-45,48-4D,50-55} 3F ──
|
||||
opl3_bank1 #40, #3F
|
||||
opl3_bank1 #41, #3F
|
||||
opl3_bank1 #42, #3F
|
||||
opl3_bank1 #43, #3F
|
||||
opl3_bank1 #44, #3F
|
||||
opl3_bank1 #45, #3F
|
||||
opl3_bank1 #48, #3F
|
||||
opl3_bank1 #49, #3F
|
||||
opl3_bank1 #4A, #3F
|
||||
opl3_bank1 #4B, #3F
|
||||
opl3_bank1 #4C, #3F
|
||||
opl3_bank1 #4D, #3F
|
||||
opl3_bank1 #50, #3F
|
||||
opl3_bank1 #51, #3F
|
||||
opl3_bank1 #52, #3F
|
||||
opl3_bank1 #53, #3F
|
||||
opl3_bank1 #54, #3F
|
||||
opl3_bank1 #55, #3F
|
||||
; ── Waveform reset: Bank 0 — VGM: 5E {E0-E5,E8-ED,F0-F5} 00 ──
|
||||
; Clears non-sine waveforms that linger between tracks.
|
||||
; Critical for clean OPL2 playback after OPL3 usage.
|
||||
opl3_bank0 #E0, #00
|
||||
opl3_bank0 #E1, #00
|
||||
opl3_bank0 #E2, #00
|
||||
opl3_bank0 #E3, #00
|
||||
opl3_bank0 #E4, #00
|
||||
opl3_bank0 #E5, #00
|
||||
opl3_bank0 #E8, #00
|
||||
opl3_bank0 #E9, #00
|
||||
opl3_bank0 #EA, #00
|
||||
opl3_bank0 #EB, #00
|
||||
opl3_bank0 #EC, #00
|
||||
opl3_bank0 #ED, #00
|
||||
opl3_bank0 #F0, #00
|
||||
opl3_bank0 #F1, #00
|
||||
opl3_bank0 #F2, #00
|
||||
opl3_bank0 #F3, #00
|
||||
opl3_bank0 #F4, #00
|
||||
opl3_bank0 #F5, #00
|
||||
; ── Waveform reset: Bank 1 — VGM: 5F {E0-E5,E8-ED,F0-F5} 00 ──
|
||||
opl3_bank1 #E0, #00
|
||||
opl3_bank1 #E1, #00
|
||||
opl3_bank1 #E2, #00
|
||||
opl3_bank1 #E3, #00
|
||||
opl3_bank1 #E4, #00
|
||||
opl3_bank1 #E5, #00
|
||||
opl3_bank1 #E8, #00
|
||||
opl3_bank1 #E9, #00
|
||||
opl3_bank1 #EA, #00
|
||||
opl3_bank1 #EB, #00
|
||||
opl3_bank1 #EC, #00
|
||||
opl3_bank1 #ED, #00
|
||||
opl3_bank1 #F0, #00
|
||||
opl3_bank1 #F1, #00
|
||||
opl3_bank1 #F2, #00
|
||||
opl3_bank1 #F3, #00
|
||||
opl3_bank1 #F4, #00
|
||||
opl3_bank1 #F5, #00
|
||||
; ── Percussion Off — VGM: 5E BD 00 ──
|
||||
opl3_bank0 #BD, #00
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SILENCE_WAVE — YMF278B (OPL4) wave-часть silence, 24 канала
|
||||
;
|
||||
; CMDBLK_SILENCE_OPL глушит только FM-банки (0x5E/0x5F) — wave-каналы
|
||||
; (табличный синтез, до 24 голосов) им не затрагиваются и продолжают
|
||||
; звучать после выхода из плеера. Для каждого канала N (0-23):
|
||||
; рег. (0x68+N), data=0x40 — Key Off + Damp (бит7:6=01, форсированный
|
||||
; быстрый спад огибающей, EG_DMP)
|
||||
; рег. (0x50+N), data=0xFF — TL=0x7F (макс. затухание) + LD=1
|
||||
; (мгновенно, без интерполяции громкости)
|
||||
; Оба варианта дублируют друг друга (как KeyOff+TL max у FM-банков) —
|
||||
; гарантия тишины независимо от состояния огибающей канала.
|
||||
;
|
||||
; VGM stream: D0 02 68 40 ... D0 02 7F 40 (Key Off+Damp, 24 канала)
|
||||
; D0 02 50 FF ... D0 02 67 FF (TL max, 24 канала)
|
||||
; 66
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_silence_wave:
|
||||
opl4_wave_write #68, #40
|
||||
opl4_wave_write #69, #40
|
||||
opl4_wave_write #6A, #40
|
||||
opl4_wave_write #6B, #40
|
||||
opl4_wave_write #6C, #40
|
||||
opl4_wave_write #6D, #40
|
||||
opl4_wave_write #6E, #40
|
||||
opl4_wave_write #6F, #40
|
||||
opl4_wave_write #70, #40
|
||||
opl4_wave_write #71, #40
|
||||
opl4_wave_write #72, #40
|
||||
opl4_wave_write #73, #40
|
||||
opl4_wave_write #74, #40
|
||||
opl4_wave_write #75, #40
|
||||
opl4_wave_write #76, #40
|
||||
opl4_wave_write #77, #40
|
||||
opl4_wave_write #78, #40
|
||||
opl4_wave_write #79, #40
|
||||
opl4_wave_write #7A, #40
|
||||
opl4_wave_write #7B, #40
|
||||
opl4_wave_write #7C, #40
|
||||
opl4_wave_write #7D, #40
|
||||
opl4_wave_write #7E, #40
|
||||
opl4_wave_write #7F, #40
|
||||
opl4_wave_write #50, #FF
|
||||
opl4_wave_write #51, #FF
|
||||
opl4_wave_write #52, #FF
|
||||
opl4_wave_write #53, #FF
|
||||
opl4_wave_write #54, #FF
|
||||
opl4_wave_write #55, #FF
|
||||
opl4_wave_write #56, #FF
|
||||
opl4_wave_write #57, #FF
|
||||
opl4_wave_write #58, #FF
|
||||
opl4_wave_write #59, #FF
|
||||
opl4_wave_write #5A, #FF
|
||||
opl4_wave_write #5B, #FF
|
||||
opl4_wave_write #5C, #FF
|
||||
opl4_wave_write #5D, #FF
|
||||
opl4_wave_write #5E, #FF
|
||||
opl4_wave_write #5F, #FF
|
||||
opl4_wave_write #60, #FF
|
||||
opl4_wave_write #61, #FF
|
||||
opl4_wave_write #62, #FF
|
||||
opl4_wave_write #63, #FF
|
||||
opl4_wave_write #64, #FF
|
||||
opl4_wave_write #65, #FF
|
||||
opl4_wave_write #66, #FF
|
||||
opl4_wave_write #67, #FF
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SILENCE_AY — Silence AY8910 chip 1
|
||||
;
|
||||
; VGM stream:
|
||||
; A0 07 3F Mixer: all tone+noise off
|
||||
; A0 08 00 Volume A = 0
|
||||
; A0 09 00 Volume B = 0
|
||||
; A0 0A 00 Volume C = 0
|
||||
; 66 End
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_silence_ay:
|
||||
ay_write 7, #3F ; VGM: A0 07 3F
|
||||
ay_write 8, #00 ; VGM: A0 08 00
|
||||
ay_write 9, #00 ; VGM: A0 09 00
|
||||
ay_write 10, #00 ; VGM: A0 0A 00
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SILENCE_AY2 — Silence AY8910 chip 2 (TurboSound)
|
||||
;
|
||||
; VGM stream (bit7 = chip 2):
|
||||
; A0 87 3F Mixer off
|
||||
; A0 88 00 Volume A = 0
|
||||
; A0 89 00 Volume B = 0
|
||||
; A0 8A 00 Volume C = 0
|
||||
; 66 End
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_silence_ay2:
|
||||
ay2_write 7, #3F ; VGM: A0 87 3F
|
||||
ay2_write 8, #00 ; VGM: A0 88 00
|
||||
ay2_write 9, #00 ; VGM: A0 89 00
|
||||
ay2_write 10, #00 ; VGM: A0 8A 00
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SILENCE_SAA — Silence SAA1099 chip 1
|
||||
;
|
||||
; VGM stream:
|
||||
; BD 1C 02 Reset all (sound gen + internal state)
|
||||
; BD 1C 00 Sound enable off
|
||||
; BD 00..05 00 Amplitude ch 0-5 = 0
|
||||
; 66 End
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_silence_saa:
|
||||
saa_write #1C, #02 ; VGM: BD 1C 02 — Reset all
|
||||
saa_write #1C, #00 ; VGM: BD 1C 00 — Sound enable off
|
||||
saa_write #00, #00 ; VGM: BD 00 00 — Amp ch0 = 0
|
||||
saa_write #01, #00 ; VGM: BD 01 00 — Amp ch1 = 0
|
||||
saa_write #02, #00 ; VGM: BD 02 00 — Amp ch2 = 0
|
||||
saa_write #03, #00 ; VGM: BD 03 00 — Amp ch3 = 0
|
||||
saa_write #04, #00 ; VGM: BD 04 00 — Amp ch4 = 0
|
||||
saa_write #05, #00 ; VGM: BD 05 00 — Amp ch5 = 0
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SILENCE_SAA2 — Silence SAA1099 chip 2
|
||||
;
|
||||
; VGM stream (bit7 = chip 2):
|
||||
; BD 9C 02 Reset all
|
||||
; BD 9C 00 Sound enable off
|
||||
; BD 80..85 00 Amplitude ch 0-5 = 0
|
||||
; 66 End
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_silence_saa2:
|
||||
saa2_write #1C, #02 ; VGM: BD 9C 02 — Reset all
|
||||
saa2_write #1C, #00 ; VGM: BD 9C 00 — Sound enable off
|
||||
saa2_write #00, #00 ; VGM: BD 80 00 — Amp ch0 = 0
|
||||
saa2_write #01, #00 ; VGM: BD 81 00 — Amp ch1 = 0
|
||||
saa2_write #02, #00 ; VGM: BD 82 00 — Amp ch2 = 0
|
||||
saa2_write #03, #00 ; VGM: BD 83 00 — Amp ch3 = 0
|
||||
saa2_write #04, #00 ; VGM: BD 84 00 — Amp ch4 = 0
|
||||
saa2_write #05, #00 ; VGM: BD 85 00 — Amp ch5 = 0
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SAA_CLK_ON — Enable SAA1099 + FM clock (MultiSound)
|
||||
;
|
||||
; Uses YM2203 opcode (0x55) so that register 0xF3 is sent raw to 0xFFFD.
|
||||
; AY opcode 0xA0 would strip bit7 for chip selection → can't reach 0xF3.
|
||||
; MultiSound intercepts reg select 0xF0-0xFF for hardware control.
|
||||
; bit0=1(YM2) bit1=1(normal) bit2=0(FM ON) bit3=0(SAA ON)
|
||||
;
|
||||
; VGM stream: 55 F3 00 66
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_saa_clock_on:
|
||||
ym2203_write #F3, #00 ; VGM: 55 F3 00 — SAA+FM clock ON
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SAA_CLK_OFF — Disable SAA1099 + FM clock (MultiSound)
|
||||
;
|
||||
; VGM stream: 55 FF 00 66
|
||||
; bit2=1(FM OFF) bit3=1(SAA OFF)
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_saa_clock_off:
|
||||
ym2203_write #FF, #00 ; VGM: 55 FF 00 — SAA+FM clock OFF
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SILENCE_YM2203 — Silence YM2203 (SSG + FM) + reset prescaler
|
||||
;
|
||||
; Prescaler reset (reg 0x2D, write-only, value ignored):
|
||||
; 55 2D 00 Reset prescaler → defaults: /6 FM, /4 SSG
|
||||
; Prevents state leak from tracks that set /3+/2 via 0x2E.
|
||||
; SSG (AY-compatible):
|
||||
; 55 07 3F Mixer: all tone+noise off
|
||||
; 55 08 00 SSG Volume A = 0
|
||||
; 55 09 00 SSG Volume B = 0
|
||||
; 55 0A 00 SSG Volume C = 0
|
||||
; FM — Key Off all 3 channels:
|
||||
; 55 28 00 Key Off ch 0
|
||||
; 55 28 01 Key Off ch 1
|
||||
; 55 28 02 Key Off ch 2
|
||||
; FM — TL max (0x7F) all 12 operators:
|
||||
; 55 40..42 7F slot 1 (M1): ch 0,1,2
|
||||
; 55 44..46 7F slot 3 (C1): ch 0,1,2
|
||||
; 55 48..4A 7F slot 2 (M2): ch 0,1,2
|
||||
; 55 4C..4E 7F slot 4 (C2): ch 0,1,2
|
||||
; Total: 20 VGM commands + end = 61 bytes
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_silence_ym2203:
|
||||
; ── SSG silence — VGM: 55 rr vv ──
|
||||
ym2203_write 7, #3F ; VGM: 55 07 3F — Mixer off
|
||||
ym2203_write 8, #00 ; VGM: 55 08 00 — SSG Vol A = 0
|
||||
ym2203_write 9, #00 ; VGM: 55 09 00 — SSG Vol B = 0
|
||||
ym2203_write 10, #00 ; VGM: 55 0A 00 — SSG Vol C = 0
|
||||
; ── FM Key Off — VGM: 55 28 xx ──
|
||||
ym2203_write #28, #00 ; VGM: 55 28 00 — Key Off ch 0
|
||||
ym2203_write #28, #01 ; VGM: 55 28 01 — Key Off ch 1
|
||||
ym2203_write #28, #02 ; VGM: 55 28 02 — Key Off ch 2
|
||||
; ── FM TL max — VGM: 55 4x 7F ──
|
||||
ym2203_write #40, #7F ; VGM: 55 40 7F — TL slot1 ch0
|
||||
ym2203_write #41, #7F ; VGM: 55 41 7F — TL slot1 ch1
|
||||
ym2203_write #42, #7F ; VGM: 55 42 7F — TL slot1 ch2
|
||||
ym2203_write #44, #7F ; VGM: 55 44 7F — TL slot3 ch0
|
||||
ym2203_write #45, #7F ; VGM: 55 45 7F — TL slot3 ch1
|
||||
ym2203_write #46, #7F ; VGM: 55 46 7F — TL slot3 ch2
|
||||
ym2203_write #48, #7F ; VGM: 55 48 7F — TL slot2 ch0
|
||||
ym2203_write #49, #7F ; VGM: 55 49 7F — TL slot2 ch1
|
||||
ym2203_write #4A, #7F ; VGM: 55 4A 7F — TL slot2 ch2
|
||||
ym2203_write #4C, #7F ; VGM: 55 4C 7F — TL slot4 ch0
|
||||
ym2203_write #4D, #7F ; VGM: 55 4D 7F — TL slot4 ch1
|
||||
ym2203_write #4E, #7F ; VGM: 55 4E 7F — TL slot4 ch2
|
||||
|
||||
; ── Prescaler reset — VGM: 55 2D 00 ──
|
||||
ym2203_write #2F, #01 ; VGM: 55 2D 00
|
||||
ym2203_write #2D, #01 ; VGM: A5 2E 00
|
||||
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SILENCE_YM2203_2 — Silence YM2203 chip 2 (TurboSound)
|
||||
;
|
||||
; Uses VGM opcode 0xA5 (dual chip: 0x55 → 0xA5) → vgm_fill_buffer
|
||||
; generates CMD_WRITE_AY2 which selects chip 2 via isr_ms_ctrl.
|
||||
;
|
||||
; Prescaler reset:
|
||||
; A5 2D 00 Reset prescaler → defaults: /6 FM, /4 SSG
|
||||
; SSG (chip 2):
|
||||
; A5 07 3F Mixer: all tone+noise off
|
||||
; A5 08 00 SSG Volume A = 0
|
||||
; A5 09 00 SSG Volume B = 0
|
||||
; A5 0A 00 SSG Volume C = 0
|
||||
; FM — Key Off all 3 channels (chip 2):
|
||||
; A5 28 00 Key Off ch 0
|
||||
; A5 28 01 Key Off ch 1
|
||||
; A5 28 02 Key Off ch 2
|
||||
; FM — TL max (0x7F) all 12 operators (chip 2):
|
||||
; A5 40..42 7F slot 1 (M1): ch 0,1,2
|
||||
; A5 44..46 7F slot 3 (C1): ch 0,1,2
|
||||
; A5 48..4A 7F slot 2 (M2): ch 0,1,2
|
||||
; A5 4C..4E 7F slot 4 (C2): ch 0,1,2
|
||||
; Total: 20 VGM commands + end = 61 bytes
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_silence_ym2203_2:
|
||||
; ── SSG silence (chip 2) — VGM: A5 rr vv ──
|
||||
ym2203_2_write 7, #3F ; VGM: A5 07 3F — Mixer off
|
||||
ym2203_2_write 8, #00 ; VGM: A5 08 00 — SSG Vol A = 0
|
||||
ym2203_2_write 9, #00 ; VGM: A5 09 00 — SSG Vol B = 0
|
||||
ym2203_2_write 10, #00 ; VGM: A5 0A 00 — SSG Vol C = 0
|
||||
; ── FM Key Off (chip 2) — VGM: A5 28 xx ──
|
||||
ym2203_2_write #28, #00 ; VGM: A5 28 00 — Key Off ch 0
|
||||
ym2203_2_write #28, #01 ; VGM: A5 28 01 — Key Off ch 1
|
||||
ym2203_2_write #28, #02 ; VGM: A5 28 02 — Key Off ch 2
|
||||
; ── FM TL max (chip 2) — VGM: A5 4x 7F ──
|
||||
ym2203_2_write #40, #7F ; VGM: A5 40 7F — TL slot1 ch0
|
||||
ym2203_2_write #41, #7F ; VGM: A5 41 7F — TL slot1 ch1
|
||||
ym2203_2_write #42, #7F ; VGM: A5 42 7F — TL slot1 ch2
|
||||
ym2203_2_write #44, #7F ; VGM: A5 44 7F — TL slot3 ch0
|
||||
ym2203_2_write #45, #7F ; VGM: A5 45 7F — TL slot3 ch1
|
||||
ym2203_2_write #46, #7F ; VGM: A5 46 7F — TL slot3 ch2
|
||||
ym2203_2_write #48, #7F ; VGM: A5 48 7F — TL slot2 ch0
|
||||
ym2203_2_write #49, #7F ; VGM: A5 49 7F — TL slot2 ch1
|
||||
ym2203_2_write #4A, #7F ; VGM: A5 4A 7F — TL slot2 ch2
|
||||
ym2203_2_write #4C, #7F ; VGM: A5 4C 7F — TL slot4 ch0
|
||||
ym2203_2_write #4D, #7F ; VGM: A5 4D 7F — TL slot4 ch1
|
||||
ym2203_2_write #4E, #7F ; VGM: A5 4E 7F — TL slot4 ch2
|
||||
|
||||
; ── Prescaler reset (chip 2) — VGM: A5 2D 00 ──
|
||||
; ── Prescaler reset — VGM: 55 2D 00 ──
|
||||
ym2203_2_write #2F, #01 ; VGM: 55 2D 00
|
||||
ym2203_2_write #2D, #01 ; VGM: A5 2E 00
|
||||
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SAA_INIT — SAA1099 chip 1 Sound Enable ON
|
||||
;
|
||||
; Writes reg 0x1C = 0x01 to chip 0 (Sound Enable ON).
|
||||
; Parsed via cmdblk → fb_is_vgm==0 → bypasses saa_mode filter.
|
||||
;
|
||||
; VGM stream: BD 1C 01 66
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_saa_init:
|
||||
saa_write #1C, #01 ; VGM: BD 1C 01 — Sound Enable ON (chip 0)
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; CMDBLK_SAA2_INIT — SAA1099 chip 2 Sound Enable ON (+ clock enable)
|
||||
;
|
||||
; Writes reg 0x1C|0x80 = 0x9C to chip 1 (bit7 selects chip 1).
|
||||
; In HDL: address write 0x9C → saa_reg_is_1c=1, then data 0x01 with
|
||||
; d[0]=1 triggers saa2_clk_en (one-way clock enable for chip 2).
|
||||
; Parsed via cmdblk → fb_is_vgm==0 → bypasses saa_mode filter.
|
||||
;
|
||||
; VGM stream: BD 9C 01 66
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
blk_saa2_init:
|
||||
saa2_write #1C, #01 ; VGM: BD 9C 01 — Sound Enable ON (chip 1 + clk)
|
||||
blk_end
|
||||
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
; Padding to 16 KB
|
||||
; ═══════════════════════════════════════════════════════════════════════
|
||||
IF $ < #10000
|
||||
ds #10000 - $, #00
|
||||
ENDIF
|
||||
|
||||
SAVEBIN "build/cmdblocks.bin", #C000, #4000
|
||||
@@ -0,0 +1,160 @@
|
||||
;==============================================================================
|
||||
; @file crt0.s
|
||||
; @brief Стартовый код WC-плагина (SDCC Z80, --no-std-crt0)
|
||||
;
|
||||
; Этот файл заменяет стандартный crt0 SDCC. Он должен линковаться
|
||||
; ПЕРВЫМ в цепочке .rel-файлов, чтобы _plugin_entry оказался
|
||||
; по адресу --code-loc (обычно #8000).
|
||||
;
|
||||
; Входные параметры WC (при CALL plugin_base):
|
||||
; A' = индекс расширения файла
|
||||
; BC = указатель на имя файла (z-строка)
|
||||
; HL = размер файла (младшее слово)
|
||||
; DE = размер файла (старшее слово)
|
||||
; IX = указатель на структуру активной панели WC
|
||||
;
|
||||
; Выход: A = код возврата (0=ESC, 2=следующий, 4=предыдущий)
|
||||
;
|
||||
; Адресное пространство:
|
||||
; #8000-#BFFF = код + данные плагина
|
||||
; #C000-#FFFF = VPL окно (данные VGM через WC API)
|
||||
;
|
||||
; ВАЖНО: gsinit обнуляет _DATA и копирует _INITIALIZER → _INITIALIZED.
|
||||
; Без этого при повторном запуске (NEXT/PREV) стат. переменные
|
||||
; содержат мусор от предыдущего запуска.
|
||||
;==============================================================================
|
||||
|
||||
.module crt0
|
||||
.globl _main
|
||||
.globl _wc_exit_code
|
||||
.globl _wc_file_idx
|
||||
.globl _wc_file_count
|
||||
|
||||
; Линкер-символы для runtime-инициализации C
|
||||
.globl s__DATA
|
||||
.globl l__DATA
|
||||
.globl s__INITIALIZED
|
||||
.globl s__INITIALIZER
|
||||
.globl l__INITIALIZER
|
||||
|
||||
;------------------------------------------------------------------------------
|
||||
; Entry point (WC вызывает по "CALL plugin_base")
|
||||
; Вход:
|
||||
; A' = индекс расширения файла
|
||||
; BC = указатель на имя файла (z-строка)
|
||||
; HL = размер файла (младшее слово)
|
||||
; DE = размер файла (старшее слово)
|
||||
; IX = указатель на структуру активной панели (нужен WC API)
|
||||
;------------------------------------------------------------------------------
|
||||
.area _CODE
|
||||
|
||||
_plugin_entry::
|
||||
;--------------------------------------------------------------
|
||||
; Сохранить параметры WC на стеке: gsinit обнулит _DATA,
|
||||
; поэтому нельзя записывать в глобалы до инициализации.
|
||||
;--------------------------------------------------------------
|
||||
push hl ; size_lo
|
||||
push de ; size_hi
|
||||
push bc ; file_name ptr
|
||||
|
||||
ex af, af'
|
||||
push af ; file_ext (в A)
|
||||
ex af, af'
|
||||
|
||||
;--- Сохранить HL'/DE' (номер объекта / кол-во файлов) ---
|
||||
exx
|
||||
push hl ; file_idx (HL')
|
||||
push de ; file_count (DE')
|
||||
exx
|
||||
|
||||
;--------------------------------------------------------------
|
||||
; Инициализировать C runtime (обнулить _DATA,
|
||||
; скопировать начальные значения в _INITIALIZED)
|
||||
;--------------------------------------------------------------
|
||||
call gsinit
|
||||
|
||||
;--------------------------------------------------------------
|
||||
; Восстановить параметры WC из стека в (теперь чистые) глобалы
|
||||
;--------------------------------------------------------------
|
||||
pop de ; file_count (DE')
|
||||
ld (_wc_file_count), de
|
||||
pop hl ; file_idx (HL')
|
||||
ld (_wc_file_idx), hl
|
||||
|
||||
pop af
|
||||
ld (_wc_file_ext), a
|
||||
|
||||
pop bc
|
||||
ld (_wc_file_name), bc
|
||||
|
||||
pop de ; size_hi
|
||||
pop hl ; size_lo
|
||||
ld (_wc_file_size+0), hl
|
||||
ld (_wc_file_size+2), de
|
||||
|
||||
; Вызвать main()
|
||||
call _main
|
||||
|
||||
; main() вернула void, код выхода — в _wc_exit_code
|
||||
ld a, (_wc_exit_code)
|
||||
|
||||
; IX не менялся — просто возврат
|
||||
ret
|
||||
|
||||
;------------------------------------------------------------------------------
|
||||
; Глобальные переменные параметров WC (читаются из main.c)
|
||||
;------------------------------------------------------------------------------
|
||||
.area _DATA
|
||||
|
||||
_wc_file_ext:: .db 0
|
||||
_wc_file_name:: .dw 0
|
||||
_wc_file_size:: .dw 0, 0 ; 32-bit LE
|
||||
_wc_exit_code:: .db 0
|
||||
_wc_file_idx:: .dw 0 ; HL': номер объекта в панели (от 1)
|
||||
_wc_file_count::.dw 0 ; DE': кол-во объектов в панели (вкл. "..")
|
||||
|
||||
;------------------------------------------------------------------------------
|
||||
; Инициализация C runtime (SDCC: _GSINIT выполняется при старте)
|
||||
;
|
||||
; 1) Обнулить _DATA — все глобальные/статические переменные без
|
||||
; явного инициализатора (аналог BSS).
|
||||
; 2) Скопировать _INITIALIZER → _INITIALIZED — переменные с явным
|
||||
; начальным значением (static uint8_t x = 5; и т.п.).
|
||||
;------------------------------------------------------------------------------
|
||||
.area _GSINIT
|
||||
|
||||
gsinit::
|
||||
; ── 1. Обнулить _DATA ────────────────────────────────────────
|
||||
ld bc, #l__DATA
|
||||
ld a, b
|
||||
or a, c
|
||||
jr z, 00001$ ; l__DATA == 0 → нечего обнулять
|
||||
|
||||
ld hl, #s__DATA
|
||||
ld (hl), #0 ; первый байт = 0
|
||||
dec bc
|
||||
ld a, b
|
||||
or a, c
|
||||
jr z, 00001$ ; l__DATA == 1 → уже готово
|
||||
|
||||
ld d, h
|
||||
ld e, l
|
||||
inc de ; DE = s__DATA + 1
|
||||
ldir ; заполнить остаток нулями
|
||||
00001$:
|
||||
|
||||
; ── 2. Скопировать _INITIALIZER → _INITIALIZED ──────────────
|
||||
ld bc, #l__INITIALIZER
|
||||
ld a, b
|
||||
or a, c
|
||||
jr z, 00002$ ; нет инициализированных данных
|
||||
|
||||
ld hl, #s__INITIALIZER ; источник (в ROM / code page 1)
|
||||
ld de, #s__INITIALIZED ; приёмник (в RAM / _DATA area)
|
||||
ldir
|
||||
00002$:
|
||||
|
||||
.area _GSFINAL
|
||||
|
||||
gsfinal::
|
||||
ret
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,330 @@
|
||||
;==============================================================================
|
||||
; inflate_call.s — SDCC обёртка (sdcccall(1)) для вызова inflate из C
|
||||
;
|
||||
; uint8_t inflate_vgz(uint8_t src_page, uint8_t dst_page);
|
||||
; void copy_pages_to_tap(uint8_t n_pages);
|
||||
;
|
||||
; sdcccall(1): A = первый аргумент
|
||||
; Возврат inflate_vgz: A = 0 (успех) / 1 (ошибка)
|
||||
;
|
||||
; Inflate binary живёт в экстра-странице WMF. Обёртка маппит её в Win 3
|
||||
; (ORG 0xC000) и делает JP 0xC000. Decode buffer — в Win 2 (0x8000),
|
||||
; настраивается самим inflate. При выходе inflate восстанавливает Win 2.
|
||||
;
|
||||
; ╔══════════════════════════════════════════════════════════════════════╗
|
||||
; ║ Почему код inflate обязан быть в Win 3, а не Win 2: ║
|
||||
; ║ DEFLATE backref вычисляет HL = write_ptr − distance. ║
|
||||
; ║ При decode buffer в Win 2 (0x8000) → HL ∈ [0x0000..0xBFFF], ║
|
||||
; ║ никогда не попадает в Win 3 (0xC000+), где живёт код. ║
|
||||
; ║ Если поменять местами (код Win 2, decode Win 3) → HL может ║
|
||||
; ║ попасть в 0x8000–0xBFFF, что потребует ремапа Win 2 во время ║
|
||||
; ║ исполнения из неё — невозможно. ║
|
||||
; ╚══════════════════════════════════════════════════════════════════════╝
|
||||
;
|
||||
; copy_pages_to_tap:
|
||||
; Копирует N страниц из мегабуфера (#20+) в TAP-страницы (#A1+).
|
||||
; DI на время копирования, восстанавливает Win 0/Win 3 из sys vars.
|
||||
;
|
||||
; inflate_vgz:
|
||||
; 1. Сохранить аргументы, IX, SP, Win 1
|
||||
; 2. DI, маппить Win 3 = inflate page через порт
|
||||
; 3. SP = 0xFFF0, push return addr → JP 0xC000
|
||||
; 4. По возврату: SP, Win 1 восстанавливаем, EI, IX → ret
|
||||
; Win 0 / Win 3 — восстанавливает вызывающий C-код через WC API
|
||||
;==============================================================================
|
||||
|
||||
.module inflate_call
|
||||
|
||||
.globl _inflate_vgz
|
||||
.globl _copy_pages_to_tap
|
||||
|
||||
; Progress bar params (filled by C before inflate_vgz)
|
||||
.globl _ifl_pb_text_pg
|
||||
.globl _ifl_pb_attr_ofs
|
||||
.globl _ifl_pb_total
|
||||
.globl _ifl_pb_bw
|
||||
.globl _ifl_pb_green
|
||||
|
||||
;--- Константы ---------------------------------------------------------------
|
||||
INFLATE_PAGE_IDX = 4 ; индекс inflate в плагине (1 code + 3 LUT)
|
||||
; физ. страница = base_page + INFLATE_PAGE_IDX
|
||||
; base_page читается из WC sys var (0x6002)
|
||||
PORT_W0 = 0x10AF ; TSConfig Win 0 page port
|
||||
PORT_W1 = 0x11AF ; TSConfig Win 1 page port
|
||||
PORT_W3 = 0x13AF ; TSConfig Win 3 page port
|
||||
|
||||
;--- DATA (0xB800+) ----------------------------------------------------------
|
||||
.area _DATA
|
||||
|
||||
_ifl_src_pg: .ds 1 ; src_page (физ. стр. TAP)
|
||||
_ifl_dst_pg: .ds 1 ; dst_page (физ. стр. мегабуфера)
|
||||
_ifl_sv_pg1: .ds 1 ; saved Win 1 physical page
|
||||
_ifl_sv_pg2: .ds 1 ; saved Win 2 physical page → inflate D reg
|
||||
_ifl_sv_sp: .ds 2 ; saved SP
|
||||
_ifl_inflate_pg: .ds 1 ; computed inflate phys page (debug/scan)
|
||||
|
||||
; Progress bar parameters (set by C, copied to inflate page at 0xC1F0)
|
||||
_ifl_pb_text_pg: .ds 1 ; text screen physical page
|
||||
_ifl_pb_attr_ofs: .ds 2 ; attr base offset within text page (lo, hi)
|
||||
_ifl_pb_total: .ds 1 ; estimated total output pages (0=disabled)
|
||||
_ifl_pb_bw: .ds 1 ; bar width in columns
|
||||
_ifl_pb_green: .ds 1 ; green attr value
|
||||
|
||||
;--- CODE (0x8000+) ----------------------------------------------------------
|
||||
.area _CODE
|
||||
|
||||
_inflate_vgz::
|
||||
push ix ; callee-saved (SDCC)
|
||||
|
||||
;--- 1. сохранить аргументы ---
|
||||
ld (_ifl_src_pg), a ; A = src_page
|
||||
ld a, l
|
||||
ld (_ifl_dst_pg), a ; L = dst_page
|
||||
|
||||
;--- 2. DI ---
|
||||
di
|
||||
|
||||
;--- 3. сохранить Win 1, Win 2 из WC sys vars (@ 0x6001/0x6002) ---
|
||||
ld a, (0x6001)
|
||||
ld (_ifl_sv_pg1), a
|
||||
ld a, (0x6002)
|
||||
ld (_ifl_sv_pg2), a
|
||||
|
||||
;--- 4. сохранить SP ---
|
||||
ld (_ifl_sv_sp), sp
|
||||
|
||||
.ifdef DBG_ENABLE
|
||||
;--- DBG: 0x01 = saved context, about to map inflate page ---
|
||||
ld bc, #0xFFAF
|
||||
ld a, #0x01
|
||||
out (c), a
|
||||
.endif
|
||||
|
||||
;--- 5. маппить Win 3 = inflate page (dynamic: base + IDX) ---
|
||||
ld a, (0x6002) ; Win 2 phys page = plugin base
|
||||
.ifdef DBG_ENABLE
|
||||
ld bc, #0xFEAF
|
||||
out (c), a ; DBG: FEAF = base page from 0x6002
|
||||
.endif
|
||||
add a, #INFLATE_PAGE_IDX ; + 5 = inflate page
|
||||
ld (_ifl_inflate_pg), a ; save computed inflate page
|
||||
.ifdef DBG_ENABLE
|
||||
ld bc, #0xFDAF
|
||||
out (c), a ; DBG: FDAF = computed inflate page
|
||||
.endif
|
||||
ld bc, #PORT_W3
|
||||
out (c), a
|
||||
|
||||
.ifdef DBG_ENABLE
|
||||
;--- DBG: 0x02 = Win3 mapped to inflate page ---
|
||||
ld bc, #0xFFAF
|
||||
ld a, #0x02
|
||||
out (c), a
|
||||
ld a, (0xC000)
|
||||
ld bc, #0xFEAF
|
||||
out (c), a ; byte[0] — expect 0xF5 (push af)
|
||||
ld a, (0xC001)
|
||||
ld bc, #0xFDAF
|
||||
out (c), a ; byte[1] — expect 0xC5 (push bc)
|
||||
|
||||
;=== DBG: scan pages base+3..base+7 looking for inflate ===
|
||||
ld a, (0x6002)
|
||||
add a, #3
|
||||
ld d, a
|
||||
ld e, #0
|
||||
_ifl_scan_loop:
|
||||
ld a, d
|
||||
ld bc, #PORT_W3
|
||||
out (c), a
|
||||
ld a, #0xD0
|
||||
add a, e
|
||||
ld bc, #0xFFAF
|
||||
out (c), a
|
||||
ld a, d
|
||||
ld bc, #0xFDAF
|
||||
out (c), a
|
||||
ld a, (0xC000)
|
||||
ld bc, #0xFEAF
|
||||
out (c), a
|
||||
inc d
|
||||
inc e
|
||||
ld a, e
|
||||
cp #5
|
||||
jr nz, _ifl_scan_loop
|
||||
|
||||
;--- DBG: 0x04 = scan done, re-map Win3 to inflate page ---
|
||||
ld bc, #0xFFAF
|
||||
ld a, #0x04
|
||||
out (c), a
|
||||
ld a, (_ifl_inflate_pg)
|
||||
ld bc, #PORT_W3
|
||||
out (c), a
|
||||
.endif
|
||||
|
||||
;--- 6. стек в Win 3, push return addr ---
|
||||
ld sp, #0xFFF0
|
||||
ld hl, #_ifl_return
|
||||
push hl
|
||||
|
||||
.ifdef DBG_ENABLE
|
||||
;--- DBG: 0x05 = SP set, return addr pushed, loading regs ---
|
||||
ld bc, #0xFFAF
|
||||
ld a, #0x05
|
||||
out (c), a
|
||||
ld a, (_ifl_src_pg)
|
||||
ld bc, #0xFEAF
|
||||
out (c), a
|
||||
ld a, (_ifl_dst_pg)
|
||||
ld bc, #0xFDAF
|
||||
out (c), a
|
||||
.endif
|
||||
|
||||
;--- 6b. Copy progress bar params to inflate page (0xC1F0+) ---
|
||||
; Win 3 = inflate page (mapped in step 5)
|
||||
; Win 2 = _CODE/_DATA (still accessible for reading _ifl_pb_*)
|
||||
ld a, (_ifl_pb_text_pg)
|
||||
ld (0xC1F0), a
|
||||
ld hl, (_ifl_pb_attr_ofs)
|
||||
ld (0xC1F1), hl
|
||||
ld a, (_ifl_pb_total)
|
||||
ld (0xC1F3), a
|
||||
ld a, (_ifl_pb_bw)
|
||||
ld (0xC1F4), a
|
||||
ld a, (_ifl_pb_green)
|
||||
ld (0xC1F5), a
|
||||
; Initialize runtime state
|
||||
xor a
|
||||
ld (0xC1F6), a ; pb_error = 0
|
||||
ld (0xC1F7), a ; pb_col = 0
|
||||
|
||||
;--- 7. входные регистры inflate: A=src, E=dst, D=saved_pg2 ---
|
||||
ld a, (_ifl_sv_pg2)
|
||||
ld d, a ; D = saved Win 2 page
|
||||
ld a, (_ifl_dst_pg)
|
||||
ld e, a ; E = dst_page
|
||||
ld a, (_ifl_src_pg) ; A = src_page
|
||||
|
||||
.ifdef DBG_ENABLE
|
||||
;--- DBG: 0x06 = about to JP 0xC000 ---
|
||||
push af
|
||||
push bc
|
||||
ld bc, #0xFFAF
|
||||
ld a, #0x06
|
||||
out (c), a
|
||||
pop bc
|
||||
pop af
|
||||
.endif
|
||||
|
||||
;--- 8. запуск inflate ---
|
||||
; JP 0xC200 = inflate_init (safe zone, survives backref LDIR)
|
||||
; НЕ JP 0xC000! Entry zone 0xC000–0xC1FF перезаписывается
|
||||
; LDIR overflow при распаковке backref'ов.
|
||||
jp 0xC200
|
||||
|
||||
;--- сюда вернёмся по RET из inflate ---
|
||||
; A = 0 (ok) / 1 (error)
|
||||
; Win 2 уже восстановлен inflate → _DATA и код доступны
|
||||
_ifl_return:
|
||||
ld e, a ; результат → E
|
||||
|
||||
.ifdef DBG_ENABLE
|
||||
;--- DBG: 0x03 = returned from inflate, A=result ---
|
||||
ld bc, #0xFFAF
|
||||
ld a, #0x03
|
||||
out (c), a
|
||||
ld a, e
|
||||
ld bc, #0xFEAF
|
||||
out (c), a
|
||||
.endif
|
||||
|
||||
;--- восстановить SP (→ C-стек, сразу после push ix) ---
|
||||
ld sp, (_ifl_sv_sp)
|
||||
|
||||
;--- восстановить Win 1 (WC API точка входа @ 0x6006) ---
|
||||
ld a, (_ifl_sv_pg1)
|
||||
ld bc, #PORT_W1
|
||||
out (c), a
|
||||
|
||||
;--- вернуть результат ---
|
||||
ld a, e
|
||||
pop ix ; восстановить IX
|
||||
ei ; EI + задержка 1 instr → ret атомарен
|
||||
ret
|
||||
|
||||
|
||||
;==============================================================================
|
||||
; void copy_pages_to_tap(uint8_t n_pages)
|
||||
;
|
||||
; Копирует n_pages страниц из мегабуфера (#20..#20+n-1) в TAP (#A1..#A1+n-1).
|
||||
; Использует прямые port-записи в Win 0 / Win 3.
|
||||
; DI на время работы; при выходе: Win 0, Win 3 восстановлены, EI.
|
||||
;
|
||||
; sdcccall(1): A = n_pages
|
||||
;==============================================================================
|
||||
|
||||
MEGABUF_START = 0x20 ; первая физ. страница мегабуфера
|
||||
TAP_START = 0xA1 ; первая физ. страница TAP
|
||||
|
||||
_copy_pages_to_tap::
|
||||
or a
|
||||
ret z ; n=0 → ничего делать
|
||||
|
||||
push ix
|
||||
|
||||
ld c, a ; C = n_pages (сохраним)
|
||||
di
|
||||
|
||||
;--- сохранить текущие страницы Win 0 / Win 3 ---
|
||||
ld a, (0x6000)
|
||||
ld (_cpt_sv_pg0), a
|
||||
ld a, (0x6003)
|
||||
ld (_cpt_sv_pg3), a
|
||||
|
||||
;--- подготовить счётчики ---
|
||||
ld d, #MEGABUF_START ; D = src phys page
|
||||
ld e, #TAP_START ; E = dst phys page
|
||||
ld b, c ; B = loop counter
|
||||
|
||||
_cpt_loop:
|
||||
push bc
|
||||
push de
|
||||
|
||||
;--- Map Win 0 = megabuf page D ---
|
||||
ld a, d
|
||||
ld bc, #PORT_W0
|
||||
out (c), a
|
||||
|
||||
;--- Map Win 3 = TAP page E ---
|
||||
ld a, e
|
||||
ld b, #0x13 ; C ещё 0xAF от предыдущего BC
|
||||
out (c), a
|
||||
|
||||
;--- LDIR 16 КБ: Win 0 → Win 3 ---
|
||||
ld hl, #0x0000
|
||||
ld de, #0xC000
|
||||
ld bc, #0x4000
|
||||
ldir
|
||||
|
||||
pop de
|
||||
inc d ; следующая megabuf page
|
||||
inc e ; следующая TAP page
|
||||
pop bc
|
||||
djnz _cpt_loop
|
||||
|
||||
;--- восстановить Win 0 ---
|
||||
ld a, (_cpt_sv_pg0)
|
||||
ld bc, #PORT_W0
|
||||
out (c), a
|
||||
|
||||
;--- восстановить Win 3 ---
|
||||
ld a, (_cpt_sv_pg3)
|
||||
ld b, #0x13
|
||||
out (c), a
|
||||
|
||||
ei
|
||||
pop ix
|
||||
ret
|
||||
|
||||
;--- static vars for copy (в CODE — не в DATA, чтобы не зависеть от Win 2) ---
|
||||
_cpt_sv_pg0: .ds 1
|
||||
_cpt_sv_pg3: .ds 1
|
||||
@@ -0,0 +1,663 @@
|
||||
;==============================================================================
|
||||
; @file isr.s
|
||||
; Формат записи: [VSINTH, VSINTL, HSINT, next_lo, next_hi]
|
||||
; Читается тремя OUTI: BC=#24AF -> VSINTH, #23AF -> VSINTL, #22AF -> HSINT,
|
||||
; затем читается next_ptr.
|
||||
;
|
||||
; Командные буферы: cmd_buf_a, cmd_buf_b (по 512 байт, 128 команд).
|
||||
;
|
||||
; Переменные ISR (volatile, доступны из C через isr.h):
|
||||
; _isr_active_buf : 0=A, 1=B (пишет ISR, читает main)
|
||||
; _isr_enabled : 0/1 (пишет main, читает ISR)
|
||||
; _isr_play_seconds : uint16 (счётчик секунд, пишет ISR)
|
||||
; _isr_read_ptr : uint16 (указатель чтения в активном буфере)
|
||||
;
|
||||
; Расход памяти _DATA:
|
||||
; _im2_table_buf : 512 байт (гарантирует 256-aligned)
|
||||
; _cmd_buf_a/b : 2 x 512 = 1024 байт
|
||||
; Переменные : 14 байт
|
||||
; Итого : ~1550 байт
|
||||
;==============================================================================
|
||||
|
||||
.module isr_mod
|
||||
|
||||
.globl _isr_ms_ctrl
|
||||
.globl _isr_active_buf
|
||||
.globl _isr_enabled
|
||||
.globl _isr_tick_ctr
|
||||
.globl _isr_border_color
|
||||
.globl _isr_play_seconds
|
||||
.globl _isr_read_ptr
|
||||
.globl _isr_done
|
||||
.globl _cmd_buf_a
|
||||
.globl _cmd_buf_b
|
||||
.globl _isr_init
|
||||
.globl _isr_deinit
|
||||
.globl _call_wc_handler
|
||||
.globl _spectrum_levels
|
||||
.globl _vgm_chip_type
|
||||
|
||||
; Импорт таблицы позиций из pos_table.s
|
||||
.globl pos_table
|
||||
|
||||
; Порты TSConfig
|
||||
PORT_HSINT = 0x22AF
|
||||
PORT_VSINTL = 0x23AF
|
||||
PORT_VSINTH = 0x24AF
|
||||
PORT_INTMASK = 0x2AAF
|
||||
PORT_SYSCONF = 0x20AF ; System Config (turbo bits [1:0])
|
||||
TURBO_7MHZ = 0x01 ; SysConf value: 7 MHz
|
||||
TURBO_14MHZ = 0x02 ; SysConf value: 14 MHz
|
||||
|
||||
; Адрес вектора FRAME INT в IM2-таблице WC (I=#5B, vector=0xFF → #5BFF)
|
||||
WC_IM2_VEC = 0x5BFF
|
||||
|
||||
; Коды команд (8 типов, шаг 0x20 + ISR_DONE)
|
||||
CMD_WRITE_AY = 0x00
|
||||
CMD_INC_SEC = 0x10
|
||||
CMD_WRITE_AY2 = 0x20
|
||||
CMD_CALL_WC = 0x30
|
||||
CMD_WRITE_B0 = 0x40
|
||||
CMD_SKIP_TICKS= 0x50
|
||||
CMD_WRITE_SAA = 0x60
|
||||
CMD_WRITE_B1 = 0x80
|
||||
CMD_WRITE_WAVE= 0x70
|
||||
CMD_WAIT = 0xC0
|
||||
CMD_END_BUF = 0xE0
|
||||
CMD_ISR_DONE = 0xF0
|
||||
|
||||
; Отладка: раскрасить бордюр во время ISR (0=выкл, 1=вкл)
|
||||
DEBUG_BORDER = 0
|
||||
|
||||
; Порты OPL3
|
||||
OPL3_ADDR0 = 0xC4
|
||||
OPL3_DATA0 = 0xC5
|
||||
OPL3_ADDR1 = 0xC6
|
||||
OPL3_DATA1 = 0xC7
|
||||
|
||||
; Порты OPL4 Wave-часть (ZXM-MoonSound: MOON_WREG/MOON_WDAT в moon_driver.asm)
|
||||
OPL4_WREG = 0x7E
|
||||
OPL4_WDAT = 0x7F
|
||||
|
||||
; Порты AY-3-8910 / YM2149
|
||||
AY_ADDR = 0xFD ; C-byte, B=0xFF → #FFFD
|
||||
AY_DATA_B = 0xBF ; B-byte, C=#FD → #BFFD
|
||||
|
||||
; Порты SAA1099 (MultiSound card, dual-chip via bit7 of address)
|
||||
; addr=#01FF data=#00FF
|
||||
; bit7 of reg byte written to #01FF selects chip (0=chip0, 1=chip1)
|
||||
; chip1 clk on: automatic on first write to chip1 (address with bit7=1)
|
||||
; both clk off: OUT #FFFD, d[3]=1
|
||||
|
||||
ISR_FREQ = 683 ; informational only (actual timing from pos_table)
|
||||
|
||||
;==============================================================================
|
||||
; isr_init() — Перехват вектора IM2 WC и настройка 683 Hz
|
||||
;==============================================================================
|
||||
.area _CODE
|
||||
|
||||
;==============================================================================
|
||||
; Вместо собственной IM2-таблицы (512 байт) используем существующую
|
||||
; таблицу WC. WC работает в IM2 с I=#5B; вектор FRAME (data bus=0xFF)
|
||||
; читается из адреса #5BFF (2 байта: lo, hi).
|
||||
;
|
||||
; isr_init:
|
||||
; 1. Сохранить текущий вектор (#5BFF) → _isr_saved_vec
|
||||
; 2. Записать адрес _isr_handler в #5BFF
|
||||
; 3. Настроить INTMASK, позицию INT, указатели
|
||||
; 4. IM 2 (для надёжности)
|
||||
; Вызывающий код делает EI.
|
||||
;
|
||||
; isr_deinit:
|
||||
; 1. Восстановить сохранённый вектор WC
|
||||
; 2. Сбросить позицию INT на line=0 (1 INT/кадр)
|
||||
; Вызывающий код делает EI.
|
||||
;==============================================================================
|
||||
_isr_init::
|
||||
di
|
||||
|
||||
;--- Сохранить оригинальный вектор WC ---
|
||||
ld hl, (WC_IM2_VEC)
|
||||
ld (_isr_saved_vec), hl
|
||||
|
||||
;--- Установить наш обработчик ---
|
||||
ld hl, #_isr_handler
|
||||
ld (WC_IM2_VEC), hl
|
||||
|
||||
;--- Разрешить только FRAME INT ---
|
||||
ld bc, #PORT_INTMASK
|
||||
ld a, #0x01
|
||||
out (c), a
|
||||
|
||||
;--- Инициализировать указатель позиции ---
|
||||
ld hl, #pos_table
|
||||
ld (_pos_ptr), hl
|
||||
|
||||
;--- Инициализировать счётчик ожидания = 0 ---
|
||||
ld hl, #0
|
||||
ld (_isr_wait_ctr), hl
|
||||
|
||||
;--- Инициализировать read_ptr → cmd_buf_a ---
|
||||
ld hl, #_cmd_buf_a
|
||||
ld (_isr_read_ptr), hl
|
||||
|
||||
;--- Счётчик тиков ---
|
||||
xor a
|
||||
ld (_isr_tick_ctr), a
|
||||
|
||||
;--- Счётчик секунд ---
|
||||
ld hl, #0
|
||||
ld (_isr_play_seconds), hl
|
||||
|
||||
;--- Reset analyzer previous mask + levels (adjacent) ---
|
||||
xor a
|
||||
ld hl, #_spectrum_prev_mask
|
||||
ld (hl), a
|
||||
ld de, #(_spectrum_prev_mask + 1)
|
||||
ld bc, #17
|
||||
ldir
|
||||
|
||||
;--- Начальная позиция: строка 0, hpos 0 ---
|
||||
ld bc, #PORT_HSINT
|
||||
out (c), a
|
||||
ld b, #>PORT_VSINTL
|
||||
out (c), a
|
||||
ld b, #>PORT_VSINTH
|
||||
out (c), a
|
||||
|
||||
;--- Подтвердить IM2 (WC и так в IM2, но для надёжности) ---
|
||||
im 2
|
||||
ret
|
||||
|
||||
;==============================================================================
|
||||
; isr_deinit() — Восстановить вектор WC и сбросить INT-позиции
|
||||
;==============================================================================
|
||||
_isr_deinit::
|
||||
di
|
||||
|
||||
;--- Проверить: вызывался ли isr_init? ---
|
||||
ld hl, (_isr_saved_vec)
|
||||
ld a, h
|
||||
or l
|
||||
ret z ; saved_vec == 0 → init не вызывался
|
||||
|
||||
;--- Восстановить оригинальный вектор WC ---
|
||||
ld (WC_IM2_VEC), hl
|
||||
|
||||
;--- Сбросить saved_vec (защита от повторного вызова) ---
|
||||
ld hl, #0
|
||||
ld (_isr_saved_vec), hl
|
||||
|
||||
;--- Сбросить позицию INT: line=0, hpos=0 (один INT/кадр) ---
|
||||
ld bc, #PORT_HSINT
|
||||
xor a
|
||||
out (c), a
|
||||
ld b, #>PORT_VSINTL
|
||||
out (c), a
|
||||
ld b, #>PORT_VSINTH
|
||||
out (c), a
|
||||
|
||||
;--- INTMASK = FRAME only (WC по умолчанию) ---
|
||||
ld bc, #PORT_INTMASK
|
||||
ld a, #0x01
|
||||
out (c), a
|
||||
|
||||
; Не делаем EI — вызывающий код решает сам
|
||||
ret
|
||||
|
||||
;==============================================================================
|
||||
; ISR_HANDLER — обработчик прерывания FRAME (2734 Гц)
|
||||
; ─── OUTI-блок и таблицу НЕ ТРОГАТЬ! ────────────────────────────────────
|
||||
;==============================================================================
|
||||
.area _CODE
|
||||
|
||||
_isr_handler::
|
||||
push af
|
||||
.if DEBUG_BORDER
|
||||
ld a, #0x06 ; DEBUG: yellow border
|
||||
out (0xFE), a
|
||||
.endif
|
||||
push hl
|
||||
push bc
|
||||
push de
|
||||
|
||||
;--- OUTI×3: записать следующую позицию INT из таблицы ---
|
||||
; Формат записи: [vsinth_byte, vsintl_byte, hsint_byte, next_lo, next_hi]
|
||||
; BC стартует 0x24AF: OUTI→0x24AF(VSINTH), B→0x23; OUTI→0x23AF(VSINTL), B→0x22; OUTI→0x22AF(HSINT)
|
||||
ld hl, (_pos_ptr)
|
||||
ld bc, #0x25AF ; B=#25, C=#AF
|
||||
outi ; → VSINTH (#24AF) outi predecrement B: B=0x24, C=0xAF
|
||||
outi ; → VSINTL (#23AF)
|
||||
outi ; → HSINT (#22AF)
|
||||
; Следующий ptr (2 байта)
|
||||
ld a, (hl)
|
||||
inc hl
|
||||
ld h, (hl)
|
||||
ld l, a ; HL = next_ptr
|
||||
ld (_pos_ptr), hl
|
||||
|
||||
;--- Счётчик тиков: простой INC byte (21T, всегда) ---
|
||||
ld hl, #_isr_tick_ctr
|
||||
inc (hl) ; 11T: wrap at 256 — main loop считает секунды
|
||||
|
||||
;--- WAIT_CTR: если 0 — выполнять команды ---
|
||||
ld a, (_isr_enabled)
|
||||
or a
|
||||
jp z, _isr_exit ; не активен — быстрый выход
|
||||
ld hl, (_isr_wait_ctr)
|
||||
ld a, h
|
||||
or l
|
||||
jp z, _isr_exec_cmd
|
||||
dec hl
|
||||
ld (_isr_wait_ctr), hl
|
||||
|
||||
_isr_exit:
|
||||
.if DEBUG_BORDER
|
||||
ld a, (_isr_border_color) ; DEBUG: restore border from variable
|
||||
out (0xFE), a
|
||||
.endif
|
||||
pop de
|
||||
pop bc
|
||||
pop hl
|
||||
pop af
|
||||
ei
|
||||
ret
|
||||
|
||||
;--- Выполнение команд --------------------------------------------------
|
||||
_isr_exec_cmd:
|
||||
ld hl, (_isr_read_ptr)
|
||||
ex de, hl ; DE = указатель команд
|
||||
|
||||
_isr_cmd_loop:
|
||||
ld a, (de)
|
||||
inc de
|
||||
; Dispatch: command types — hot path first
|
||||
; OPL Bank 0 writes (most common register writes)
|
||||
cp #CMD_WRITE_B0
|
||||
jp z, _isr_write_b0
|
||||
; ★ CMD_SKIP_TICKS — every ISR tick ends with this (promoted from #12)
|
||||
cp #CMD_SKIP_TICKS
|
||||
jp z, _isr_skip_ticks
|
||||
; OPL Bank 1 writes (OPL3 second bank)
|
||||
cp #CMD_WRITE_B1
|
||||
jp z, _isr_write_b1
|
||||
; Wait (long pauses, less common than SKIP_TICKS)
|
||||
cp #CMD_WAIT
|
||||
jp z, _isr_do_wait
|
||||
; AY/SAA/end commands (less frequent per-interrupt)
|
||||
cp #CMD_WRITE_AY
|
||||
jp z, _isr_write_ay
|
||||
cp #CMD_WRITE_AY2
|
||||
jp z, _isr_write_ay2
|
||||
cp #CMD_WRITE_SAA
|
||||
jp z, _isr_write_saa
|
||||
; OPL4 Wave-часть (ZXM-MoonSound), редко используется
|
||||
cp #CMD_WRITE_WAVE
|
||||
jp z, _isr_write_wave
|
||||
; Rare commands
|
||||
cp #CMD_END_BUF
|
||||
jp z, _isr_do_end_buf
|
||||
cp #CMD_ISR_DONE
|
||||
jp z, _isr_cmd_done
|
||||
cp #CMD_INC_SEC
|
||||
jp z, _isr_inc_sec
|
||||
cp #CMD_CALL_WC
|
||||
jp z, _isr_call_wc
|
||||
; Unknown command: skip 3 bytes, continue
|
||||
inc de
|
||||
inc de
|
||||
inc de
|
||||
jp _isr_cmd_loop
|
||||
|
||||
_isr_do_end_buf:
|
||||
ld a, (_isr_active_buf)
|
||||
xor #1
|
||||
ld (_isr_active_buf), a
|
||||
jr nz, _isr_end_use_b
|
||||
ld hl, #_cmd_buf_a
|
||||
jr _isr_end_save_ptr
|
||||
_isr_end_use_b:
|
||||
ld hl, #_cmd_buf_b
|
||||
_isr_end_save_ptr:
|
||||
ld (_isr_read_ptr), hl
|
||||
ex de, hl
|
||||
jp _isr_cmd_loop
|
||||
|
||||
_isr_write_b0:
|
||||
ld bc, #PORT_SYSCONF
|
||||
ld a, #TURBO_7MHZ
|
||||
out (c), a ; → 7 MHz
|
||||
ld a, (de) ; reg
|
||||
ld c, #OPL3_ADDR0 ; B=#20, don't-care for C4-C7
|
||||
out (c), a
|
||||
inc de
|
||||
ld a, (de) ; val
|
||||
inc c ; C5 = data port
|
||||
out (c), a
|
||||
ld c, #0xAF ; BC=#20AF (B still #20)
|
||||
ld a, #TURBO_14MHZ
|
||||
out (c), a ; → 14 MHz
|
||||
inc de
|
||||
inc de ; pad
|
||||
jp _isr_cmd_loop
|
||||
|
||||
_isr_write_b1:
|
||||
ld bc, #PORT_SYSCONF
|
||||
ld a, #TURBO_7MHZ
|
||||
out (c), a ; → 7 MHz
|
||||
ld a, (de)
|
||||
ld c, #OPL3_ADDR1 ; B=#20, don't-care for C4-C7
|
||||
out (c), a
|
||||
inc de
|
||||
ld a, (de)
|
||||
inc c ; C7 = data port
|
||||
out (c), a
|
||||
ld c, #0xAF ; BC=#20AF (B still #20)
|
||||
ld a, #TURBO_14MHZ
|
||||
out (c), a ; → 14 MHz
|
||||
inc de
|
||||
inc de
|
||||
jp _isr_cmd_loop
|
||||
|
||||
_isr_do_wait:
|
||||
ld a, (de) ; lo
|
||||
inc de
|
||||
ld l, a
|
||||
ld a, (de) ; hi
|
||||
inc de
|
||||
ld h, a
|
||||
inc de ; skip pad byte
|
||||
ld (_isr_wait_ctr), hl
|
||||
ex de, hl
|
||||
ld (_isr_read_ptr), hl
|
||||
jp _isr_exit
|
||||
|
||||
;--- AY8910 chip 1 write: [reg, val, pad] --------------------------------
|
||||
_isr_write_ay:
|
||||
ld bc, #PORT_SYSCONF
|
||||
ld a, #TURBO_7MHZ
|
||||
out (c), a ; → 7 MHz
|
||||
ld bc, #0xFFFD
|
||||
ld a, (_isr_ms_ctrl) ; MultiSound ctrl (bit0=0 → chip1)
|
||||
out (c), a
|
||||
ld a, (de) ; reg
|
||||
out (c), a ; select AY register
|
||||
inc de
|
||||
ld a, (de) ; val
|
||||
ld b, #0xBF ; BC = #BFFD
|
||||
out (c), a ; write data
|
||||
ld bc, #PORT_SYSCONF
|
||||
ld a, #TURBO_14MHZ
|
||||
out (c), a ; → 14 MHz
|
||||
inc de
|
||||
inc de ; pad
|
||||
jp _isr_cmd_loop
|
||||
|
||||
;--- AY8910 chip 2 write (TurboSound): [reg, val, pad] -------------------
|
||||
_isr_write_ay2:
|
||||
ld bc, #PORT_SYSCONF
|
||||
ld a, #TURBO_7MHZ
|
||||
out (c), a ; → 7 MHz
|
||||
ld bc, #0xFFFD
|
||||
ld a, (_isr_ms_ctrl) ; MultiSound ctrl base
|
||||
or a, #0x01 ; bit0=1 → chip2
|
||||
out (c), a
|
||||
ld a, (de) ; reg
|
||||
out (c), a ; select register
|
||||
inc de
|
||||
ld a, (de) ; val
|
||||
ld b, #0xBF ; BC = #BFFD
|
||||
out (c), a
|
||||
ld bc, #PORT_SYSCONF
|
||||
ld a, #TURBO_14MHZ
|
||||
out (c), a ; → 14 MHz
|
||||
inc de
|
||||
inc de ; pad
|
||||
jp _isr_cmd_loop
|
||||
|
||||
;--- SAA1099 write: [reg, val, pad] ----------------------------------------
|
||||
; reg bit7 = chip select (0=chip0, 1=chip1), passed directly to #01FF.
|
||||
; HDL uses bit7 for CS routing; SAA1099 ignores upper address bits.
|
||||
_isr_write_saa:
|
||||
ld bc, #PORT_SYSCONF
|
||||
ld a, #TURBO_7MHZ
|
||||
out (c), a ; → 7 MHz
|
||||
ld a, (de) ; reg (bit7 = chip select)
|
||||
ld bc, #0x01FF ; SAA address port
|
||||
out (c), a ; write register address (bit7 → chip CS)
|
||||
inc de
|
||||
ld a, (de) ; val
|
||||
ld b, #0x00 ; BC = #00FF SAA data port
|
||||
out (c), a
|
||||
ld bc, #PORT_SYSCONF
|
||||
ld a, #TURBO_14MHZ
|
||||
out (c), a ; → 14 MHz
|
||||
inc de
|
||||
inc de ; pad
|
||||
jp _isr_cmd_loop
|
||||
|
||||
;--- OPL4 Wave-часть write (ZXM-MoonSound): [reg, val, pad] --------------
|
||||
; Порты #7E (addr) / #7F (data) — как MOON_WREG/MOON_WDAT в оригинальном
|
||||
; Z80-драйвере карты (moon_driver.asm). B don't-care (карта декодирует
|
||||
; только младший байт адреса), как и для OPL3_ADDR0..OPL3_DATA1.
|
||||
;
|
||||
; ВАЖНО: в отличие от FM-банков (isr_write_b0/b1), референсный NedoOS-
|
||||
; драйвер (common/moonsound.asm: opl4_wait) ждёт busy-флаг (бит0 порта
|
||||
; #C4) перед КАЖДОЙ записью в порты #7E/#7F, включая обычные регистры
|
||||
; wave-канала (не только memory-access режим) — без этого запись может
|
||||
; быть потеряна чипом. См. chip_wait()/wave_out_reg()/wave_out_dat() в
|
||||
; lib/vgm.c (та же логика для синхронной заливки сэмплов).
|
||||
_isr_write_wave:
|
||||
ld bc, #PORT_SYSCONF
|
||||
ld a, #TURBO_7MHZ
|
||||
out (c), a ; → 7 MHz
|
||||
ld c, #OPL4_WREG ; B=#20, don't-care (как MOON_WREG)
|
||||
wwave_wait1:
|
||||
in a, (0xC4) ; busy-флаг (бит0)
|
||||
rrca
|
||||
jr c, wwave_wait1
|
||||
ld a, (de) ; reg
|
||||
out (c), a
|
||||
inc de
|
||||
inc c ; C=#7F = data port (MOON_WDAT)
|
||||
wwave_wait2:
|
||||
in a, (0xC4)
|
||||
rrca
|
||||
jr c, wwave_wait2
|
||||
ld a, (de) ; val
|
||||
out (c), a
|
||||
ld bc, #PORT_SYSCONF
|
||||
ld a, #TURBO_14MHZ
|
||||
out (c), a ; → 14 MHz
|
||||
inc de
|
||||
inc de ; pad
|
||||
jp _isr_cmd_loop
|
||||
|
||||
;--- CMD_INC_SEC: инкремент счётчика секунд [0,0,0] ---------------------
|
||||
_isr_inc_sec:
|
||||
inc de
|
||||
inc de
|
||||
inc de ; пропустить 3 байта параметров
|
||||
ld hl, (_isr_play_seconds)
|
||||
inc hl
|
||||
ld (_isr_play_seconds), hl
|
||||
jp _isr_cmd_loop
|
||||
|
||||
;--- CMD_SKIP_TICKS: skip N + spectrum bitmask [N,smask_lo,smask_hi] --------
|
||||
; CMD_SKIP_TICKS(N):
|
||||
; N=0 — не перепрограммирует, просто exit (1 тик пауза)
|
||||
; N>=1 — пропустить N записей, перепрограммировать INT (N+1 тиков пауза)
|
||||
; Max N=55 (кадр минус 1).
|
||||
_isr_skip_ticks:
|
||||
ld a, (de) ; N = skip count (0..55)
|
||||
inc de
|
||||
;--- Spectrum: KeyOn attack + sustained activity floor ---
|
||||
push af ; save N
|
||||
ld a, (de) ; mask_lo
|
||||
inc de
|
||||
ld l, a ; L = mask_lo
|
||||
ld a, (de) ; mask_hi
|
||||
inc de
|
||||
ld h, a ; H = mask_hi
|
||||
push de ; save read_ptr
|
||||
|
||||
; OPL4 gets edge/sustain processing. Other chips keep the old
|
||||
; event-analyzer behaviour by comparing every mask with zero.
|
||||
ld a, (_vgm_chip_type)
|
||||
cp #4 ; VGM_CHIP_OPL4
|
||||
jr z, _skip_spec_opl4
|
||||
ld de, #0
|
||||
jr _skip_spec_masks_ready
|
||||
_skip_spec_opl4:
|
||||
ld de, (_spectrum_prev_mask)
|
||||
ld (_spectrum_prev_mask), hl
|
||||
_skip_spec_masks_ready:
|
||||
; E/D are previous low/high, L/H are current low/high.
|
||||
ld c, e ; previous low byte
|
||||
ld e, h ; preserve current high byte
|
||||
ld a, l ; current low byte
|
||||
ld hl, #_spectrum_levels
|
||||
call _skip_spec_byte
|
||||
ld c, d ; previous high byte
|
||||
ld a, e ; current high byte
|
||||
call _skip_spec_byte
|
||||
pop de ; restore read_ptr
|
||||
pop af ; restore N
|
||||
; Сохранить read_ptr
|
||||
ex de, hl
|
||||
ld (_isr_read_ptr), hl
|
||||
; N=0: просто exit (как CMD_WAIT(0) без накладных на wait_ctr)
|
||||
or a
|
||||
jp z, _isr_exit
|
||||
; Пропустить N записей pos_table: перейти по N-1 ссылкам,
|
||||
; затем OUTI×3 из N-ой записи (перепрограммировать INT)
|
||||
ld hl, (_pos_ptr)
|
||||
dec a ; N-1 линков пропустить
|
||||
jr z, _skip_program ; N=1: сразу программируем текущую
|
||||
ld b, a
|
||||
_skip_loop:
|
||||
inc hl ; пропустить VSINTH
|
||||
inc hl ; VSINTL
|
||||
inc hl ; HSINT
|
||||
ld a, (hl) ; next_lo
|
||||
inc hl
|
||||
ld h, (hl) ; next_hi
|
||||
ld l, a
|
||||
djnz _skip_loop
|
||||
_skip_program:
|
||||
; HL = запись для перепрограммирования INT портов
|
||||
ld bc, #0x25AF
|
||||
outi ; VSINTH (#24AF)
|
||||
outi ; VSINTL (#23AF)
|
||||
outi ; HSINT (#22AF)
|
||||
; Следующий next_ptr
|
||||
ld a, (hl)
|
||||
inc hl
|
||||
ld h, (hl)
|
||||
ld l, a
|
||||
ld (_pos_ptr), hl
|
||||
jp _isr_exit
|
||||
|
||||
; Unpack one activity byte into eight spectrum bars.
|
||||
; A=current bits, C=previous bits, HL=first level; advances HL by 8.
|
||||
; Rising edge: peak 8. Sustained KeyOn: decay is allowed down to floor 3.
|
||||
_skip_spec_byte:
|
||||
ld b, #8
|
||||
_skip_spec_bit:
|
||||
rr c ; carry = previous activity bit
|
||||
jr c, _skip_spec_was_on
|
||||
rra ; carry = current activity bit
|
||||
jr nc, _skip_spec_next
|
||||
ld (hl), #8 ; new KeyOn attack
|
||||
jr _skip_spec_next
|
||||
_skip_spec_was_on:
|
||||
rra ; carry = current activity bit
|
||||
jr nc, _skip_spec_next ; KeyOff: ordinary decay
|
||||
push af ; preserve remaining current bits
|
||||
ld a, (hl)
|
||||
cp #3
|
||||
jr nc, _skip_spec_floor_ok
|
||||
ld (hl), #3 ; sustained activity floor
|
||||
_skip_spec_floor_ok:
|
||||
pop af
|
||||
_skip_spec_next:
|
||||
inc hl
|
||||
djnz _skip_spec_bit
|
||||
ret
|
||||
|
||||
;--- CMD_ISR_DONE: заморозить ISR, выставить флаг [0,0,0] ------------------
|
||||
; ISR зацикливается на этой команде, не переключает буферы.
|
||||
; Main loop ждёт isr_done == 1 перед завершением.
|
||||
_isr_cmd_done:
|
||||
inc de
|
||||
inc de
|
||||
inc de ; пропустить 3 байта параметров
|
||||
; Выставить флаг готовности к завершению
|
||||
ld a, #1
|
||||
ld (_isr_done), a
|
||||
; Перемотать read_ptr на начало CMD_ISR_DONE (DE - 4)
|
||||
ex de, hl
|
||||
dec hl
|
||||
dec hl
|
||||
dec hl
|
||||
dec hl
|
||||
ld (_isr_read_ptr), hl
|
||||
jp _isr_exit
|
||||
|
||||
;--- CMD_CALL_WC: вызов оригинального WC handler [0,0,0] ------------------
|
||||
; Внутренняя команда ISR (не используется buffer filler, зарезервировано)
|
||||
_isr_call_wc:
|
||||
inc de
|
||||
inc de
|
||||
inc de ; пропустить 3 байта параметров
|
||||
jp _isr_cmd_loop ; просто пропустить
|
||||
|
||||
;==============================================================================
|
||||
; call_wc_handler() — Вызов WC ISR из main loop (C-callable)
|
||||
;
|
||||
; WC ожидает периодический вызов своего ISR для обновления TMN,
|
||||
; клавиатуры, часов и др. Вызываем из main loop ~50 Гц.
|
||||
;
|
||||
; Механизм: DI → JP (saved_vec). WC handler делает EI+RET
|
||||
; обратно к вызывающему коду (tail-call через JP).
|
||||
;==============================================================================
|
||||
_call_wc_handler::
|
||||
ld hl, (_isr_saved_vec)
|
||||
ld a, h
|
||||
or l
|
||||
ret z ; saved_vec==0 → isr_init не вызывался
|
||||
di
|
||||
jp (hl) ; tail-call: WC handler делает EI+RET
|
||||
|
||||
;==============================================================================
|
||||
.area _DATA
|
||||
|
||||
_isr_ms_ctrl:: .db 0xFE ; MultiSound ctrl: FM OFF + SAA OFF (idle)
|
||||
_isr_active_buf:: .db 0
|
||||
_isr_enabled:: .db 0
|
||||
_isr_tick_ctr:: .db 0 ; инкрементируется каждые ISR тик (2734/сек, wrap 256)
|
||||
_isr_border_color:: .db 0 ; цвет бордюра при выходе из ISR (пишет main, читает ISR)
|
||||
_isr_play_seconds:: .dw 0 ; инкрементируется CMD_INC_SEC
|
||||
_isr_read_ptr:: .dw 0
|
||||
_isr_wait_ctr:: .dw 0
|
||||
_isr_done:: .db 0 ; 1 = ISR замер на CMD_ISR_DONE, готов к завершению
|
||||
_pos_ptr: .dw 0 ; инициализируется в isr_init()
|
||||
_isr_saved_vec: .dw 0 ; сохранённый вектор WC (#5BFF)
|
||||
|
||||
;==============================================================================
|
||||
; Spectrum analyzer levels (16 bars, updated by 16-bit bitmask in CMD_SKIP_TICKS)
|
||||
;==============================================================================
|
||||
_spectrum_prev_mask:
|
||||
.ds 2 ; previous CMD_SKIP activity bits
|
||||
_spectrum_levels::
|
||||
.ds 16 ; 16 bytes, one per bar (0-8)
|
||||
|
||||
;==============================================================================
|
||||
; Командные буферы (512 байт каждый)
|
||||
;==============================================================================
|
||||
|
||||
.even
|
||||
_cmd_buf_a::
|
||||
.ds 512
|
||||
_cmd_buf_b::
|
||||
.ds 512
|
||||
@@ -0,0 +1,335 @@
|
||||
;==============================================================================
|
||||
; @file opl3.s
|
||||
; @brief OPL3/AY/SAA register operations for SDCC Z80 (sdcccall(1))
|
||||
;
|
||||
; Порты TSConfig для OPL3:
|
||||
; #C4 = Bank 0 Address #C5 = Bank 0 Data
|
||||
; #C6 = Bank 1 Address #C7 = Bank 1 Data
|
||||
;==============================================================================
|
||||
|
||||
.module opl3
|
||||
.area _CODE
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; opl3_write_b0(uint8_t addr, uint8_t data) — Bank 0
|
||||
;----------------------------------------------------------------------
|
||||
.globl _opl3_write_b0
|
||||
_opl3_write_b0::
|
||||
ld c, #0xC4
|
||||
jr _opl3_write
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; opl3_write_b1(uint8_t addr, uint8_t data) — Bank 1
|
||||
;----------------------------------------------------------------------
|
||||
.globl _opl3_write_b1
|
||||
_opl3_write_b1::
|
||||
ld c, #0xC6
|
||||
_opl3_write:
|
||||
out (c), a ; addr (B don't-care for C4-C7)
|
||||
ld hl, #2
|
||||
add hl, sp
|
||||
ld a, (hl)
|
||||
inc c
|
||||
out (c), a ; data
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; tsconf_out(uint16_t port, uint8_t val)
|
||||
;----------------------------------------------------------------------
|
||||
.globl _tsconf_out
|
||||
_tsconf_out::
|
||||
ld b, d
|
||||
ld c, e
|
||||
ld hl, #2
|
||||
add hl, sp
|
||||
ld a, (hl)
|
||||
out (c), a
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; opl3_init() — OPL3 enable + wavesel + reset (falls through)
|
||||
;----------------------------------------------------------------------
|
||||
.globl _opl3_init
|
||||
_opl3_init::
|
||||
ld a, #0x05
|
||||
ld c, #0xC6
|
||||
out (c), a ; B don't-care for C4-C7
|
||||
ld a, #0x01 ; NEW bit only (OPL3 enable)
|
||||
inc c
|
||||
out (c), a
|
||||
ld a, #0x01
|
||||
ld c, #0xC4
|
||||
out (c), a
|
||||
ld a, #0x20
|
||||
inc c
|
||||
out (c), a
|
||||
; fall through to opl3_reset
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; opl3_reset() — Full OPL3 silence: zero ALL significant registers
|
||||
; Both banks: Key-Off (B0-B8=0), Total Level max atten (40-55=3F),
|
||||
; Percussion off (BD=0), F-Num (A0-A8=0), Feedback (C0-C8=0),
|
||||
; AM/Vib/Mult (20-35=0), AR/DR (60-75=0), SL/RR (80-95=0),
|
||||
; Waveform (E0-F5=0).
|
||||
; Uses _opl3_zero_range and _opl3_fill_range helpers.
|
||||
;----------------------------------------------------------------------
|
||||
.globl _opl3_reset
|
||||
_opl3_reset::
|
||||
; ── Bank 0 ──
|
||||
ld c, #0xC4
|
||||
call _opl3_reset_bank
|
||||
|
||||
; Percussion off: BD = 0 (Bank 0 only)
|
||||
ld a, #0xBD
|
||||
out (c), a
|
||||
inc c
|
||||
xor a, a
|
||||
out (c), a
|
||||
dec c
|
||||
|
||||
; ── Bank 1 ──
|
||||
ld c, #0xC6
|
||||
; fall through to _opl3_reset_bank
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; _opl3_reset_bank — zero all significant regs of one OPL3 bank
|
||||
; C = addr port (0xC4 or 0xC6)
|
||||
; Destroys: A, B, D, E
|
||||
;----------------------------------------------------------------------
|
||||
_opl3_reset_bank:
|
||||
; KeyOff: B0-B8 = 0
|
||||
ld d, #0xB0
|
||||
ld e, #9
|
||||
call _opl3_zero_range
|
||||
|
||||
; Total Level max attenuation: 40-55 = 0x3F
|
||||
ld d, #0x40
|
||||
ld e, #22
|
||||
ld a, #0x3F
|
||||
call _opl3_fill_range
|
||||
|
||||
; F-Num lo: A0-A8 = 0
|
||||
ld d, #0xA0
|
||||
ld e, #9
|
||||
call _opl3_zero_range
|
||||
|
||||
; Feedback/Output: C0-C8 = 0
|
||||
ld d, #0xC0
|
||||
ld e, #9
|
||||
call _opl3_zero_range
|
||||
|
||||
; AM/Vib/Mult: 20-35 = 0
|
||||
ld d, #0x20
|
||||
ld e, #22
|
||||
call _opl3_zero_range
|
||||
|
||||
; AR/DR: 60-75 = 0
|
||||
ld d, #0x60
|
||||
ld e, #22
|
||||
call _opl3_zero_range
|
||||
|
||||
; SL/RR: 80-95 = 0
|
||||
ld d, #0x80
|
||||
ld e, #22
|
||||
call _opl3_zero_range
|
||||
|
||||
; Waveform: E0-F5 = 0
|
||||
ld d, #0xE0
|
||||
ld e, #22
|
||||
jr _opl3_zero_range ; tail-call → ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; _opl3_zero_range — fill E consecutive regs starting at D with 0
|
||||
; C = port, D = start reg, E = count
|
||||
;----------------------------------------------------------------------
|
||||
_opl3_zero_range:
|
||||
xor a, a
|
||||
; fall through
|
||||
;----------------------------------------------------------------------
|
||||
; _opl3_fill_range — fill E consecutive regs starting at D with value A
|
||||
; C = port (0xC4 for Bank0, 0xC6 for Bank1)
|
||||
; D = start register, E = count, A = value to write
|
||||
; Destroys: A, B, D, E
|
||||
;----------------------------------------------------------------------
|
||||
_opl3_fill_range:
|
||||
ld b, a ; B = value to write
|
||||
_opl3_fr_lp:
|
||||
ld a, d
|
||||
out (c), a ; write register address
|
||||
inc c
|
||||
ld a, b
|
||||
out (c), a ; write value
|
||||
dec c
|
||||
inc d
|
||||
dec e
|
||||
jr nz, _opl3_fr_lp
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; Общая подпрограмма: silence текущего AY чипа. C уже = 0xFD.
|
||||
; Reg 7 = 0x3F (mixer off), Regs 8-10 = 0 (volume off)
|
||||
;----------------------------------------------------------------------
|
||||
_ay_silence_core:
|
||||
ld b, #0xFF
|
||||
ld a, #7
|
||||
out (c), a
|
||||
ld b, #0xBF
|
||||
ld a, #0x3F
|
||||
out (c), a
|
||||
ld d, #8
|
||||
_ayc_lp:
|
||||
ld b, #0xFF
|
||||
ld a, d
|
||||
out (c), a
|
||||
ld b, #0xBF
|
||||
xor a, a
|
||||
out (c), a
|
||||
inc d
|
||||
ld a, d
|
||||
cp a, #11
|
||||
jr c, _ayc_lp
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; ay_write_reg(uint8_t reg, uint8_t val) — write AY chip 1 register
|
||||
; reg in A, val on stack
|
||||
;----------------------------------------------------------------------
|
||||
.globl _ay_write_reg
|
||||
_ay_write_reg::
|
||||
ld c, #0xFD
|
||||
ld b, #0xFF
|
||||
out (c), a ; select register
|
||||
ld hl, #2
|
||||
add hl, sp
|
||||
ld a, (hl)
|
||||
ld b, #0xBF
|
||||
out (c), a ; write value
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; ay2_write_reg(uint8_t reg, uint8_t val) — write AY chip 2 register
|
||||
;----------------------------------------------------------------------
|
||||
.globl _ay2_write_reg
|
||||
_ay2_write_reg::
|
||||
ld c, #0xFD
|
||||
ld b, #0xFF
|
||||
push af ; save reg
|
||||
ld a, #0xFE
|
||||
out (c), a ; select chip 2
|
||||
pop af
|
||||
ld b, #0xFF
|
||||
out (c), a ; select register
|
||||
ld hl, #2
|
||||
add hl, sp
|
||||
ld a, (hl)
|
||||
ld b, #0xBF
|
||||
out (c), a ; write value
|
||||
ld b, #0xFF
|
||||
ld a, #0xFF
|
||||
out (c), a ; switch back to chip 1
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; saa_write_reg(uint8_t reg, uint8_t val) — SAA1099 chip 1
|
||||
; addr=#00FF, data=#01FF
|
||||
;----------------------------------------------------------------------
|
||||
.globl _saa_write_reg
|
||||
_saa_write_reg::
|
||||
ld c, #0xFF
|
||||
ld b, #0x00
|
||||
out (c), a ; addr = reg
|
||||
ld hl, #2
|
||||
add hl, sp
|
||||
ld a, (hl)
|
||||
inc b ; B = 0x01
|
||||
out (c), a ; data = val
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; saa2_write_reg(uint8_t reg, uint8_t val) — SAA1099 chip 2
|
||||
; addr=#02FF, data=#03FF
|
||||
;----------------------------------------------------------------------
|
||||
.globl _saa2_write_reg
|
||||
_saa2_write_reg::
|
||||
ld c, #0xFF
|
||||
ld b, #0x02
|
||||
out (c), a ; addr = reg
|
||||
ld hl, #2
|
||||
add hl, sp
|
||||
ld a, (hl)
|
||||
inc b ; B = 0x03
|
||||
out (c), a ; data = val
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; ay_silence() — AY chip 1 (FFFD/BFFD)
|
||||
;----------------------------------------------------------------------
|
||||
.globl _ay_silence
|
||||
_ay_silence::
|
||||
ld c, #0xFD
|
||||
jr _ay_silence_core
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; ay2_silence() — TurboSound chip 2: select → silence → switch back
|
||||
;----------------------------------------------------------------------
|
||||
.globl _ay2_silence
|
||||
_ay2_silence::
|
||||
ld c, #0xFD
|
||||
ld b, #0xFF
|
||||
ld a, #0xFE
|
||||
out (c), a ; select chip 2
|
||||
call _ay_silence_core
|
||||
ld b, #0xFF
|
||||
ld a, #0xFF
|
||||
out (c), a ; switch back to chip 1
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; Общая подпрограмма: silence SAA чипа. C=0xFF, B=addr port hi.
|
||||
; Data port = B+1 (inc b / dec b).
|
||||
; Reset (0x1C=02), disable (0x1C=00), amplitude regs 0x00-0x05=0.
|
||||
;----------------------------------------------------------------------
|
||||
_saa_silence_core:
|
||||
ld a, #0x1C
|
||||
out (c), a ; addr
|
||||
inc b
|
||||
ld a, #0x02
|
||||
out (c), a ; data: reset
|
||||
dec b
|
||||
ld a, #0x1C
|
||||
out (c), a ; addr
|
||||
inc b
|
||||
xor a, a
|
||||
out (c), a ; data: sound off
|
||||
dec b
|
||||
ld d, #0
|
||||
_saac_lp:
|
||||
ld a, d
|
||||
out (c), a ; addr: reg N
|
||||
inc b
|
||||
xor a, a
|
||||
out (c), a ; data: 0
|
||||
dec b
|
||||
inc d
|
||||
ld a, d
|
||||
cp a, #6
|
||||
jr c, _saac_lp
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; saa_silence() — SAA1099 chip 1 (addr=#00FF, data=#01FF)
|
||||
;----------------------------------------------------------------------
|
||||
.globl _saa_silence
|
||||
_saa_silence::
|
||||
ld c, #0xFF
|
||||
ld b, #0x00
|
||||
jr _saa_silence_core
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; saa2_silence() — SAA1099 chip 2 (addr=#02FF, data=#03FF)
|
||||
;----------------------------------------------------------------------
|
||||
.globl _saa2_silence
|
||||
_saa2_silence::
|
||||
ld c, #0xFF
|
||||
ld b, #0x02
|
||||
jr _saa_silence_core
|
||||
@@ -0,0 +1,58 @@
|
||||
;============================================================================
|
||||
; pos_table.s — Таблица позиций INT (СГЕНЕРИРОВАНО, не редактировать)
|
||||
;
|
||||
; entries=14, step=5120, line_len=224
|
||||
; ISR freq: 3,500,000 / 5120 = 684 Hz (~44100/16)
|
||||
;============================================================================
|
||||
|
||||
.module pos_table_mod
|
||||
.globl pos_table
|
||||
|
||||
.area _CODE
|
||||
|
||||
pos_table:
|
||||
_pt_0:
|
||||
.db 0x00, 0x00, 0x00 ; [ 0] line= 0, hpos= 0
|
||||
.dw _pt_1
|
||||
_pt_1:
|
||||
.db 0x00, 0x16, 0xC0 ; [ 1] line= 22, hpos=192
|
||||
.dw _pt_2
|
||||
_pt_2:
|
||||
.db 0x00, 0x2D, 0xA0 ; [ 2] line= 45, hpos=160
|
||||
.dw _pt_3
|
||||
_pt_3:
|
||||
.db 0x00, 0x44, 0x80 ; [ 3] line= 68, hpos=128
|
||||
.dw _pt_4
|
||||
_pt_4:
|
||||
.db 0x00, 0x5B, 0x60 ; [ 4] line= 91, hpos= 96
|
||||
.dw _pt_5
|
||||
_pt_5:
|
||||
.db 0x00, 0x72, 0x40 ; [ 5] line=114, hpos= 64
|
||||
.dw _pt_6
|
||||
_pt_6:
|
||||
.db 0x00, 0x89, 0x20 ; [ 6] line=137, hpos= 32
|
||||
.dw _pt_7
|
||||
_pt_7:
|
||||
.db 0x00, 0xA0, 0x00 ; [ 7] line=160, hpos= 0
|
||||
.dw _pt_8
|
||||
_pt_8:
|
||||
.db 0x00, 0xB6, 0xC0 ; [ 8] line=182, hpos=192
|
||||
.dw _pt_9
|
||||
_pt_9:
|
||||
.db 0x00, 0xCD, 0xA0 ; [ 9] line=205, hpos=160
|
||||
.dw _pt_10
|
||||
_pt_10:
|
||||
.db 0x00, 0xE4, 0x80 ; [10] line=228, hpos=128
|
||||
.dw _pt_11
|
||||
_pt_11:
|
||||
.db 0x00, 0xFB, 0x60 ; [11] line=251, hpos= 96
|
||||
.dw _pt_12
|
||||
_pt_12:
|
||||
.db 0x01, 0x12, 0x40 ; [12] line=274, hpos= 64
|
||||
.dw _pt_13
|
||||
_pt_13:
|
||||
.db 0x01, 0x29, 0x20 ; [13] line=297, hpos= 32
|
||||
.dw pos_table
|
||||
|
||||
.globl pos_table_end
|
||||
pos_table_end:
|
||||
@@ -0,0 +1,953 @@
|
||||
;==============================================================================
|
||||
; 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
|
||||
@@ -0,0 +1,561 @@
|
||||
;==============================================================================
|
||||
; txtlib.s — Text buffer library (hand-optimised Z80, sdcccall(1))
|
||||
;
|
||||
; Replaces lib/txtlib.c. Every public function matches the ABI
|
||||
; generated by SDCC for the prototypes in inc/txtlib.h.
|
||||
;
|
||||
; Buffer layout: buf[0] = max capacity, buf[1] = current length,
|
||||
; buf[2..] = character data.
|
||||
;
|
||||
; sdcccall(1) register conventions:
|
||||
; 1st ptr/u16 → HL 2nd ptr/u16 → DE
|
||||
; 1st u8 → A 2nd u8 (when 1st is u8) → L
|
||||
; Further args → stack (callee-cleans)
|
||||
; Return u8 → A Return u16 → HL
|
||||
;==============================================================================
|
||||
|
||||
.module txtlib_mod
|
||||
|
||||
.globl _buf_append_str
|
||||
.globl _buf_append_char
|
||||
.globl _buf_append_u8_hex
|
||||
.globl _buf_append_u16_dec
|
||||
.globl _buf_append_u32_dec
|
||||
.globl _buf_append_mmss
|
||||
.globl _print_line
|
||||
|
||||
.globl _wc_prsrw_attr ; from wc_api.s
|
||||
|
||||
.area _CODE
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; Helper: nibble (0–15 in A) → ASCII char ('0'–'F') in A
|
||||
;----------------------------------------------------------------------
|
||||
_nib2asc:
|
||||
cp a, #10
|
||||
jr c, 001$
|
||||
add a, #7
|
||||
001$: add a, #0x30
|
||||
ret
|
||||
|
||||
;======================================================================
|
||||
; uint8_t buf_append_str(char *buf, const char *src)
|
||||
; HL=buf DE=src → A=added (no stack params)
|
||||
;======================================================================
|
||||
_buf_append_str::
|
||||
ld c, (hl) ; C = max
|
||||
inc hl
|
||||
ld b, (hl) ; B = curlen
|
||||
ld a, c
|
||||
sub a, b ; A = remaining
|
||||
jr z, bas_z
|
||||
jr c, bas_z
|
||||
push hl ; save &buf[1]
|
||||
ld c, a ; C = remaining
|
||||
inc hl ; HL → buf[2]
|
||||
ld a, b
|
||||
add a, l
|
||||
ld l, a
|
||||
jr nc, bas_nc
|
||||
inc h
|
||||
bas_nc: ld b, #0 ; B = added
|
||||
bas_lp: ld a, (de)
|
||||
or a, a
|
||||
jr z, bas_dn
|
||||
ld (hl), a
|
||||
inc hl
|
||||
inc de
|
||||
inc b
|
||||
dec c
|
||||
jr nz, bas_lp
|
||||
bas_dn: pop hl ; &buf[1]
|
||||
ld a, (hl)
|
||||
add a, b
|
||||
ld (hl), a
|
||||
ld a, b
|
||||
ret
|
||||
bas_z: xor a, a
|
||||
ret
|
||||
|
||||
;======================================================================
|
||||
; uint8_t buf_append_char(char *buf, char c)
|
||||
; HL=buf stack: c(1) → A=0|1 callee-cleans 1
|
||||
;======================================================================
|
||||
_buf_append_char::
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
; 4(ix) = c
|
||||
ld c, (hl) ; max
|
||||
inc hl
|
||||
ld a, (hl) ; curlen
|
||||
cp a, c
|
||||
jr nc, bac_f
|
||||
ld e, a
|
||||
inc a
|
||||
ld (hl), a ; buf[1]++
|
||||
inc hl ; → buf[2]
|
||||
ld a, e
|
||||
add a, l
|
||||
ld l, a
|
||||
jr nc, bac_nc
|
||||
inc h
|
||||
bac_nc: ld a, 4 (ix)
|
||||
ld (hl), a
|
||||
ld a, #1
|
||||
jr bac_r
|
||||
bac_f: xor a, a
|
||||
bac_r: pop ix
|
||||
pop hl
|
||||
inc sp ; skip c (1 byte)
|
||||
jp (hl)
|
||||
|
||||
;======================================================================
|
||||
; uint8_t buf_append_u8_hex(char *buf, uint8_t val)
|
||||
; HL=buf stack: val(1) → A=0|2 callee-cleans 1
|
||||
;======================================================================
|
||||
_buf_append_u8_hex::
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
; 4(ix) = val
|
||||
ld c, (hl) ; max
|
||||
inc hl
|
||||
ld a, c
|
||||
sub a, (hl) ; remaining
|
||||
cp a, #2
|
||||
jr c, bh8_f
|
||||
ld b, (hl) ; curlen
|
||||
ld a, b
|
||||
add a, #2
|
||||
ld (hl), a ; buf[1] += 2
|
||||
inc hl
|
||||
ld a, b
|
||||
add a, l
|
||||
ld l, a
|
||||
jr nc, bh8_nc
|
||||
inc h
|
||||
bh8_nc: ld a, 4 (ix)
|
||||
rrca
|
||||
rrca
|
||||
rrca
|
||||
rrca
|
||||
and a, #0x0F
|
||||
call _nib2asc
|
||||
ld (hl), a
|
||||
inc hl
|
||||
ld a, 4 (ix)
|
||||
and a, #0x0F
|
||||
call _nib2asc
|
||||
ld (hl), a
|
||||
ld a, #2
|
||||
jr bh8_r
|
||||
bh8_f: xor a, a
|
||||
bh8_r: pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
jp (hl)
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; Shared helper: emit one decimal digit.
|
||||
; A = digit (0–9)
|
||||
; B = "added" counter (0 = not-yet-started → suppress leading 0)
|
||||
; C = remaining buffer space
|
||||
; IY = destination pointer (advanced on write)
|
||||
;----------------------------------------------------------------------
|
||||
_emit_digit:
|
||||
push af
|
||||
or a, b ; z iff digit==0 AND not started
|
||||
jr z, ed_skip
|
||||
ld a, c
|
||||
or a, a
|
||||
jr z, ed_skip ; no space
|
||||
dec c
|
||||
pop af
|
||||
add a, #0x30
|
||||
ld 0 (iy), a
|
||||
inc iy
|
||||
inc b
|
||||
ret
|
||||
ed_skip:
|
||||
pop af
|
||||
ret
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; Shared helper: extract one 32-bit decimal digit via repeated
|
||||
; subtraction of a 4-byte constant taken from inline data after CALL.
|
||||
;
|
||||
; Usage: call _d32_digit
|
||||
; .db lo, b1, b2, hi ; 32-bit subtrahend (LE)
|
||||
;
|
||||
; In: DEHL = 32-bit value, -3(ix) = scratch,
|
||||
; B = started/added, C = remaining, IY = dest ptr
|
||||
; Out: DEHL = remainder, digit emitted via _emit_digit
|
||||
;----------------------------------------------------------------------
|
||||
_d32_digit:
|
||||
ex (sp), hl ; HL = inline-data ptr, (SP)=val_lo
|
||||
ld a, (hl)
|
||||
ld (d32c0 + 1), a
|
||||
inc hl
|
||||
ld a, (hl)
|
||||
ld (d32c1 + 1), a
|
||||
inc hl
|
||||
ld a, (hl)
|
||||
ld (d32c2 + 1), a
|
||||
inc hl
|
||||
ld a, (hl)
|
||||
ld (d32c3 + 1), a
|
||||
inc hl ; HL past data = true ret addr
|
||||
ex (sp), hl ; (SP)=true ret, HL=val_lo restored
|
||||
ld -3 (ix), #0xFF
|
||||
d32d_lp:inc -3 (ix)
|
||||
push de
|
||||
push hl
|
||||
ld a, l
|
||||
d32c0: sub a, #0x00 ; patched: subtrahend byte 0
|
||||
ld l, a
|
||||
ld a, h
|
||||
d32c1: sbc a, #0x00 ; patched: byte 1
|
||||
ld h, a
|
||||
ld a, e
|
||||
d32c2: sbc a, #0x00 ; patched: byte 2
|
||||
ld e, a
|
||||
ld a, d
|
||||
d32c3: sbc a, #0x00 ; patched: byte 3
|
||||
ld d, a
|
||||
jr c, d32d_un
|
||||
pop af
|
||||
pop af
|
||||
jr d32d_lp
|
||||
d32d_un:pop hl
|
||||
pop de
|
||||
ld a, -3 (ix)
|
||||
jp _emit_digit ; tail-call, ret goes past inline data
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; Shared 16-bit decimal division: 10000/1000/100/10/ones.
|
||||
; Entry _div_10k_ones: full 16-bit decimal (10000..1).
|
||||
; Entry _div_1k_ones: skip 10000, do 1000..1.
|
||||
; In: DE=value, B=added counter, C=remaining, IY=dst.
|
||||
; Out: B=updated, DE=remainder. A,HL clobbered.
|
||||
;----------------------------------------------------------------------
|
||||
_div_10k_ones:
|
||||
;-- 10000 (neg 0xD8F0) --
|
||||
ld a, #0xFF
|
||||
_d10k: inc a
|
||||
ld hl, #0xD8F0
|
||||
add hl, de
|
||||
jr nc, _d10ke
|
||||
ex de, hl
|
||||
jr _d10k
|
||||
_d10ke: call _emit_digit
|
||||
_div_1k_ones:
|
||||
;-- 1000 (neg 0xFC18) --
|
||||
ld a, #0xFF
|
||||
_d1k: inc a
|
||||
ld hl, #0xFC18
|
||||
add hl, de
|
||||
jr nc, _d1ke
|
||||
ex de, hl
|
||||
jr _d1k
|
||||
_d1ke: call _emit_digit
|
||||
;-- 100 (neg 0xFF9C) --
|
||||
ld a, #0xFF
|
||||
_d100: inc a
|
||||
ld hl, #0xFF9C
|
||||
add hl, de
|
||||
jr nc, _d100e
|
||||
ex de, hl
|
||||
jr _d100
|
||||
_d100e: call _emit_digit
|
||||
;-- 10 (neg 0xFFF6) --
|
||||
ld a, #0xFF
|
||||
_d10: inc a
|
||||
ld hl, #0xFFF6
|
||||
add hl, de
|
||||
jr nc, _d10e
|
||||
ex de, hl
|
||||
jr _d10
|
||||
_d10e: call _emit_digit
|
||||
;-- ones (always emit) --
|
||||
ld a, c
|
||||
or a, a
|
||||
ret z
|
||||
ld a, e
|
||||
add a, #0x30
|
||||
ld 0 (iy), a
|
||||
inc iy
|
||||
inc b
|
||||
ret
|
||||
|
||||
;======================================================================
|
||||
; uint8_t buf_append_u16_dec(char *buf, uint16_t val)
|
||||
; HL=buf DE=val → A=added (no stack params)
|
||||
;======================================================================
|
||||
_buf_append_u16_dec::
|
||||
push iy
|
||||
ld c, (hl)
|
||||
inc hl
|
||||
ld a, c
|
||||
sub a, (hl)
|
||||
jr z, d16_z
|
||||
jr c, d16_z
|
||||
ld c, a ; C = remaining
|
||||
ld b, (hl) ; B = curlen (temp)
|
||||
push hl ; save &buf[1]
|
||||
inc hl
|
||||
ld a, b
|
||||
add a, l
|
||||
ld l, a
|
||||
jr nc, d16_nc
|
||||
inc h
|
||||
d16_nc: push hl
|
||||
pop iy ; IY = dst
|
||||
ld b, #0 ; B = added/started
|
||||
call _div_10k_ones
|
||||
pop hl ; &buf[1]
|
||||
ld a, (hl)
|
||||
add a, b
|
||||
ld (hl), a
|
||||
ld a, b
|
||||
pop iy
|
||||
ret
|
||||
d16_z: xor a, a
|
||||
pop iy
|
||||
ret
|
||||
|
||||
;======================================================================
|
||||
; uint8_t buf_append_u32_dec(char *buf, uint32_t val)
|
||||
; HL=buf stack: val(4) → A=added callee-cleans 4
|
||||
; 4(ix)=byte0(LSB) 5(ix)=byte1 6(ix)=byte2 7(ix)=byte3(MSB)
|
||||
;======================================================================
|
||||
_buf_append_u32_dec::
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
push iy ; -2,-1(ix)
|
||||
push af ; -4,-3(ix) scratch
|
||||
push af ; -6,-5(ix) scratch
|
||||
; -3(ix) used as digit counter per power
|
||||
ld c, (hl)
|
||||
inc hl
|
||||
ld a, c
|
||||
sub a, (hl)
|
||||
jp z, d32_z
|
||||
jp c, d32_z
|
||||
ld c, a ; C = remaining
|
||||
ld b, (hl) ; B = curlen (temp)
|
||||
push hl ; save &buf[1] → -8,-7(ix)
|
||||
inc hl
|
||||
ld a, b
|
||||
add a, l
|
||||
ld l, a
|
||||
jr nc, d32_nc
|
||||
inc h
|
||||
d32_nc: push hl
|
||||
pop iy ; IY = dst
|
||||
; Load 32-bit val into DEHL
|
||||
ld l, 4 (ix)
|
||||
ld h, 5 (ix)
|
||||
ld e, 6 (ix)
|
||||
ld d, 7 (ix)
|
||||
ld b, #0 ; B = added/started
|
||||
;-- 1,000,000 / 100,000 / 10,000 via shared subroutine --
|
||||
call _d32_digit
|
||||
.db 0x40, 0x42, 0x0F, 0x00 ; 1,000,000
|
||||
call _d32_digit
|
||||
.db 0xA0, 0x86, 0x01, 0x00 ; 100,000
|
||||
call _d32_digit
|
||||
.db 0x10, 0x27, 0x00, 0x00 ; 10,000
|
||||
;-- remainder < 10000, fits in HL → move to DE --
|
||||
ex de, hl
|
||||
call _div_1k_ones
|
||||
pop hl ; &buf[1]
|
||||
ld a, (hl)
|
||||
add a, b
|
||||
ld (hl), a
|
||||
ld a, b ; return value
|
||||
d32_exit:
|
||||
; clean locals, restore, callee-clean 4
|
||||
pop de ; eat -6,-5
|
||||
pop de ; eat -4,-3
|
||||
pop iy
|
||||
pop ix
|
||||
pop hl ; ret addr
|
||||
pop de ; skip val lo
|
||||
pop de ; skip val hi
|
||||
jp (hl)
|
||||
d32_z: xor a, a
|
||||
jr d32_exit
|
||||
|
||||
;======================================================================
|
||||
; void print_line(wc_window_t *win, uint8_t y, const char *buf,
|
||||
; uint8_t attr)
|
||||
; HL=win stack: y(1) buf(2) attr(1) callee-cleans 4
|
||||
; Prints buf content + spaces padding to fill content width.
|
||||
;======================================================================
|
||||
_print_line::
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
; 4(ix)=y 5,6(ix)=buf 7(ix)=attr
|
||||
push af ; -2,-1(ix): cw at -1, slen at -2
|
||||
push hl ; -4,-3(ix): win
|
||||
;-- cw = get_content_width(win) --
|
||||
ld de, #4
|
||||
add hl, de
|
||||
ld a, (hl)
|
||||
or a, a
|
||||
jr nz, pl_nz
|
||||
ld a, #96
|
||||
pl_nz: sub a, #4
|
||||
ld -1 (ix), a
|
||||
;-- slen = buf[1] --
|
||||
ld l, 5 (ix)
|
||||
ld h, 6 (ix)
|
||||
inc hl
|
||||
ld a, (hl)
|
||||
ld -2 (ix), a
|
||||
;-- print_len = min(slen, cw) --
|
||||
cp a, -1 (ix)
|
||||
jr c, pl_min
|
||||
ld a, -1 (ix)
|
||||
pl_min: ld c, a ; C = print_len
|
||||
;-- 1st wc_prsrw_attr(win, data, y, 2, print_len, attr) --
|
||||
ld a, 7 (ix)
|
||||
push af
|
||||
inc sp
|
||||
ld b, #0
|
||||
push bc
|
||||
ld h, #2
|
||||
ld l, 4 (ix)
|
||||
push hl
|
||||
; DE = buf + 2
|
||||
ld a, 5 (ix)
|
||||
add a, #2
|
||||
ld e, a
|
||||
ld a, 6 (ix)
|
||||
adc a, #0
|
||||
ld d, a
|
||||
; HL = win
|
||||
ld l, -4 (ix)
|
||||
ld h, -3 (ix)
|
||||
call _wc_prsrw_attr
|
||||
;-- padding? --
|
||||
ld a, -2 (ix)
|
||||
cp a, -1 (ix)
|
||||
jr nc, pl_done
|
||||
; pad = cw − slen
|
||||
ld a, -1 (ix)
|
||||
sub a, -2 (ix)
|
||||
ld c, a
|
||||
; Fill buf[2..2+pad-1] with 0x20 (runtime space fill)
|
||||
ld l, 5 (ix)
|
||||
ld h, 6 (ix)
|
||||
inc hl
|
||||
inc hl ; HL = &buf[2]
|
||||
ld b, c ; B = pad count
|
||||
ld a, #0x20
|
||||
pf_fill:ld (hl), a
|
||||
inc hl
|
||||
djnz pf_fill ; B=0 after loop
|
||||
;-- 2nd wc_prsrw_attr(win, &buf[2], y, 2+slen, pad, attr) --
|
||||
ld a, 7 (ix)
|
||||
push af
|
||||
inc sp
|
||||
push bc ; len=pad (B=0, C=pad)
|
||||
ld a, -2 (ix)
|
||||
add a, #2
|
||||
ld h, a ; x = 2 + slen
|
||||
ld l, 4 (ix) ; y
|
||||
push hl
|
||||
ld e, 5 (ix)
|
||||
ld d, 6 (ix)
|
||||
inc de
|
||||
inc de ; DE = &buf[2]
|
||||
ld l, -4 (ix)
|
||||
ld h, -3 (ix)
|
||||
call _wc_prsrw_attr
|
||||
pl_done:
|
||||
ld sp, ix
|
||||
pop ix
|
||||
pop hl
|
||||
pop af
|
||||
pop af ; callee-clean 4
|
||||
jp (hl)
|
||||
|
||||
;----------------------------------------------------------------------
|
||||
; buf_append_mmss(buf, sec) — append "MM:SS" (5 chars) to buffer
|
||||
;
|
||||
; sdcccall(1): HL = buf, DE = sec (uint16_t, 0..5999)
|
||||
; No division — subtraction loops only (~30 bytes code).
|
||||
;----------------------------------------------------------------------
|
||||
_buf_append_mmss:
|
||||
push hl
|
||||
pop ix ; IX = buf
|
||||
; DE = total seconds. Divide by 60 via subtraction → B=min, E=sec
|
||||
ld b, #0
|
||||
mmss_d60:
|
||||
ld a, e
|
||||
sub a, #60
|
||||
ld c, a
|
||||
ld a, d
|
||||
sbc a, #0
|
||||
jr c, mmss_d60_done
|
||||
ld d, a
|
||||
ld e, c
|
||||
inc b
|
||||
jr mmss_d60
|
||||
mmss_d60_done:
|
||||
; B = minutes, E = seconds
|
||||
push de ; save seconds (mmss_2dig clobbers DE)
|
||||
ld a, b
|
||||
call mmss_2dig ; appends 2 digits of A
|
||||
; Append ':'
|
||||
ld a, 1 (ix)
|
||||
ld c, a
|
||||
ld b, #0
|
||||
inc a
|
||||
ld 1 (ix), a
|
||||
push ix
|
||||
pop hl
|
||||
add hl, bc
|
||||
inc hl ; skip buf[0] (max)
|
||||
inc hl ; skip buf[1] (len)
|
||||
ld (hl), #0x3A ; ':'
|
||||
; Append seconds
|
||||
pop de ; restore E = seconds
|
||||
ld a, e
|
||||
; fall through to mmss_2dig — but IX is still buf
|
||||
; call mmss_2dig
|
||||
call mmss_2dig
|
||||
ret
|
||||
|
||||
; Subroutine: append 2 ASCII digits of A (0..99) to buf at IX
|
||||
; Destroys A, C, B, HL
|
||||
mmss_2dig:
|
||||
ld c, #0x30 ; tens digit = '0'
|
||||
mmss_t10:
|
||||
sub a, #10
|
||||
jr c, mmss_t10_done
|
||||
inc c
|
||||
jr mmss_t10
|
||||
mmss_t10_done:
|
||||
add a, #10 ; undo last sub
|
||||
add a, #0x30 ; units → ASCII
|
||||
ld b, a ; save units
|
||||
; Append tens (C) then units (B)
|
||||
ld a, 1 (ix) ; len
|
||||
ld l, a
|
||||
ld h, #0
|
||||
add a, #2
|
||||
ld 1 (ix), a ; len += 2
|
||||
push ix
|
||||
pop de ; DE = buf base
|
||||
add hl, de
|
||||
inc hl ; skip buf[0] (max)
|
||||
inc hl ; skip buf[1] (len)
|
||||
ld (hl), c ; tens
|
||||
inc hl
|
||||
ld (hl), b ; units
|
||||
ret
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user