Add OPL4 playback and analyzer support
This commit is contained in:
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build VGM Player",
|
||||
"type": "shell",
|
||||
"command": "cmd /c build.bat",
|
||||
"isBackground": false,
|
||||
"problemMatcher": [],
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
@@ -0,0 +1,172 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
REM ===========================================================================
|
||||
REM build.bat — Сборка VGM Player Plugin (SDCC/Z80)
|
||||
REM
|
||||
REM Инструменты:
|
||||
REM sdcc — компилятор C (sdcccall(1), mz80, без frame pointer)
|
||||
REM sdas — ассемблер SDCC (ASM файлы)
|
||||
REM sdld / sdcc --link-cmd-file — линковщик
|
||||
REM node — JS-скрипты (gen_pos_table, ihx2wmf)
|
||||
REM
|
||||
REM Структура вывода: build/ (временные .rel) → vgmplay.wmf
|
||||
REM ===========================================================================
|
||||
|
||||
setlocal
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
REM --- Папка для временных файлов ---
|
||||
if not exist build mkdir build
|
||||
|
||||
REM --- Флаги SDCC ---
|
||||
REM -mz80 : целевая архитектура Z80
|
||||
REM --sdcccall 1 : соглашение вызова sdcccall(1) (регистровая передача)
|
||||
REM --no-std-crt0 : не линковать стандартный crt0 (наш в asm/crt0.s)
|
||||
REM --opt-code-size : оптимизация по размеру (важно для 8-бит)
|
||||
REM --no-peep-asm : не трогать __naked asm-блоки
|
||||
set CFLAGS=-mz80 --sdcccall 1 --no-std-crt0 --opt-code-size -I inc --disable-warning 85
|
||||
|
||||
REM --- Флаги ассемблера (sdas) ---
|
||||
set ASFLAGS=-plosff
|
||||
|
||||
REM --- Читаем параметры варианта из variant_cfg.h ---
|
||||
for /f "tokens=3" %%a in ('findstr /C:"#define VARIANT_POS_ENTRIES" inc\variant_cfg.h') do set POS_ENTRIES=%%a
|
||||
for /f "tokens=3" %%a in ('findstr /C:"#define VARIANT_POS_STEP" inc\variant_cfg.h') do set POS_STEP=%%a
|
||||
for /f "tokens=3" %%a in ('findstr /C:"#define VARIANT_DATA_LOC" inc\variant_cfg.h') do set DATA_LOC=%%a
|
||||
echo variant_cfg: entries=%POS_ENTRIES% step=%POS_STEP% data-loc=%DATA_LOC%
|
||||
|
||||
echo [1/8] Генерация pos_table.s...
|
||||
node scripts\gen_pos_table.js --entries %POS_ENTRIES% --step %POS_STEP%
|
||||
if errorlevel 1 ( echo FAIL gen_pos_table && goto :err )
|
||||
|
||||
echo [2/8] Генерация freq_tables.bin...
|
||||
node scripts\gen_freq_tables.js --page-base 1 --binary build\freq_tables.bin --cheader inc\freq_lut_map.h
|
||||
if errorlevel 1 ( echo FAIL gen_freq_tables && goto :err )
|
||||
|
||||
echo [3/9] Сборка inflate.asm (sjasmplus)...
|
||||
..\tools\sjasm\sjasmplus.exe asm\inflate.asm >nul 2>&1
|
||||
if errorlevel 1 ( echo FAIL inflate.asm && goto :err )
|
||||
REM inflate.asm имеет SAVEBIN "build/inflate.bin" внутри
|
||||
REM Паддинг inflate.bin до 16KB (чтобы cmdblocks.bin начинался на границе страницы)
|
||||
powershell -NoProfile -Command "$f=[IO.File]::ReadAllBytes('build\inflate.bin'); if($f.Length -lt 16384){$pad=New-Object byte[] (16384-$f.Length); [IO.File]::WriteAllBytes('build\inflate.bin',$f+$pad); Write-Host (' inflate.bin padded: '+$f.Length+' -> 16384')}"
|
||||
if errorlevel 1 ( echo FAIL pad inflate && goto :err )
|
||||
|
||||
echo [3b/9] Сборка cmdblocks.asm (sjasmplus)...
|
||||
..\tools\sjasm\sjasmplus.exe asm\cmdblocks.asm >nul 2>&1
|
||||
if errorlevel 1 ( echo FAIL cmdblocks.asm && goto :err )
|
||||
REM cmdblocks.asm имеет SAVEBIN "build/cmdblocks.bin" внутри
|
||||
|
||||
echo [4/9] Компиляция C файлов...
|
||||
sdcc %CFLAGS% -c main.c -o build\main.rel >nul
|
||||
if errorlevel 1 ( echo FAIL main.c && goto :err )
|
||||
|
||||
sdcc %CFLAGS% -c lib\vgm.c -o build\vgm.rel >nul
|
||||
if errorlevel 1 ( echo FAIL vgm.c && goto :err )
|
||||
|
||||
sdcc %CFLAGS% -c lib\keys.c -o build\keys.rel >nul
|
||||
if errorlevel 1 ( echo FAIL keys.c && goto :err )
|
||||
|
||||
REM txtlib is now hand-written assembly (asm\txtlib.s), assembled below
|
||||
|
||||
echo [5/9] Сборка ASM файлов...
|
||||
sdasz80 %ASFLAGS% build\crt0.rel asm\crt0.s >nul
|
||||
if errorlevel 1 ( echo FAIL crt0.s && goto :err )
|
||||
|
||||
sdasz80 %ASFLAGS% build\isr.rel asm\isr.s >nul
|
||||
if errorlevel 1 ( echo FAIL isr.s && goto :err )
|
||||
|
||||
sdasz80 %ASFLAGS% build\pos_table.rel asm\pos_table.s >nul
|
||||
if errorlevel 1 ( echo FAIL pos_table.s && goto :err )
|
||||
|
||||
call lib\wc_api\build_lib.bat >nul
|
||||
if errorlevel 1 ( echo FAIL wc_api.lib && goto :err )
|
||||
|
||||
sdasz80 %ASFLAGS% build\txtlib.rel asm\txtlib.s >nul
|
||||
if errorlevel 1 ( echo FAIL txtlib.s && goto :err )
|
||||
|
||||
sdasz80 %ASFLAGS% build\inflate_call.rel asm\inflate_call.s >nul
|
||||
if errorlevel 1 ( echo FAIL inflate_call.s && goto :err )
|
||||
|
||||
sdasz80 %ASFLAGS% build\spectrum.rel asm\spectrum.s >nul
|
||||
if errorlevel 1 ( echo FAIL spectrum.s && goto :err )
|
||||
|
||||
echo [6/9] Линковка...
|
||||
REM Порядок важен: crt0 первым (entry point), затем модули
|
||||
REM Layout: CODE #8000–#B8CF, DATA #B8D0–#BFFF
|
||||
REM Все секции (DATA + GSINIT + HOME + INITIALIZER) должны
|
||||
REM уместиться до #BFFF (Win2). Если CODE вырастет →
|
||||
REM понизить --data-loc или оптимизировать код.
|
||||
sdcc -mz80 --no-std-crt0 --out-fmt-ihx --code-loc 0x8000 --data-loc %DATA_LOC% ^
|
||||
build\crt0.rel ^
|
||||
build\main.rel ^
|
||||
build\vgm.rel ^
|
||||
build\keys.rel ^
|
||||
build\isr.rel ^
|
||||
build\pos_table.rel ^
|
||||
build\wc_api.lib ^
|
||||
build\txtlib.rel ^
|
||||
build\inflate_call.rel ^
|
||||
build\spectrum.rel ^
|
||||
-o build\vgmplay.ihx >nul
|
||||
if errorlevel 1 ( echo FAIL link && goto :err )
|
||||
|
||||
REM --- Вывод карты памяти секций из map-файла ---
|
||||
powershell -NoProfile -Command ^
|
||||
"$m=Get-Content build\vgmplay.map -Raw;" ^
|
||||
"function gs($p){if($m -match '([0-9a-f]{8})\s+'+$p){return $Matches[1]}return '00000000'};" ^
|
||||
"$secs=@('CODE','DATA','GSINIT','GSFINAL','INITIALIZED','HOME','INITIALIZER');" ^
|
||||
"Write-Host '';" ^
|
||||
"Write-Host ' Section Start End Size';" ^
|
||||
"Write-Host ' -------------- -------- -------- ----------';" ^
|
||||
"$lastEnd=0;$codeEnd=0;$dataStart=0;" ^
|
||||
"foreach($s in $secs){" ^
|
||||
" $st=[Convert]::ToInt32((gs ('s__'+$s)),16);" ^
|
||||
" $ln=[Convert]::ToInt32((gs ('l__'+$s)),16);" ^
|
||||
" if($ln -eq 0){continue};" ^
|
||||
" $en=$st+$ln-1;" ^
|
||||
" if($en -gt $lastEnd){$lastEnd=$en};" ^
|
||||
" if($s -eq 'CODE'){$codeEnd=$en};" ^
|
||||
" if($s -eq 'DATA'){$dataStart=$st};" ^
|
||||
" Write-Host (' _'+$s.PadRight(14)+' 0x'+$st.ToString('X4')+' 0x'+$en.ToString('X4')+' '+$ln.ToString().PadLeft(5)+' bytes')" ^
|
||||
"};" ^
|
||||
"$free=0xBFFF-$lastEnd;" ^
|
||||
"Write-Host ' -------------- -------- -------- ----------';" ^
|
||||
"Write-Host (' Last byte: 0x'+$lastEnd.ToString('X4')+' Free before C000: '+$free+' bytes');" ^
|
||||
"if($codeEnd -ge $dataStart -and $dataStart -gt 0){Write-Host (' *** ERROR: _CODE (end 0x'+$codeEnd.ToString('X4')+') overlaps _DATA (start 0x'+$dataStart.ToString('X4')+')! ***') -ForegroundColor Red; exit 1};" ^
|
||||
"if($lastEnd -ge 0xC000){Write-Host ' *** ERROR: sections overflow past 0xBFFF! ***' -ForegroundColor Red; exit 1}"
|
||||
if errorlevel 1 goto :err
|
||||
|
||||
echo [7/9] Генерация WMF (multi-page)...
|
||||
REM Склеить freq_tables.bin + inflate page + cmdblocks.bin
|
||||
copy /b build\freq_tables.bin+build\inflate.bin+build\cmdblocks.bin build\extra_combined.bin >nul
|
||||
if errorlevel 1 ( echo FAIL combine extra && goto :err )
|
||||
node scripts\ihx2wmf.js build\vgmplay.ihx ..\DiskRef\WC\VGMPLAY.WMF --extra build\extra_combined.bin
|
||||
if errorlevel 1 ( echo FAIL ihx2wmf && goto :err )
|
||||
copy /Y ..\DiskRef\WC\VGMPLAY.WMF ..\VGMPLAY.WMF >nul
|
||||
|
||||
echo [8/9] Проверка размеров binary...
|
||||
powershell -NoProfile -Command "Get-ChildItem build\*.bin | Select-Object Name, Length | Format-Table -AutoSize"
|
||||
|
||||
echo [9/9] Сборка образа диска...
|
||||
if not exist "..\tools\robimg.exe" (
|
||||
echo SKIP: tools\robimg.exe не найден
|
||||
goto :ok
|
||||
)
|
||||
del /Q ..\tsconf.img 2>nul
|
||||
..\tools\robimg.exe -p="..\tsconf.img" -s=280000 -C="..\DiskRef" >nul 2>&1
|
||||
if errorlevel 1 ( echo FAIL robimg && goto :err )
|
||||
echo tsconf.img готов
|
||||
|
||||
:ok
|
||||
echo.
|
||||
echo === BUILD OK ===
|
||||
goto :end
|
||||
|
||||
:err
|
||||
echo.
|
||||
echo === BUILD FAILED ===
|
||||
exit /b 1
|
||||
|
||||
:end
|
||||
endlocal
|
||||
@@ -0,0 +1,236 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Build multiple ISR frequency / command budget variants for A/B testing.
|
||||
Each variant produces a separate tsconf_*.img disk image.
|
||||
|
||||
.DESCRIPTION
|
||||
Generates inc\variant_cfg.h for each variant, runs build.bat, copies
|
||||
the output image. Restores the original variant_cfg.h afterward.
|
||||
|
||||
NO source files are regex-patched — only variant_cfg.h is overwritten.
|
||||
|
||||
.NOTES
|
||||
C compilation is SLOW (~60 sec per variant). Do NOT interrupt (Ctrl-C).
|
||||
#>
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $MyInvocation.MyCommand.Path # src_sdcc
|
||||
$projRoot = Split-Path -Parent $root # project root
|
||||
$cfgFile = "$root\inc\variant_cfg.h"
|
||||
|
||||
# ── Frequency tiers ──────────────────────────────────────────────────
|
||||
# ISR_FREQ = 3 500 000 / Step; Entries = 71 680 / Step; TPF = Entries
|
||||
# Shift = log2(44100 / Freq) rounded down
|
||||
$freqTiers = @{
|
||||
'2734' = @{ Tag=''; Freq=2734; Step=1280; Entries=56; Shift=4; Mask='0x0F'; TPF=56; Budgets=@(8,16) }
|
||||
'1367' = @{ Tag='hf'; Freq=1367; Step=2560; Entries=28; Shift=5; Mask='0x1F'; TPF=28; Budgets=@(8,16,32) }
|
||||
'683' = @{ Tag='qf'; Freq=683; Step=5120; Entries=14; Shift=6; Mask='0x3F'; TPF=14; Budgets=@(16,32,64) }
|
||||
}
|
||||
|
||||
# ── Generate variants: 2+3+3 = 8 base × 2 NS = 16 total ─────────────
|
||||
$baseVariants = @()
|
||||
foreach ($ft in $freqTiers.Values | Sort-Object Freq -Descending) {
|
||||
foreach ($b in $ft.Budgets) {
|
||||
$suffix = if ($ft.Tag) { "_$($ft.Tag)" } else { '' }
|
||||
$name = "b${b}${suffix}"
|
||||
$baseVariants += @{
|
||||
Name = $name
|
||||
Budget = $b
|
||||
Entries = $ft.Entries
|
||||
Step = $ft.Step
|
||||
Freq = $ft.Freq
|
||||
Shift = $ft.Shift
|
||||
Mask = $ft.Mask
|
||||
TPF = $ft.TPF
|
||||
DataLoc = '0xB600'
|
||||
NS = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Auto-generate _ns (no short waits) clones
|
||||
$nsVariants = foreach ($bv in $baseVariants) {
|
||||
$nv = $bv.Clone()
|
||||
$nv.Name = $bv.Name + '_ns'
|
||||
$nv.NS = $true
|
||||
$nv
|
||||
}
|
||||
$variants = $baseVariants + $nsVariants
|
||||
|
||||
# ── Save original variant_cfg.h ─────────────────────────────────────
|
||||
$cfgBackup = Get-Content $cfgFile -Raw
|
||||
|
||||
function Restore-Cfg {
|
||||
Set-Content $script:cfgFile -Value $script:cfgBackup -NoNewline
|
||||
}
|
||||
|
||||
# ── Helper: generate variant_cfg.h ──────────────────────────────────
|
||||
function Write-VariantCfg($v) {
|
||||
$nsLine = if ($v.NS) { "#define VGM_NO_SHORT_WAITS" } else { "/* #define VGM_NO_SHORT_WAITS */" }
|
||||
$content = @"
|
||||
/* variant_cfg.h -- AUTO-GENERATED by build_variants.ps1
|
||||
* Variant: $($v.Name) Freq=$($v.Freq)Hz Budget=$($v.Budget) NS=$($v.NS)
|
||||
*/
|
||||
#ifndef VARIANT_CFG_H
|
||||
#define VARIANT_CFG_H
|
||||
|
||||
#define ISR_FREQ $($v.Freq)
|
||||
#define ISR_TICKS_PER_FRAME $($v.TPF)
|
||||
#define VGM_SAMPLE_SHIFT $($v.Shift)
|
||||
#define VGM_SAMPLE_MASK $($v.Mask)
|
||||
#define VGM_FILL_CMD_BUDGET $($v.Budget)
|
||||
|
||||
#define VARIANT_POS_ENTRIES $($v.Entries)
|
||||
#define VARIANT_POS_STEP $($v.Step)
|
||||
#define VARIANT_DATA_LOC $($v.DataLoc)
|
||||
|
||||
$nsLine
|
||||
|
||||
#endif /* VARIANT_CFG_H */
|
||||
"@
|
||||
Set-Content $script:cfgFile -Value $content -NoNewline
|
||||
}
|
||||
|
||||
# ── Output directory ─────────────────────────────────────────────────
|
||||
$outDir = "$projRoot\variants"
|
||||
if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir | Out-Null }
|
||||
|
||||
# ── Build loop ──────────────────────────────────────────────────────
|
||||
$total = $variants.Count
|
||||
$idx = 0
|
||||
$results = @()
|
||||
|
||||
foreach ($v in $variants) {
|
||||
$idx++
|
||||
$name = $v.Name
|
||||
Write-Host "`n========== [$idx/$total] Building $name ==========" -ForegroundColor Cyan
|
||||
Write-Host " Budget=$($v.Budget) Freq=$($v.Freq)Hz Entries=$($v.Entries) Step=$($v.Step) Shift=$($v.Shift) TPF=$($v.TPF) NS=$($v.NS)"
|
||||
|
||||
# --- Generate variant_cfg.h ---
|
||||
Write-VariantCfg $v
|
||||
|
||||
# --- Clean .rel files to force recompile ---
|
||||
Remove-Item "$root\build\*.rel" -ErrorAction SilentlyContinue
|
||||
|
||||
# --- Run build ---
|
||||
Write-Host " Building... (this takes ~60 sec)" -ForegroundColor Yellow
|
||||
$buildOut = cmd /c "cd /d $root && build.bat 2>&1"
|
||||
$buildOk = $buildOut | Where-Object { $_ -match 'BUILD OK' }
|
||||
$buildFail = $buildOut | Where-Object { $_ -match 'FAIL|ERROR|overflow' }
|
||||
|
||||
if ($buildFail -or -not $buildOk) {
|
||||
Write-Host " *** BUILD FAILED for $name ***" -ForegroundColor Red
|
||||
$buildOut | Where-Object { $_ -match 'FAIL|error|warning|_CODE|_DATA|Last byte|overflow|variant_cfg' } |
|
||||
ForEach-Object { Write-Host " $_" }
|
||||
$results += [pscustomobject]@{ Name=$name; Status='FAIL'; Code=0; Free=0 }
|
||||
continue
|
||||
}
|
||||
|
||||
# --- Extract sizes from output ---
|
||||
$codeLine = $buildOut | Where-Object { $_ -match '_CODE' } | Select-Object -First 1
|
||||
$freeLine = $buildOut | Where-Object { $_ -match 'Free before' } | Select-Object -First 1
|
||||
$codeSize = if ($codeLine -match '(\d+)\s+bytes') { [int]$Matches[1] } else { 0 }
|
||||
$freeSize = if ($freeLine -match 'Free before C000:\s*(\d+)') { [int]$Matches[1] } else { 0 }
|
||||
|
||||
# --- Show map ---
|
||||
$buildOut | Where-Object { $_ -match 'variant_cfg:|Section|_CODE|_DATA|_GSINIT|_HOME|_INITIAL|Last byte|Code:|Written.*WMF' } |
|
||||
ForEach-Object { Write-Host " $_" }
|
||||
|
||||
# --- Copy output image ---
|
||||
$srcImg = "$projRoot\tsconf.img"
|
||||
$dstImg = "$outDir\tsconf_$name.img"
|
||||
if (Test-Path $srcImg) {
|
||||
Copy-Item $srcImg $dstImg -Force
|
||||
Write-Host " -> $dstImg" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " WARNING: tsconf.img not found" -ForegroundColor Yellow
|
||||
}
|
||||
# Also copy WMF
|
||||
$srcWmf = "$projRoot\DiskRef\WC\VGMPLAY.WMF"
|
||||
$dstWmf = "$outDir\VGMPLAY_$name.WMF"
|
||||
if (Test-Path $srcWmf) {
|
||||
Copy-Item $srcWmf $dstWmf -Force
|
||||
}
|
||||
|
||||
$results += [pscustomobject]@{ Name=$name; Status='OK'; Code=$codeSize; Free=$freeSize }
|
||||
}
|
||||
|
||||
# ── Restore original variant_cfg.h ──────────────────────────────────
|
||||
Write-Host "`n========== Restoring original variant_cfg.h ==========" -ForegroundColor Cyan
|
||||
Restore-Cfg
|
||||
# Rebuild baseline to leave workspace clean
|
||||
Remove-Item "$root\build\*.rel" -ErrorAction SilentlyContinue
|
||||
Write-Host " Rebuilding baseline..." -ForegroundColor Yellow
|
||||
$null = cmd /c "cd /d $root && build.bat 2>&1"
|
||||
Write-Host " Done." -ForegroundColor Green
|
||||
|
||||
# ── Summary ──────────────────────────────────────────────────────────
|
||||
Write-Host "`n========== VARIANT SUMMARY ==========" -ForegroundColor Cyan
|
||||
$fmt = "{0,-18} {1,6} {2,6} {3,6} {4,3} {5,5} {6,5}"
|
||||
Write-Host ($fmt -f 'Name','Budget','Freq','Code','NS','Free','St')
|
||||
Write-Host ($fmt -f '----','------','----','----','--','----','--')
|
||||
foreach ($r in $results) {
|
||||
$v = $variants | Where-Object { $_.Name -eq $r.Name }
|
||||
$nsTag = if ($v.NS) { 'yes' } else { ' - ' }
|
||||
$color = if ($r.Status -eq 'OK') { 'Green' } else { 'Red' }
|
||||
Write-Host ($fmt -f $r.Name, $v.Budget, $v.Freq, $r.Code, $nsTag, $r.Free, $r.Status) -ForegroundColor $color
|
||||
}
|
||||
Write-Host "`nImages in: $outDir"
|
||||
|
||||
# ── Generate README.txt ──────────────────────────────────────────────
|
||||
$readme = @"
|
||||
Variants of VGM player for A/B testing
|
||||
=======================================
|
||||
|
||||
Parameters stored in inc\variant_cfg.h (single config file).
|
||||
build_variants.ps1 regenerates variant_cfg.h for each variant.
|
||||
|
||||
Frequency tiers:
|
||||
qf - quarter freq (ISR 683 Hz, step 5120, 14 ticks/frame)
|
||||
(none) - base freq (ISR 2734 Hz, step 1280, 56 ticks/frame)
|
||||
hf - half freq (ISR 1367 Hz, step 2560, 28 ticks/frame)
|
||||
|
||||
Other tags:
|
||||
b<N> - command budget (max commands between forced pauses)
|
||||
ns - no short waits (0x70-0x7F pauses skipped)
|
||||
|
||||
"@
|
||||
|
||||
$readme += "{0,-30} {1,6} {2,8} {3,3} {4,6} {5,5} {6}`n" -f 'Image','Budget','Freq','NS','Code','Free','Status'
|
||||
$readme += "{0,-30} {1,6} {2,8} {3,3} {4,6} {5,5} {6}`n" -f '-----','------','----','--','----','----','------'
|
||||
foreach ($r in $results) {
|
||||
$v = $variants | Where-Object { $_.Name -eq $r.Name }
|
||||
$nsTag = if ($v.NS) { 'yes' } else { ' - ' }
|
||||
$freq = "$($v.Freq)Hz"
|
||||
$readme += "{0,-30} {1,6} {2,8} {3,3} {4,6} {5,5} {6}`n" -f "tsconf_$($r.Name).img", $v.Budget, $freq, $nsTag, $r.Code, $r.Free, $r.Status
|
||||
}
|
||||
|
||||
$readme += @"
|
||||
|
||||
What to test
|
||||
------------
|
||||
1. Higher budget helps (b32/b48/b64 vs b8/b16)
|
||||
-> Increase VGM_FILL_CMD_BUDGET.
|
||||
|
||||
2. Lower freq helps (hf or qf better than base)
|
||||
-> ISR overhead is the bottleneck, reduce frequency.
|
||||
|
||||
3. Higher freq helps (base better than hf/qf)
|
||||
-> More granular timing needed, keep higher ISR.
|
||||
|
||||
4. _ns variants better -> short waits 0x70-0x7F are the problem.
|
||||
|
||||
5. Nothing helps -> something else (OPL3 write delay, VGM parsing, etc.)
|
||||
"@
|
||||
|
||||
Set-Content "$outDir\README.txt" -Value $readme -NoNewline -Encoding UTF8
|
||||
Write-Host "`nREADME.txt written to $outDir" -ForegroundColor Green
|
||||
|
||||
Write-Host "`nLegend:" -ForegroundColor DarkGray
|
||||
Write-Host " qf = quarter freq (683 Hz)" -ForegroundColor DarkGray
|
||||
Write-Host " hf = half freq (1367 Hz)" -ForegroundColor DarkGray
|
||||
Write-Host " (no suffix) = base freq (2734 Hz)" -ForegroundColor DarkGray
|
||||
Write-Host " bN = budget N commands per ISR tick" -ForegroundColor DarkGray
|
||||
Write-Host " ns = no short waits (0x70-0x7F skipped)" -ForegroundColor DarkGray
|
||||
Binary file not shown.
@@ -0,0 +1,69 @@
|
||||
/* Auto-generated by gen_freq_tables.js — DO NOT EDIT */
|
||||
#ifndef FREQ_LUT_MAP_H
|
||||
#define FREQ_LUT_MAP_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* AY/YM2149: bypass |psg_khz - 1750| <= 35 */
|
||||
#define FREQ_LUT_HW_KHZ 1750u
|
||||
#define FREQ_LUT_BYPASS_TOL 35u /* 2% */
|
||||
/* YM2203: bypass |ym_khz - 3500| <= 70 */
|
||||
#define FREQ_LUT_HW_YM_KHZ 3500u
|
||||
#define FREQ_LUT_BYPASS_TOL_YM 70u /* 2% */
|
||||
|
||||
#define FREQ_LUT_AY_COUNT 6u
|
||||
#define FREQ_LUT_YM_COUNT 5u
|
||||
#define FREQ_LUT_PSG_ENTRIES 4096u /* 12-bit PSG */
|
||||
#define FREQ_LUT_PSG_BYTES 8192u /* per PSG table */
|
||||
|
||||
typedef struct {
|
||||
uint16_t clk_khz; /* canonical clock in kHz (PSG or YM master) */
|
||||
uint16_t tol_khz; /* ±tolerance in kHz (5%) */
|
||||
uint8_t page; /* plugin page (PAGE_BASE-based) */
|
||||
uint16_t offset; /* PSG table byte offset within page */
|
||||
} freq_lut_entry_t;
|
||||
|
||||
/* AY/YM2149 PSG table map (tolerance = 5% of canonical PSG clock)
|
||||
* Layout: 2 ratios per page, PSG_A@0x0000, PSG_B@0x2000 */
|
||||
static const freq_lut_entry_t freq_lut_map_ay[FREQ_LUT_AY_COUNT] = {
|
||||
{ 1790u, 90u, 1, 0x0000 }, /* NTSC_1790 */
|
||||
{ 1500u, 75u, 1, 0x2000 }, /* 3MHz_1500 */
|
||||
{ 2000u, 100u, 2, 0x0000 }, /* 4MHz_2000 */
|
||||
{ 1000u, 50u, 2, 0x2000 }, /* CPC_1000 */
|
||||
{ 1250u, 63u, 3, 0x0000 }, /* 1250 */
|
||||
{ 750u, 38u, 3, 0x2000 } /* 1500_750 */
|
||||
};
|
||||
|
||||
/* YM2203 table map (full master clock, PSG table = same as AY entry) */
|
||||
static const freq_lut_entry_t freq_lut_map_ym[FREQ_LUT_YM_COUNT] = {
|
||||
{ 3580u, 179u, 1, 0x0000 }, /* NTSC_1790 (YM2203@3580kHz) */
|
||||
{ 3000u, 150u, 1, 0x2000 }, /* 3MHz_1500 (YM2203@3000kHz) */
|
||||
{ 4000u, 200u, 2, 0x0000 }, /* 4MHz_2000 (YM2203@4000kHz) */
|
||||
{ 2500u, 125u, 3, 0x0000 }, /* 1250 (YM2203@2500kHz) */
|
||||
{ 1500u, 75u, 3, 0x2000 } /* 1500_750 (YM2203@1500kHz) */
|
||||
};
|
||||
|
||||
/* FM F-Number множитель (YM2203): scaled = fnum × fm_mul / 7.
|
||||
* 0 = bypass (масштабирование не нужно).
|
||||
* ASM на Z80: умножение через shifts+adds, деление на 7 binary division */
|
||||
static const uint8_t freq_lut_fm_mul[FREQ_LUT_YM_COUNT] = {
|
||||
0, /* NTSC_1790: 1.022727 = bypass (2.3%) */
|
||||
6, /* 3MHz_1500: 0.857143 = ×6/7 */
|
||||
8, /* 4MHz_2000: 1.142857 = ×8/7 */
|
||||
5, /* 1250: 0.714286 = ×5/7 */
|
||||
3 /* 1500_750: 0.428571 = ×3/7 */
|
||||
};
|
||||
|
||||
/*
|
||||
* Usage in vgm.c (parse_header):
|
||||
*
|
||||
* 1. Bypass check: |clock_khz - HW| <= BYPASS_TOL → native
|
||||
* 2. Find matching table → set page, PSG base = offset
|
||||
* 3. For YM2203: fm_mul = freq_lut_fm_mul[j]
|
||||
*
|
||||
* PSG: scaled_period = freq_lut_base[period_12bit]
|
||||
* FM: asm_scale_fm() : fb_scaled = fnum × fm_mul / 7
|
||||
* if (fb_scaled > 2047) block++, fb_scaled >>= 1
|
||||
*/
|
||||
|
||||
#endif /* FREQ_LUT_MAP_H */
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* @file isr.h
|
||||
* @brief ISR (Interrupt Service Routine) для FRAME-прерывания TSConfig
|
||||
* @version 2.0
|
||||
*
|
||||
* Зависимости: types.h.
|
||||
* Реализация: asm/isr.s (чистый ASM, hot-path без C).
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* АРХИТЕКТУРА
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Частота ISR задаётся вариантом сборки (ISR_FREQ в variant_cfg.h).
|
||||
* N_ENTRIES прерываний × STEP T-states = 71680 T = 1 TV-кадр (замыкание).
|
||||
*
|
||||
* Позиции INT — предрассчитанная таблица POS_TABLE (N_ENTRIES x 5 байт).
|
||||
* ISR читает 3 байта через OUTI и записывает в VSINTH/VSINTL/HSINT,
|
||||
* затем переходит по next_ptr (замкнутый цикл).
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ДВОЙНАЯ БУФЕРИЗАЦИЯ КОМАНД
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* CMD_BUF_A, CMD_BUF_B — по 512 байт (до 128 команд).
|
||||
* ISR читает из активного буфера, main loop заполняет свободный.
|
||||
* Переключение буфера: CMD_END_BUF в конце каждого заполненного.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ФОРМАТ КОМАНДЫ (4 байта, выровнено)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Код │ Байт 1 │ Байт 2 │ Байт 3 │ Действие
|
||||
* ──────────────┼────────┼────────┼────────┼─────────────────────
|
||||
* CMD_WRITE_AY │ reg │ val │ 0 │ OUT AY8910 chip 1
|
||||
* CMD_INC_SEC │ 0 │ 0 │ 0 │ Инкр. isr_play_seconds
|
||||
* CMD_WRITE_AY2 │ reg │ val │ 0 │ OUT AY8910 chip 2 (TS)
|
||||
* CMD_CALL_WC │ 0 │ 0 │ 0 │ Вызов WC ISR handler
|
||||
* CMD_WRITE_B0 │ reg │ val │ 0 │ OUT OPL3 Bank 0
|
||||
* CMD_SKIP_TICKS│ N │ smsk_lo│ smsk_hi│ Пропустить N позиций pos_table
|
||||
* CMD_WRITE_SAA │ reg │ val │ 0 │ OUT SAA1099 chip 0
|
||||
* CMD_WRITE_B1 │ reg │ val │ 0 │ OUT OPL3 Bank 1
|
||||
* CMD_WRITE_SA2 │ reg │ val │ 0 │ OUT SAA1099 chip 1
|
||||
* CMD_WRITE_WAVE│ reg │ val │ 0 │ OUT OPL4 Wave-часть (#7E/#7F)
|
||||
* CMD_WAIT │ lo │ hi │ 0 │ Ждать (hi<<8)|lo тиков
|
||||
* CMD_END_BUF │ 0 │ 0 │ 0 │ Переключить буфер
|
||||
*
|
||||
* CMD_SKIP_TICKS carries 16-bit spectrum bitmask: bit N → bar N triggered
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПРИМЕР ИСПОЛЬЗОВАНИЯ
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* #include "isr.h"
|
||||
*
|
||||
* // 1. Заполнить оба буфера
|
||||
* fill_buffer(cmd_buf_a);
|
||||
* fill_buffer(cmd_buf_b);
|
||||
*
|
||||
* // 2. Настроить ISR
|
||||
* isr_enabled = 0; // пока не запускать
|
||||
* isr_active_buf = 0; // начать с буфера A
|
||||
* isr_init(); // IM2 + таблица векторов
|
||||
*
|
||||
* // 3. Запустить
|
||||
* isr_enabled = 1;
|
||||
* __asm__("ei");
|
||||
*
|
||||
* // 4. В main loop:
|
||||
* if (isr_active_buf != last_buf) {
|
||||
* fill_buffer(isr_active_buf ? cmd_buf_a : cmd_buf_b);
|
||||
* last_buf = isr_active_buf;
|
||||
* }
|
||||
*/
|
||||
|
||||
#ifndef ISR_H
|
||||
#define ISR_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* ── Коды команд (8 типов, шаг 0x20 + ISR_DONE) ─────────────────── */
|
||||
#define CMD_WRITE_AY 0x00 /* AY8910 chip 1: [reg, val, 0] */
|
||||
#define CMD_INC_SEC 0x10 /* Инкрементировать счётчик секунд: [0,0,0] */
|
||||
#define CMD_WRITE_AY2 0x20 /* AY8910 chip 2 (TurboSound): [reg,val,0]*/
|
||||
#define CMD_CALL_WC 0x30 /* Вызов WC ISR handler: [0,0,0] */
|
||||
#define CMD_WRITE_B0 0x40 /* OPL3 Bank 0 write: [reg, val, 0] */
|
||||
#define CMD_SKIP_TICKS 0x50 /* Пропустить N pos_table: [N,sml,smh] */
|
||||
#define CMD_WRITE_SAA 0x60 /* SAA1099 write: [reg, val, 0] bit7=chip */
|
||||
#define CMD_WRITE_B1 0x80 /* OPL3 Bank 1 write: [reg, val, 0] */
|
||||
#define CMD_WRITE_WAVE 0x70 /* OPL4 Wave-часть write (ZXM-MoonSound, порты #7E/#7F): [reg, val, 0] */
|
||||
#define CMD_WAIT 0xC0 /* Ждать N ISR-тиков: [lo, hi, 0] */
|
||||
#define CMD_END_BUF 0xE0 /* Переключить буфер: [0, 0, 0] */
|
||||
#define CMD_ISR_DONE 0xF0 /* Заморозить ISR, выставить isr_done: [0,0,0] */
|
||||
|
||||
/* ── Размер командного буфера ────────────────────────────────────── */
|
||||
#define CMD_BUF_SIZE 512 /* байт; 128 команд по 4 байта */
|
||||
|
||||
/* ── Параметры варианта (ISR_FREQ, VGM_SAMPLE_*, budget) ───── */
|
||||
#include "variant_cfg.h"
|
||||
|
||||
/* ── Переменные ISR (volatile = разделяемые между ISR и main) ────── */
|
||||
|
||||
/** Управляющий байт MultiSound (пишется в #FFFD перед каждой записью AY).
|
||||
* bit0: chip select (0=chip1, 1=chip2) — ISR управляет автоматически.
|
||||
* bit1: 1=normal read (всегда 1).
|
||||
* bit2: 0=FM ON (high-Z), 1=FM OFF (forced low).
|
||||
* bit3: 0=SAA CLK ON, 1=SAA CLK OFF.
|
||||
* bit[7:4]: 0xF (маркер MultiSound ctrl).
|
||||
* Idle = 0xFE (FM OFF, SAA OFF). */
|
||||
extern volatile uint8_t isr_ms_ctrl;
|
||||
|
||||
/** Индекс активного буфера: 0 = A, 1 = B.
|
||||
* Записывается ISR при CMD_END_BUF, читается main loop. */
|
||||
extern volatile uint8_t isr_active_buf;
|
||||
|
||||
/** Флаг включения: 0 = ISR пропускает выполнение команд
|
||||
* (pos_table и tick_ctr продолжают работать).
|
||||
* Записывается main loop, читается ISR.
|
||||
* instant_abort() использует это для заморозки без доп. флагов. */
|
||||
extern volatile uint8_t isr_enabled;
|
||||
|
||||
/** Счётчик тиков ISR. Инкрементируется каждый ISR-вызов (частота = ISR_FREQ),
|
||||
* оборачивается при 256 (полный оборот ≈ 256/ISR_FREQ секунд).
|
||||
* main loop читает для определения прошедшего времени. */
|
||||
extern volatile uint8_t isr_tick_ctr;
|
||||
|
||||
/** Цвет бордюра, восстанавливаемый ISR при выходе.
|
||||
* Записывается main loop (0=black, 2=red и т.д.), читается ISR. */
|
||||
extern volatile uint8_t isr_border_color;
|
||||
|
||||
/** Счётчик секунд воспроизведения.
|
||||
* Инкрементируется ISR по команде CMD_INC_SEC, читается main loop. */
|
||||
extern volatile uint16_t isr_play_seconds;
|
||||
|
||||
/** Флаг завершения: 1 = ISR заморожен на CMD_ISR_DONE.
|
||||
* Записывается ISR, читается main loop.
|
||||
* Main loop очищает при старте воспроизведения. */
|
||||
extern volatile uint8_t isr_done;
|
||||
|
||||
/** Командные буферы A и B. Заполняются main loop, читаются ISR. */
|
||||
extern uint8_t cmd_buf_a[CMD_BUF_SIZE];
|
||||
extern uint8_t cmd_buf_b[CMD_BUF_SIZE];
|
||||
|
||||
/* ── Spectrum analyzer (16 bars, 0-8 levels) ─────────────────────── */
|
||||
#define SPECTRUM_BARS 16
|
||||
#define SPECTRUM_MAX_LEVEL 8
|
||||
|
||||
/** уровни спектра для каждой из 16 полос, 0..SPECTRUM_MAX_LEVEL.
|
||||
* Заполняются ISR (через CMD_SKIP_TICKS), читаются main loop для рендеринга.
|
||||
* Каждый бит в CMD_SKIP_TICKS соответствует одной полосе, устанавливается при активации.
|
||||
* Например, если бит 0x0001 установлен, то полоса 0 (самая левая) будет на максимальном уровне.
|
||||
* Если бит 0x0002 установлен, то полоса 1 будет на максимальном уровне, и так далее.
|
||||
*/
|
||||
extern volatile uint8_t spectrum_levels[SPECTRUM_BARS];
|
||||
|
||||
/** Контрольный счётчик текущей задержки CMD_WAIT.
|
||||
* ISR декрементирует при каждом тике, переходит к следующей команде после достижения 0.
|
||||
* Не используется напрямую main loop — только ISR. */
|
||||
extern volatile uint16_t isr_wait_ctr;
|
||||
|
||||
/** Указатель чтения ISR внутри активного буфера. */
|
||||
extern volatile uint16_t isr_read_ptr;
|
||||
|
||||
/* ── Функции ─────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Перехват вектора IM2 (#5BFF) и настройка 2734 Hz.
|
||||
*
|
||||
* Действия:
|
||||
* 1. Сохранить вектор WC (#5BFF) → isr_saved_vec
|
||||
* 2. Записать адрес isr_handler в #5BFF
|
||||
* 3. Настроить INTMASK = FRAME only, позиции INT
|
||||
* 4. Инициализировать pos_ptr, wait_ctr, tick_ctr, read_ptr
|
||||
* 5. IM 2 (EI делается вызывающим кодом)
|
||||
*
|
||||
* ВАЖНО: вызывать ПОСЛЕ заполнения обоих буферов!
|
||||
* ВАЖНО: вызывать при DI (прерывания выключены).
|
||||
*/
|
||||
void isr_init(void);
|
||||
|
||||
/**
|
||||
* Восстановить вектор WC и сбросить INT-позиции.
|
||||
*
|
||||
* Действия:
|
||||
* 1. Восстановить оригинальный вектор из isr_saved_vec → #5BFF
|
||||
* 2. Сбросить позицию INT на line=0 (один INT/кадр)
|
||||
* 3. INTMASK = FRAME only (WC default)
|
||||
* НЕ делает EI — вызывающий код решает сам.
|
||||
*
|
||||
* Безопасно вызывать без предшествующего isr_init (no-op).
|
||||
*/
|
||||
void isr_deinit(void);
|
||||
|
||||
/**
|
||||
* Вызов оригинального WC ISR handler из main loop.
|
||||
*
|
||||
* WC ожидает периодический вызов своего ISR (обновление таймера,
|
||||
* клавиатуры, часов). Вызывать ~50 Гц из main loop.
|
||||
* Делает DI → JP в WC handler → WC делает EI+RET.
|
||||
* Безопасно вызывать без предшествующего isr_init (no-op).
|
||||
*/
|
||||
void call_wc_handler(void);
|
||||
|
||||
#endif /* ISR_H */
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @file keys.h
|
||||
* @brief PS/2 клавиатура через TSConfig CMOS FIFO (#EFF7/#DFF7/#BFF7)
|
||||
*
|
||||
* ps2_init() — одноразовая инициализация: открытие PS/2 портов,
|
||||
* активация FIFO, старт FSM в режиме DRAIN.
|
||||
* ps2_poll() — вызывать из главного цикла; rate-limit (~1 раз в TV-кадр).
|
||||
* За один вызов полностью сливает FIFO (tight loop).
|
||||
* Возвращает последний найденный KEY_* или KEY_NONE.
|
||||
*
|
||||
* Единственный бит состояния — skip flag (после F0 пропустить 1 байт).
|
||||
* E0 не требует skip — сами коды не совпадают с нашими, а E0 F0 SC
|
||||
* корректно обрабатывается через F0→skip.
|
||||
*
|
||||
* PS/2 Set 2 скан-коды:
|
||||
* ESC (#76) → KEY_ESC — выход
|
||||
* SPACE (#29) → KEY_NEXT — следующий трек
|
||||
* P (#4D) → KEY_PREV — предыдущий трек
|
||||
* 1 (#16) → KEY_SAA1 — SAA режим 1 (chip0)
|
||||
* 2 (#1E) → KEY_SAA2 — SAA режим 2 (chip1)
|
||||
*/
|
||||
|
||||
#ifndef KEYS_H
|
||||
#define KEYS_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* ── Коды клавиш (возврат ps2_poll) ──────────────────────────────────── */
|
||||
#define KEY_NONE 0 /* нет нажатия */
|
||||
#define KEY_ESC 1 /* ESC → выход */
|
||||
#define KEY_NEXT 2 /* SPACE → следующий */
|
||||
#define KEY_PREV 3 /* P → предыдущий */
|
||||
#define KEY_SAA1 4 /* 1 → SAA mode 1 */
|
||||
#define KEY_SAA2 5 /* 2 → SAA mode 2 */
|
||||
#define KEY_SAA3 6 /* 3 → SAA mode 3 */
|
||||
|
||||
/**
|
||||
* Инициализация PS/2: открыть порты, активировать FIFO, слив буфера.
|
||||
* Вызвать один раз при старте плагина (до первого ps2_poll).
|
||||
*/
|
||||
void ps2_init(void);
|
||||
|
||||
/**
|
||||
* Деинициализация PS/2: слив FIFO, деактивация FIFO, отключение портов.
|
||||
* Вызвать при выходе из плагина (после главного цикла).
|
||||
*/
|
||||
void ps2_deinit(void);
|
||||
|
||||
/**
|
||||
* Опросить PS/2 FIFO (rate-limited, ~1 раз в кадр).
|
||||
* Клавиша подтверждается только после полного цикла make + release.
|
||||
* Возвращает KEY_* при подтверждении, KEY_NONE иначе.
|
||||
*/
|
||||
uint8_t ps2_poll(void);
|
||||
|
||||
#endif /* KEYS_H */
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @file opl3.h
|
||||
* @brief OPL3 (YMF262) — константы регистров и функции записи
|
||||
* @version 2.0
|
||||
*
|
||||
* Самодостаточная библиотека: зависит только от types.h.
|
||||
* Реализация — asm/opl3.s (чистый inline OUT).
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПОРТЫ TSConfig ДЛЯ OPL3
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Порт │ Описание
|
||||
* ──────┼────────────────────────────
|
||||
* #C4 │ Bank 0 Address (reg number)
|
||||
* #C5 │ Bank 0 Data (reg value)
|
||||
* #C6 │ Bank 1 Address
|
||||
* #C7 │ Bank 1 Data
|
||||
*
|
||||
* Задержка между записью адреса и данных НЕ нужна при 14 МГц
|
||||
* с wait-states: одна инструкция OUT (C),A занимает достаточно
|
||||
* тактов чтобы YMF262 обработал адрес до прихода данных.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* КАРТА РЕГИСТРОВ OPL3 (Bank 0, то же для Bank 1)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Рег. │ Описание
|
||||
* ───────────┼────────────────────────────────────────
|
||||
* 0x01 │ Wavesel enable (бит 5 = 1)
|
||||
* 0x04 │ Connection select (4-op mode, Bank 0)
|
||||
* 0x05 (B1) │ OPL3 mode enable (бит 0 = 1)
|
||||
* 0x08 │ Note select / CSW (только Bank 0)
|
||||
* 0x20–0x35 │ Operator: AM/VIB/EG type/KSR/MULT
|
||||
* 0x40–0x55 │ Operator: KSL/Total Level (громкость)
|
||||
* 0x60–0x75 │ Operator: Attack Rate/Decay Rate
|
||||
* 0x80–0x95 │ Operator: Sustain Level/Release Rate
|
||||
* 0xA0–0xA8 │ Channel: F-Number low 8 bits
|
||||
* 0xB0–0xB8 │ Channel: Key-On + Octave + F-Number hi
|
||||
* 0xBD │ Percussion mode / AM depth / VIB depth
|
||||
* 0xC0–0xC8 │ Channel: Feedback + Algorithm + Output
|
||||
* 0xE0–0xF5 │ Operator: Waveform select
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПРИМЕР
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* #include "opl3.h"
|
||||
*
|
||||
* void play_note(void) {
|
||||
* opl3_init(); // включить OPL3
|
||||
* opl3_write_b0(0x20, 0x01); // op1: MULT=1
|
||||
* opl3_write_b0(0x40, 0x10); // op1: TL=16
|
||||
* opl3_write_b0(0x60, 0xF0); // op1: AR=F, DR=0
|
||||
* opl3_write_b0(0x80, 0x77); // op1: SL=7, RR=7
|
||||
* opl3_write_b0(0xA0, 0x98); // F-num lo
|
||||
* opl3_write_b0(0xB0, 0x31); // Key-On, Oct=4, F-hi
|
||||
* }
|
||||
*/
|
||||
|
||||
#ifndef OPL3_H
|
||||
#define OPL3_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* ── Порты OPL3 на шине TSConfig ─────────────────────────────────── */
|
||||
#define OPL3_ADDR0 0xC4 /* Bank 0 register address */
|
||||
#define OPL3_DATA0 0xC5 /* Bank 0 register data */
|
||||
#define OPL3_ADDR1 0xC6 /* Bank 1 register address */
|
||||
#define OPL3_DATA1 0xC7 /* Bank 1 register data */
|
||||
|
||||
/* ── Нумерация регистров (часто используемые) ────────────────────── */
|
||||
#define OPL3_REG_WAVESEL 0x01 /* Waveform select enable (бит 5) */
|
||||
#define OPL3_REG_CONNSEL 0x04 /* Connection select (4-op, Bank0) */
|
||||
#define OPL3_REG_OPL3EN 0x05 /* OPL3 mode reg (Bank 1 only!) */
|
||||
#define OPL3_OPL3EN_ON 0x01 /* NEW=1: OPL3 mode (L/R via C0-C8)*/
|
||||
#define OPL3_OPL3EN_OFF 0x00 /* NEW=0: OPL2 compat (L+R always) */
|
||||
#define OPL3_REG_CSW 0x08 /* CSW / Note select (Bank 0) */
|
||||
#define OPL3_REG_BD 0xBD /* Percussion / AM / VIB depth */
|
||||
|
||||
/* Key-On бит в регистрах 0xB0-0xB8 */
|
||||
#define OPL3_KEY_ON 0x20
|
||||
|
||||
/* ── Функции (реализация: asm/opl3.s) ────────────────────────────── */
|
||||
|
||||
/** Записать регистр Bank 0: addr -> #C4, data -> #C5 */
|
||||
void opl3_write_b0(uint8_t addr, uint8_t data);
|
||||
|
||||
/** Записать регистр Bank 1: addr -> #C6, data -> #C7 */
|
||||
void opl3_write_b1(uint8_t addr, uint8_t data);
|
||||
|
||||
/**
|
||||
* Инициализация OPL3:
|
||||
* 1. Bank1 reg[0x05] = 0x01 — NEW=1, OPL3 mode (регистры C0-C8 управляют L/R)
|
||||
* 2. Bank0 reg[0x01] = 0x20 — Waveform Select Enable
|
||||
* 3. Полный сброс всех 18 каналов: KeyOff, TL, F-Num (opl3_reset)
|
||||
*
|
||||
* ⚠ После `opl3_init()` чип находится в режиме OPL3 (NEW=1):
|
||||
* - Регистры C0-C8 задают маршрутизацию L/R (биты 4-5)
|
||||
* - OPL2/OPL1 VGM файлы НЕ устанавливают биты L/R → тишина!
|
||||
*
|
||||
* Для OPL1/OPL2 файлов после init вызовите:
|
||||
* opl3_write_b1(OPL3_REG_OPL3EN, 0x00); // NEW=0: OPL2-compat mode
|
||||
* В этом режиме L+R включены автоматически, биты C0-C8[5:4] игнорируются.
|
||||
*/
|
||||
void opl3_init(void);
|
||||
|
||||
/** Заглушить все 18 каналов (KeyOff: B0-B8 = 0, обе банки) */
|
||||
void opl3_reset(void);
|
||||
|
||||
/** AY-3-8910 / YM2149 chip 1: mixer off + volume 0 */
|
||||
void ay_silence(void);
|
||||
|
||||
/** AY-3-8910 chip 2 (TurboSound): select chip2, silence, switch back */
|
||||
void ay2_silence(void);
|
||||
|
||||
/** AY chip 1: write single register */
|
||||
void ay_write_reg(uint8_t reg, uint8_t val);
|
||||
|
||||
/** AY chip 2 (TurboSound): write single register */
|
||||
void ay2_write_reg(uint8_t reg, uint8_t val);
|
||||
|
||||
/** SAA1099 chip 1: reset + disable + zero amplitudes */
|
||||
void saa_silence(void);
|
||||
|
||||
/** SAA1099 chip 2: reset + disable + zero amplitudes */
|
||||
void saa2_silence(void);
|
||||
|
||||
/** SAA1099 chip 1: write single register */
|
||||
void saa_write_reg(uint8_t reg, uint8_t val);
|
||||
|
||||
/** SAA1099 chip 2: write single register */
|
||||
void saa2_write_reg(uint8_t reg, uint8_t val);
|
||||
|
||||
#endif /* OPL3_H */
|
||||
@@ -0,0 +1,531 @@
|
||||
/**
|
||||
* @file tsconf.h
|
||||
* @brief Полный справочник регистров TSConfig (TSL FPGA-ускоритель ZX Spectrum)
|
||||
* @version 2.0
|
||||
*
|
||||
* Самодостаточная библиотека: зависит только от types.h.
|
||||
* Подходит для любого TSConfig-проекта на SDCC Z80.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* АРХИТЕКТУРА ПОРТОВ
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Все порты TSConfig адресуются по схеме: (REG_NUM << 8) | 0xAF
|
||||
* Запись: OUT (C),A где BC = порт.
|
||||
*
|
||||
* На ASM:
|
||||
* LD BC, TSCONF_PORT_SYSCONFIG ; BC = 0x20AF
|
||||
* LD A, TSCONF_CPU_14MHZ
|
||||
* OUT (C), A
|
||||
*
|
||||
* На C:
|
||||
* tsconf_out(TSCONF_PORT_SYSCONFIG, TSCONF_CPU_14MHZ);
|
||||
*
|
||||
* Макросы-обёртки (конец файла) упрощают типичные операции:
|
||||
* tsconf_set_cpu(TSCONF_CPU_14MHZ);
|
||||
* tsconf_set_border(1);
|
||||
* tsconf_set_frame_int(100, 0);
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ТАЙМИНГИ TV-РЕЖИМА (Pentagon-128)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Строк в кадре: 320 (0–319)
|
||||
* Строки 0–31: вертикальное гашение
|
||||
* Строки 32–319: видимая область (бордер + пиксели)
|
||||
*
|
||||
* Пикселей в строке: 448 (7 МГц), = 224 T-states при 3.5 МГц
|
||||
* Пиксели 0–87: горизонтальное гашение
|
||||
* Пиксели 88–447: видимая область
|
||||
*
|
||||
* T-states в кадре: 320 × 224 = 71680 T
|
||||
*
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
* ПРЕРЫВАНИЯ (IM2)
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Три источника, каждый выставляет свой вектор на шину данных:
|
||||
* FRAME (#FF, приоритет 0) — совпадение растровых счётчиков
|
||||
* с регистрами HSINT/VSINT
|
||||
* LINE (#FD, приоритет 1) — каждая строка при HCNT = 0
|
||||
* DMA (#FB, приоритет 2) — конец DMA-транзакции
|
||||
*
|
||||
* HSINT = HCNT[8:1]: горизонтальная позиция (0–223). >223 = выкл.
|
||||
* VSINTL/VSINTH = VCNT[8:0]: вертикальная строка (0–319). >319 = выкл.
|
||||
*
|
||||
* VINT_INC[3:0] в VSINTH: автоинкремент VCNT после каждого FRAME-INT
|
||||
* (позволяет менять строку прерывания без записи в порты).
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ВИДЕО
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* VM[1:0]: 00=ZX (256x192, атрибуты), 01=16c, 10=256c, 11=txt
|
||||
* RRES[1:0]: 00=256x192, 01=320x200, 10=320x240, 11=360x288
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* DMA
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Адрес = 22-бит: [X(8)][H(6)][L(7)][0] — младший бит всегда 0.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПРИМЕР (минимальный)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* #include "tsconf.h"
|
||||
*
|
||||
* void demo(void) {
|
||||
* tsconf_set_cpu(TSCONF_CPU_14MHZ); // 14 МГц
|
||||
* tsconf_set_border(2); // красный бордюр
|
||||
* tsconf_set_vconfig(TSCONF_VM_ZX // ZX-режим
|
||||
* | TSCONF_RRES_256x192); // 256x192
|
||||
* tsconf_set_frame_int(100, 0); // INT на строке 100
|
||||
* tsconf_set_intmask(TSCONF_INT_FRAME); // только FRAME
|
||||
* tsconf_set_cpu(TSCONF_CPU_3_5MHZ); // вернуть 3.5
|
||||
* }
|
||||
*/
|
||||
|
||||
#ifndef TSCONF_H
|
||||
#define TSCONF_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* Макрос формирования адреса порта
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT(reg) (((uint16_t)(reg) << 8) | 0xAF)
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* ВИДЕО — VConfig (#00AF)
|
||||
* W: RRES[1:0] | - | NOGFX | NOTSU | GFXOVR* | FT_EN** | VM[1:0]
|
||||
* R: - | PWR_UP | - | FDRVER | VDVER[2:0]
|
||||
* * только VDAC, ** только VDAC2. Сброс: 0x00
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_VCONFIG TSCONF_PORT(0x00)
|
||||
|
||||
/* VM[1:0] — видеорежим */
|
||||
#define TSCONF_VM_ZX 0x00 /* ZX-Spectrum, 256×192, атрибуты */
|
||||
#define TSCONF_VM_16C 0x01 /* 16 цветов, 4 бита/пиксель */
|
||||
#define TSCONF_VM_256C 0x02 /* 256 цветов, 8 бит/пиксель */
|
||||
#define TSCONF_VM_TXT 0x03 /* Текстовый режим */
|
||||
#define TSCONF_VM_MASK 0x03
|
||||
|
||||
/* FT_EN, GFXOVR, NOTSU, NOGFX */
|
||||
#define TSCONF_VC_FT_EN 0x04 /* FT812 enable (VDAC2) */
|
||||
#define TSCONF_VC_GFXOVR 0x08 /* GFX поверх TSU (VDAC) */
|
||||
#define TSCONF_VC_NOTSU 0x10 /* Отключить TSU-графику */
|
||||
#define TSCONF_VC_NOGFX 0x20 /* Отключить основную графику */
|
||||
|
||||
/* RRES[1:0] — разрешение экрана */
|
||||
#define TSCONF_RRES_256x192 (0x00 << 6)
|
||||
#define TSCONF_RRES_320x200 (0x01 << 6)
|
||||
#define TSCONF_RRES_320x240 (0x02 << 6)
|
||||
#define TSCONF_RRES_360x288 (0x03 << 6)
|
||||
#define TSCONF_RRES_MASK (0x03 << 6)
|
||||
|
||||
/* R: Status (то же адрес #00AF, чтение) */
|
||||
#define TSCONF_STATUS_VDVER_MASK 0x07
|
||||
#define TSCONF_STATUS_FDRVER 0x10
|
||||
#define TSCONF_STATUS_PWR_UP 0x40
|
||||
#define TSCONF_VDVER_2BIT_PWM 0x00
|
||||
#define TSCONF_VDVER_3BIT 0x01
|
||||
#define TSCONF_VDVER_4BIT 0x02
|
||||
#define TSCONF_VDVER_5BIT 0x03
|
||||
#define TSCONF_VDVER_VDAC2 0x07
|
||||
|
||||
/* ── VPage (#01AF): адрес видеостраницы RA[21:14] ─────────────────── */
|
||||
#define TSCONF_PORT_VPAGE TSCONF_PORT(0x01)
|
||||
|
||||
/* ── GXOffsL (#02AF), GXOffsH (#03AF): X-смещение графики ─────────── */
|
||||
#define TSCONF_PORT_GXOFFSL TSCONF_PORT(0x02) /* XO[7:0] */
|
||||
#define TSCONF_PORT_GXOFFSH TSCONF_PORT(0x03) /* бит 0 = XO[8] */
|
||||
|
||||
/* ── GYOffsL (#04AF), GYOffsH (#05AF): Y-смещение графики ─────────── */
|
||||
#define TSCONF_PORT_GYOFFSL TSCONF_PORT(0x04) /* YO[7:0] */
|
||||
#define TSCONF_PORT_GYOFFSH TSCONF_PORT(0x05) /* бит 0 = YO[8] */
|
||||
|
||||
/* ── TSConfig (#06AF): включение tile/sprite движка ────────────────── */
|
||||
#define TSCONF_PORT_TSCONFIG TSCONF_PORT(0x06)
|
||||
#define TSCONF_TS_EXT 0x01 /* TSU 360×288 (не ZX-Evo) */
|
||||
#define TSCONF_T0Z_EN 0x04 /* Тайл [0] виден (плоск. 0) */
|
||||
#define TSCONF_T1Z_EN 0x08 /* Тайл [0] виден (плоск. 1) */
|
||||
#define TSCONF_T0_EN 0x20 /* Tile-layer 0 enable */
|
||||
#define TSCONF_T1_EN 0x40 /* Tile-layer 1 enable */
|
||||
#define TSCONF_S_EN 0x80 /* Sprite layers enable */
|
||||
|
||||
/* ── PalSel (#07AF): выбор палитр ──────────────────────────────────── */
|
||||
/* GPAL[7:4] | T0PAL[7:6] | T1PAL[7:6]. Сброс = 0x0F */
|
||||
#define TSCONF_PORT_PALSEL TSCONF_PORT(0x07)
|
||||
|
||||
/* ── Border (#0FAF): цвет бордюра ──────────────────────────────────── */
|
||||
#define TSCONF_PORT_BORDER TSCONF_PORT(0x0F)
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* УПРАВЛЕНИЕ ПАМЯТЬЮ
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
* RA[21:14] — номер 16KB-страницы DRAM
|
||||
* Page0 = W (write-only), Page1 = W, Page2 = RW, Page3 = RW
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_PAGE0 TSCONF_PORT(0x10) /* окно #0000, W */
|
||||
#define TSCONF_PORT_PAGE1 TSCONF_PORT(0x11) /* окно #4000, W */
|
||||
#define TSCONF_PORT_PAGE2 TSCONF_PORT(0x12) /* окно #8000, RW */
|
||||
#define TSCONF_PORT_PAGE3 TSCONF_PORT(0x13) /* окно #C000, RW */
|
||||
|
||||
/* ── FMAddr (#14AF): адрес Font Maps ───────────────────────────────── */
|
||||
#define TSCONF_PORT_FMADDR TSCONF_PORT(0x14)
|
||||
#define TSCONF_FMADDR_EN 0x10 /* бит 4 = FM_EN. Биты 3:0 = A[15:12] */
|
||||
|
||||
/* ── TMPage (#15AF): страница TileMap ──────────────────────────────── */
|
||||
#define TSCONF_PORT_TMPAGE TSCONF_PORT(0x15) /* RA[21:14] */
|
||||
|
||||
/* ── T0GPage (#16AF): страница графики тайлов плоскости 0 ──────────── */
|
||||
#define TSCONF_PORT_T0GPAGE TSCONF_PORT(0x16) /* RA[21:17] */
|
||||
|
||||
/* ── T1GPage (#17AF): страница графики тайлов плоскости 1 ──────────── */
|
||||
#define TSCONF_PORT_T1GPAGE TSCONF_PORT(0x17) /* RA[21:17] */
|
||||
|
||||
/* ── SGPage (#18AF): страница графики спрайтов ─────────────────────── */
|
||||
#define TSCONF_PORT_SGPAGE TSCONF_PORT(0x18) /* RA[21:17] */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* DMA — адреса источника и назначения
|
||||
* Адрес = 22-бит: [X(8)][H(6)][L(7)][0]
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_DMASADDRL TSCONF_PORT(0x19) /* Source RA[7:1] */
|
||||
#define TSCONF_PORT_DMASADDRH TSCONF_PORT(0x1A) /* Source RA[13:8] */
|
||||
#define TSCONF_PORT_DMASADDRX TSCONF_PORT(0x1B) /* Source RA[21:14] */
|
||||
#define TSCONF_PORT_DMADADDRL TSCONF_PORT(0x1C) /* Dest RA[7:1] */
|
||||
#define TSCONF_PORT_DMADADDRH TSCONF_PORT(0x1D) /* Dest RA[13:8] */
|
||||
#define TSCONF_PORT_DMADADDRX TSCONF_PORT(0x1E) /* Dest RA[21:14] */
|
||||
#define TSCONF_PORT_DMAWPDEV TSCONF_PORT(0x25) /* DMA Wait Port Dev */
|
||||
#define TSCONF_PORT_DMALEN TSCONF_PORT(0x26) /* Burst Length [7:0] */
|
||||
#define TSCONF_PORT_DMACTRL TSCONF_PORT(0x27) /* Ctrl W / Status R */
|
||||
#define TSCONF_PORT_DMANUM TSCONF_PORT(0x28) /* Bursts Num [7:0] */
|
||||
#define TSCONF_PORT_DMANUMH TSCONF_PORT(0x2C) /* Bursts Num [9:8] */
|
||||
#define TSCONF_PORT_DMAWPADDR TSCONF_PORT(0x2D) /* Wait Port Addr */
|
||||
|
||||
/* DMACtrl W — DDEV[3:0]: режим DMA */
|
||||
#define TSCONF_DMA_RAM_RAM 0x09 /* RAM→RAM copy */
|
||||
#define TSCONF_DMA_BLT1_RAM 0x89 /* RAM→RAM, пропуск нулей (OPT=1) */
|
||||
#define TSCONF_DMA_FILL 0x21 /* Fill: src → dest*N */
|
||||
#define TSCONF_DMA_RAM_CRAM 0xA1 /* RAM→CRAM (палитра) */
|
||||
#define TSCONF_DMA_SPI_RAM 0x11 /* SPI→RAM */
|
||||
#define TSCONF_DMA_RAM_SPI 0x91 /* RAM→SPI */
|
||||
#define TSCONF_DMA_IDE_RAM 0x19 /* IDE→RAM */
|
||||
#define TSCONF_DMA_RAM_IDE 0x99 /* RAM→IDE */
|
||||
#define TSCONF_DMA_FDD_RAM 0x29 /* FDD→RAM */
|
||||
#define TSCONF_DMA_RAM_WTP 0xB9 /* RAM→Wait Port */
|
||||
#define TSCONF_DMA_WTP_RAM 0x39 /* Wait Port→RAM */
|
||||
#define TSCONF_DMA_BLT2_RAM 0x31 /* Блиттер с аддером */
|
||||
|
||||
/* DMACtrl — флаги */
|
||||
#define TSCONF_DMA_ASZ_256 0x00 /* Выравнивание 256 байт */
|
||||
#define TSCONF_DMA_ASZ_512 0x04 /* Выравнивание 512 байт */
|
||||
#define TSCONF_DMA_D_ALGN 0x08 /* Выравнивание назначения */
|
||||
#define TSCONF_DMA_S_ALGN 0x10 /* Выравнивание источника */
|
||||
/* R: DMAStatus */
|
||||
#define TSCONF_DMA_ACT 0x80 /* DMA активна */
|
||||
|
||||
/* DMAWPDev */
|
||||
#define TSCONF_DMAWP_GLUCLOCK 0x00
|
||||
#define TSCONF_DMAWP_COMPORT 0x01
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* СИСТЕМНАЯ КОНФИГУРАЦИЯ
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── SysConfig (#20AF): частота CPU и кэш ──────────────────────────── */
|
||||
#define TSCONF_PORT_SYSCONFIG TSCONF_PORT(0x20)
|
||||
#define TSCONF_CPU_3_5MHZ 0x00
|
||||
#define TSCONF_CPU_7MHZ 0x01
|
||||
#define TSCONF_CPU_14MHZ 0x02
|
||||
#define TSCONF_CPU_MASK 0x03
|
||||
#define TSCONF_CACHE_EN 0x04 /* Включить кэш (все 4 окна) */
|
||||
|
||||
/* ── MemConfig (#21AF) ────────────────────────────────────────────────
|
||||
* ROM128: бит 0 = то же что бит 4 в #7FFD (BootROM/Basic)
|
||||
* W0_WE: разрешить запись в окно #0000
|
||||
* !W0_MAP: 0=маппинг через Page0, 1=стандартная страница 0
|
||||
* W0_RAM: 0=ROM, 1=RAM в #0000
|
||||
* LCK128: режим блокировки 128K-совместимости
|
||||
* ──────────────────────────────────────────────────────────────────── */
|
||||
#define TSCONF_PORT_MEMCONFIG TSCONF_PORT(0x21)
|
||||
#define TSCONF_MEM_ROM128 0x01
|
||||
#define TSCONF_MEM_W0_WE 0x02
|
||||
#define TSCONF_MEM_W0_MAP_DIS 0x04
|
||||
#define TSCONF_MEM_W0_RAM 0x08
|
||||
#define TSCONF_MEM_LCK128_512K 0x00 /* 512K, биты 7:6 #7FFD=Page3[4:3] */
|
||||
#define TSCONF_MEM_LCK128_128K 0x40 /* 128K, биты 7:6 #7FFD=00 */
|
||||
#define TSCONF_MEM_LCK128_AUTO 0x80 /* Auto (!a[13]) */
|
||||
#define TSCONF_MEM_LCK128_1M 0xC0 /* 1024K */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* ПРЕРЫВАНИЯ — позиция FRAME-INT
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── HSINT (#22AF): горизонтальная позиция ─────────────────────────── */
|
||||
/* HCNT[8:1] в диапазоне 0–223; >223 = выключить INT в этой строке */
|
||||
#define TSCONF_PORT_HSINT TSCONF_PORT(0x22)
|
||||
#define TSCONF_HSINT_OFF 0xFF
|
||||
|
||||
/* ── VSINTL (#23AF): вертикальная позиция [7:0] ────────────────────── */
|
||||
/* VCNT[7:0]; строки 0–319; >319 = FRAME-INT выключен */
|
||||
#define TSCONF_PORT_VSINTL TSCONF_PORT(0x23)
|
||||
|
||||
/* ── VSINTH (#24AF): вертикальная позиция [8] + автоинкремент ──────── */
|
||||
/* Бит 0 = VCNT[8]; биты [7:4] = VINT_INC[3:0] */
|
||||
/* VINT_INC: после каждого FRAME-INT → VCNT += VINT_INC (0 = нет) */
|
||||
#define TSCONF_PORT_VSINTH TSCONF_PORT(0x24)
|
||||
#define TSCONF_VSINTH_VCNT8 0x01
|
||||
#define TSCONF_VSINTH_INC_MASK 0xF0
|
||||
#define TSCONF_VSINTH_INC(n) (((n) & 0x0F) << 4)
|
||||
|
||||
/* ── INTMask (#2AAF): маска прерываний ─────────────────────────────── */
|
||||
/* Сброс: 0x01 (только FRAME включён) */
|
||||
#define TSCONF_PORT_INTMASK TSCONF_PORT(0x2A)
|
||||
#define TSCONF_INT_FRAME 0x01
|
||||
#define TSCONF_INT_LINE 0x02
|
||||
#define TSCONF_INT_DMA 0x04
|
||||
#define TSCONF_INT_WTP 0x08
|
||||
|
||||
/* Векторы IM2 (значение на шине данных при прерывании) */
|
||||
#define TSCONF_IVEC_FRAME 0xFF
|
||||
#define TSCONF_IVEC_LINE 0xFD
|
||||
#define TSCONF_IVEC_DMA 0xFB
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* КЭШ — CacheConfig (#2BAF)
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_CACHECONFIG TSCONF_PORT(0x2B)
|
||||
#define TSCONF_CACHE_EN_0000 0x01
|
||||
#define TSCONF_CACHE_EN_4000 0x02
|
||||
#define TSCONF_CACHE_EN_8000 0x04
|
||||
#define TSCONF_CACHE_EN_C000 0x08
|
||||
#define TSCONF_CACHE_ALL 0x0F
|
||||
#define TSCONF_CACHE_NONE 0x00
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* FDD VIRTUAL DRIVE — FDDVirt (#29AF)
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_FDDVIRT TSCONF_PORT(0x29)
|
||||
#define TSCONF_FDD_A 0x01
|
||||
#define TSCONF_FDD_B 0x02
|
||||
#define TSCONF_FDD_C 0x04
|
||||
#define TSCONF_FDD_D 0x08
|
||||
#define TSCONF_FDD_VG_OPEN 0x80
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* ТАЙЛОВЫЕ ПЛОСКОСТИ — смещения
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_T0XOFFSL TSCONF_PORT(0x40)
|
||||
#define TSCONF_PORT_T0XOFFSH TSCONF_PORT(0x41)
|
||||
#define TSCONF_PORT_T0YOFFSL TSCONF_PORT(0x42)
|
||||
#define TSCONF_PORT_T0YOFFSH TSCONF_PORT(0x43)
|
||||
#define TSCONF_PORT_T1XOFFSL TSCONF_PORT(0x44)
|
||||
#define TSCONF_PORT_T1XOFFSH TSCONF_PORT(0x45)
|
||||
#define TSCONF_PORT_T1YOFFSL TSCONF_PORT(0x46)
|
||||
#define TSCONF_PORT_T1YOFFSH TSCONF_PORT(0x47)
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* HUS — Hurricane Sound Engine (#80AF–#87AF)
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_HUSCTRL TSCONF_PORT(0x80) /* W: управление */
|
||||
#define TSCONF_PORT_HUSSAMPRATE TSCONF_PORT(0x81) /* W: SR[7:0] (1750k/(SR+1)) */
|
||||
#define TSCONF_PORT_HUSTICKRATEL TSCONF_PORT(0x82) /* W: TR[7:0] */
|
||||
#define TSCONF_PORT_HUSTICKRATEH TSCONF_PORT(0x83) /* W: TR[9:8] */
|
||||
#define TSCONF_PORT_HUSRLD0 TSCONF_PORT(0x84) /* Reload ch 0–7 */
|
||||
#define TSCONF_PORT_HUSRLD1 TSCONF_PORT(0x85) /* Reload ch 8–15 */
|
||||
#define TSCONF_PORT_HUSRLD2 TSCONF_PORT(0x86)
|
||||
#define TSCONF_PORT_HUSRLD3 TSCONF_PORT(0x87)
|
||||
/* R: HUSStatus (#80AF) */
|
||||
#define TSCONF_HUS_ACT 0x01
|
||||
#define TSCONF_HUS_TICK 0x02 /* сбрасывается при чтении */
|
||||
#define TSCONF_HUS_HALF 0x40 /* буфер наполовину пуст */
|
||||
#define TSCONF_HUS_EMPTY 0x80
|
||||
/* W: HUSControl */
|
||||
#define TSCONF_HUS_PAUSE 0x02
|
||||
#define TSCONF_HUS_RESET 0x80
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* OPL3 (YMF262) — порты на шине TSConfig
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_OPL3_ADDR0 0x00C4
|
||||
#define TSCONF_PORT_OPL3_DATA0 0x00C5
|
||||
#define TSCONF_PORT_OPL3_ADDR1 0x00C6
|
||||
#define TSCONF_PORT_OPL3_DATA1 0x00C7
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* AY-3-8910 / YM2149 — стандартные порты ZX Spectrum 128
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
* #FFFD — выбор регистра (W) / чтение данных (R)
|
||||
* #BFFD — запись данных (W)
|
||||
*
|
||||
* TurboSound (два AY): запись в #FFFD значения #FF/#FE выбирает чип:
|
||||
* #FF → чип 0 (первый AY); #FE → чип 1 (второй AY).
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_AY_ADDR 0xFFFD /* register select / read */
|
||||
#define TSCONF_PORT_AY_DATA 0xBFFD /* data write */
|
||||
#define TSCONF_AY_TS_CHIP0 0xFF /* TurboSound: select chip 0 */
|
||||
#define TSCONF_AY_TS_CHIP1 0xFE /* TurboSound: select chip 1 */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* SAA1099 — порты SAM Coupé / ZX-совместимые
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
* #01FF — выбор регистра (address write)
|
||||
* #00FF — запись данных (data write)
|
||||
* Второй SAA1099 (если есть):
|
||||
* #01FE — выбор регистра
|
||||
* #00FE — запись данных
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_SAA_ADDR 0x01FF /* SAA1099 #1 address */
|
||||
#define TSCONF_PORT_SAA_DATA 0x00FF /* SAA1099 #1 data */
|
||||
#define TSCONF_PORT_SAA2_ADDR 0x01FE /* SAA1099 #2 address */
|
||||
#define TSCONF_PORT_SAA2_DATA 0x00FE /* SAA1099 #2 data */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* WC СИСТЕМНЫЕ ПЕРЕМЕННЫЕ — адреса в ROM-области ядра WC
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define WC_ADDR_PAGES 0x6000 /* [3 байта] страницы #0000,#4000,#8000 */
|
||||
#define WC_ADDR_ABT 0x6004 /* флаг ESC */
|
||||
#define WC_ADDR_ENT 0x6005 /* флаг ENTER */
|
||||
#define WC_ADDR_TMN 0x6009 /* таймер (инкр. по INT), uint16 */
|
||||
#define WC_ADDR_CNFV 0x600D /* версия конфигурации */
|
||||
#define WC_ADDR_HEI 0x600E /* высота экрана (25/30/36 строк) */
|
||||
|
||||
#define WC_CNFV_PWM 0x00
|
||||
#define WC_CNFV_3BIT 0x01
|
||||
#define WC_CNFV_4BIT 0x02
|
||||
#define WC_CNFV_5BIT 0x03
|
||||
#define WC_CNFV_VDAC2 0x07
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* SFR — прямой доступ к портам из SDCC-C
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* __sfr __at 0xNN — 8-bit port: out (N), a / in a, (N)
|
||||
* __sfr __banked __at 0xNNNN — 16-bit port: ld bc,#NNNN; out (c),a
|
||||
*
|
||||
* Не создают переменных/символов — чисто compile-time mapping.
|
||||
* Присваивание sfr_xxx = val; генерирует inline OUT.
|
||||
* Чтение val = sfr_xxx; генерирует inline IN.
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* --- Стандартный ZX Spectrum: 8-bit порт ----------------------------- */
|
||||
__sfr __at 0xFE sfr_zx_fe; /* border / keyboard */
|
||||
|
||||
/* --- TSConfig регистры (addr = reg<<8 | 0xAF) ----------------------- */
|
||||
__sfr __banked __at 0x00AF sfr_vconfig; /* VConfig */
|
||||
__sfr __banked __at 0x01AF sfr_vpage; /* VPage */
|
||||
__sfr __banked __at 0x02AF sfr_gxoffsl; /* GXOffsL */
|
||||
__sfr __banked __at 0x03AF sfr_gxoffsh; /* GXOffsH */
|
||||
__sfr __banked __at 0x04AF sfr_gyoffsl; /* GYOffsL */
|
||||
__sfr __banked __at 0x05AF sfr_gyoffsh; /* GYOffsH */
|
||||
__sfr __banked __at 0x06AF sfr_tsconfig; /* TSConfig */
|
||||
__sfr __banked __at 0x07AF sfr_palsel; /* PalSel */
|
||||
__sfr __banked __at 0x0FAF sfr_border; /* Border Color */
|
||||
__sfr __banked __at 0x10AF sfr_page0; /* Page0 (#0000) */
|
||||
__sfr __banked __at 0x11AF sfr_page1; /* Page1 (#4000) */
|
||||
__sfr __banked __at 0x12AF sfr_page2; /* Page2 (#8000) */
|
||||
__sfr __banked __at 0x13AF sfr_page3; /* Page3 (#C000) */
|
||||
__sfr __banked __at 0x14AF sfr_fmaddr; /* FMAddr */
|
||||
__sfr __banked __at 0x15AF sfr_tmpage; /* TMPage */
|
||||
__sfr __banked __at 0x16AF sfr_t0gpage; /* T0GPage */
|
||||
__sfr __banked __at 0x17AF sfr_t1gpage; /* T1GPage */
|
||||
__sfr __banked __at 0x18AF sfr_sgpage; /* SGPage */
|
||||
__sfr __banked __at 0x20AF sfr_sysconfig; /* SysConfig (CPU) */
|
||||
__sfr __banked __at 0x21AF sfr_memconfig; /* MemConfig */
|
||||
__sfr __banked __at 0x22AF sfr_hsint; /* HSINT */
|
||||
__sfr __banked __at 0x23AF sfr_vsintl; /* VSINTL */
|
||||
__sfr __banked __at 0x24AF sfr_vsinth; /* VSINTH */
|
||||
__sfr __banked __at 0x2AAF sfr_intmask; /* INTMask */
|
||||
__sfr __banked __at 0x2BAF sfr_cacheconfig; /* CacheConfig */
|
||||
__sfr __banked __at 0x29AF sfr_fddvirt; /* FDDVirt */
|
||||
|
||||
/* --- DMA --- */
|
||||
__sfr __banked __at 0x1AAF sfr_dmasaddrh; /* DMA Src Addr H */
|
||||
__sfr __banked __at 0x1BAF sfr_dmasaddrx; /* DMA Src Addr X */
|
||||
__sfr __banked __at 0x19AF sfr_dmasaddrl; /* DMA Src Addr L */
|
||||
__sfr __banked __at 0x1CAF sfr_dmadaddrl; /* DMA Dst Addr L */
|
||||
__sfr __banked __at 0x1DAF sfr_dmadaddrh; /* DMA Dst Addr H */
|
||||
__sfr __banked __at 0x1EAF sfr_dmadaddrx; /* DMA Dst Addr X */
|
||||
__sfr __banked __at 0x26AF sfr_dmalen; /* DMA Burst Length */
|
||||
__sfr __banked __at 0x27AF sfr_dmactrl; /* DMA Ctrl / Status */
|
||||
__sfr __banked __at 0x28AF sfr_dmanum; /* DMA Bursts Num L */
|
||||
__sfr __banked __at 0x2CAF sfr_dmanumh; /* DMA Bursts Num H */
|
||||
|
||||
/* --- OPL3 (YMF262) --- */
|
||||
__sfr __banked __at 0x00C4 sfr_opl3_addr0; /* Bank0 address */
|
||||
__sfr __banked __at 0x00C5 sfr_opl3_data0; /* Bank0 data */
|
||||
__sfr __banked __at 0x00C6 sfr_opl3_addr1; /* Bank1 address */
|
||||
__sfr __banked __at 0x00C7 sfr_opl3_data1; /* Bank1 data */
|
||||
|
||||
/* --- AY-3-8910 / YM2149 --- */
|
||||
__sfr __banked __at 0xFFFD sfr_ay_addr; /* reg select / read */
|
||||
__sfr __banked __at 0xBFFD sfr_ay_data; /* data write */
|
||||
|
||||
/* --- SAA1099 --- */
|
||||
__sfr __banked __at 0x01FF sfr_saa_addr; /* SAA1099 #1 address */
|
||||
__sfr __banked __at 0x00FF sfr_saa_data; /* SAA1099 #1 data */
|
||||
__sfr __banked __at 0x01FE sfr_saa2_addr; /* SAA1099 #2 address */
|
||||
__sfr __banked __at 0x00FE sfr_saa2_data; /* SAA1099 #2 data */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* ФУНКЦИИ — обёртки
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/** Запись значения в произвольный TSConfig-порт (asm, для динамических адресов)
|
||||
* @param port TSCONF_PORT(reg)
|
||||
* @param val значение */
|
||||
extern void tsconf_out(uint16_t port, uint8_t val);
|
||||
|
||||
/* ── Удобные макросы (через __sfr — inline, без call) ────────────────── */
|
||||
|
||||
/** Установить скорость CPU (TSCONF_CPU_3_5MHZ / 7MHZ / 14MHZ) */
|
||||
#define tsconf_set_cpu(speed) \
|
||||
(sfr_sysconfig = (uint8_t)((speed) & TSCONF_CPU_MASK))
|
||||
|
||||
/** Установить скорость CPU с управлением кэшем */
|
||||
#define tsconf_set_cpu_cache(speed, cache_en) \
|
||||
(sfr_sysconfig = (uint8_t)(((speed) & TSCONF_CPU_MASK) | ((cache_en) ? TSCONF_CACHE_EN : 0)))
|
||||
|
||||
/** Установить маску прерываний (OR нужных TSCONF_INT_*) */
|
||||
#define tsconf_set_intmask(mask) \
|
||||
(sfr_intmask = (uint8_t)(mask))
|
||||
|
||||
/** Настроить позицию FRAME-INT: line=0–319, hpos=0–223 */
|
||||
#define tsconf_set_frame_int(line, hpos) do { \
|
||||
sfr_vsinth = (uint8_t)(((line) >> 8) & 0x01); \
|
||||
sfr_vsintl = (uint8_t)((line) & 0xFF); \
|
||||
sfr_hsint = (uint8_t)(hpos); \
|
||||
} while(0)
|
||||
|
||||
/** Выключить FRAME-INT полностью */
|
||||
#define tsconf_disable_frame_int() \
|
||||
(sfr_hsint = TSCONF_HSINT_OFF)
|
||||
|
||||
/** Настроить кэш по окнам: маска из TSCONF_CACHE_EN_* */
|
||||
#define tsconf_set_cache(mask) \
|
||||
(sfr_cacheconfig = (uint8_t)(mask))
|
||||
|
||||
/** Установить цвет бордюра (TSConfig-регистр) */
|
||||
#define tsconf_set_border(color) \
|
||||
(sfr_border = (uint8_t)(color))
|
||||
|
||||
/** Подключить видеостраницу (номер 0–63) на адрес #C000 */
|
||||
#define tsconf_set_vpage(page) \
|
||||
(sfr_vpage = (uint8_t)(page))
|
||||
|
||||
/** Установить видеорежим (OR из TSCONF_VM_*, TSCONF_RRES_*, TSCONF_VC_*) */
|
||||
#define tsconf_set_vconfig(cfg) \
|
||||
(sfr_vconfig = (uint8_t)(cfg))
|
||||
|
||||
/** Подключить RAM-страницу page на окно #C000 (Page3) */
|
||||
#define tsconf_set_page3(page) \
|
||||
(sfr_page3 = (uint8_t)(page))
|
||||
|
||||
/** Подключить RAM-страницу page на окно #8000 (Page2) */
|
||||
#define tsconf_set_page2(page) \
|
||||
(sfr_page2 = (uint8_t)(page))
|
||||
|
||||
#endif /* TSCONF_H */
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* @file txtlib.h
|
||||
* @brief 䇃䇐䈜䈛 для вывода текста в окна WC — динамические буферы и печать строк
|
||||
* @version 1.0
|
||||
*
|
||||
* Зависимости: wc_api.h (wc_prsrw, wc_window_t).
|
||||
* Реализация: lib/txtlib.c.
|
||||
*
|
||||
* Формат буфера:
|
||||
* buf[0] = capacity — максимальная емкость в символах (задаётся ОДИН РАЗ при buf_init)
|
||||
* buf[1] = len — текущая длина (количество символов)
|
||||
* buf[2..] = символы — не завершается \0!
|
||||
*
|
||||
* Объявление буфера:
|
||||
* char work_buf[66]; // capacity = 64 символа
|
||||
*
|
||||
* Типичное использование:
|
||||
* buf_init(work_buf, 64);
|
||||
* buf_clear(work_buf);
|
||||
* buf_append_str(work_buf, "Time: ");
|
||||
* buf_append_u16_dec(work_buf, seconds);
|
||||
* print_line(&win, 5, work_buf, WC_COLOR(WC_BLACK, WC_BRIGHT_WHITE));
|
||||
*/
|
||||
|
||||
#ifndef TXTLIB_H
|
||||
#define TXTLIB_H
|
||||
|
||||
#include "wc_api.h"
|
||||
|
||||
#define FS_SCREEN_COLS 96U /* макс. ширина экрана (90x36, запас) */
|
||||
|
||||
/**
|
||||
* Вернуть (ширина окна - 4) = полезная ширина содержимого.
|
||||
* Если win->width == 0, используется FS_SCREEN_COLS.
|
||||
*/
|
||||
static inline uint8_t get_content_width(const wc_window_t *win) {
|
||||
uint8_t w = win->width;
|
||||
return (w ? w : FS_SCREEN_COLS) - 4U;
|
||||
}
|
||||
|
||||
/* ================================================================== */
|
||||
/* ИНИЦИАЛИЗАЦИЯ И БАЗОВЫЕ ОПЕРАЦИИ */
|
||||
/* ================================================================== */
|
||||
|
||||
/** Инициализировать буфер: записать capacity, len=0. Вызывать ОДИН РАЗ! */
|
||||
static inline void buf_init(char *buf, uint8_t capacity) {
|
||||
buf[0] = capacity;
|
||||
buf[1] = 0;
|
||||
}
|
||||
|
||||
/** Очистить буфер: len=0 (емкость не меняется). */
|
||||
static inline void buf_clear(char *buf) {
|
||||
buf[1] = 0;
|
||||
}
|
||||
|
||||
/** Вернуть текущую длину буфера (количество символов). */
|
||||
static inline uint8_t buf_len(const char *buf) {
|
||||
return buf[1];
|
||||
}
|
||||
|
||||
/* ================================================================== */
|
||||
/* APPEND */
|
||||
/* ================================================================== */
|
||||
|
||||
/**
|
||||
* Добавить строку (\0-terminated) в конец буфера.
|
||||
* Тихо усекается при переполнении (len не превышает capacity).
|
||||
* @return len после добавления
|
||||
*/
|
||||
uint8_t buf_append_str(char *buf, const char *src);
|
||||
|
||||
/**
|
||||
* Добавить один символ c в буфер.
|
||||
* @return len после добавления
|
||||
*/
|
||||
uint8_t buf_append_char(char *buf, char c);
|
||||
|
||||
/**
|
||||
* Добавить двухсимвольное hex-представление байта ("00".."FF").
|
||||
* @param val значение 0..255
|
||||
* @return len после добавления (+2)
|
||||
*/
|
||||
uint8_t buf_append_u8_hex(char *buf, uint8_t val);
|
||||
|
||||
/**
|
||||
* Добавить uint16 в десятичном формате (без ведущих нулей).
|
||||
* @return len после добавления
|
||||
*/
|
||||
uint8_t buf_append_u16_dec(char *buf, uint16_t val);
|
||||
|
||||
/**
|
||||
* Добавить uint32 в десятичном формате.
|
||||
* @return len после добавления
|
||||
*/
|
||||
uint8_t buf_append_u32_dec(char *buf, uint32_t val);
|
||||
|
||||
/**
|
||||
* Добавить "MM:SS" (5 символов) в буфер. Без деления — вычитание.
|
||||
* @param sec секунды (0..5999)
|
||||
*/
|
||||
void buf_append_mmss(char *buf, uint16_t sec);
|
||||
|
||||
|
||||
/* ================================================================== */
|
||||
/* ВЫВОД */
|
||||
/* ================================================================== */
|
||||
|
||||
/**
|
||||
* Вывести одну строку из буфера в окно win заполняя до ширины окна.
|
||||
* Передаётся атрибут цвета attr (EGA: bg[7:4] | fg[3:0]).
|
||||
*
|
||||
* @param win окно WC
|
||||
* @param y строка внутри окна (1-базированное)
|
||||
* @param buf буфер с длиной в buf[1] (формат buf[])
|
||||
* @param attr EGA-атрибут: WC_COLOR(bg, fg)
|
||||
*/
|
||||
void print_line(wc_window_t *win, uint8_t y, const char *buf, uint8_t attr);
|
||||
|
||||
/* ================================================================== */
|
||||
/* ИНИЦИАЛИЗАЦИЯ И ПРИМЕР ИСПОЛЬЗОВАНИЯ */
|
||||
/* ================================================================== */
|
||||
|
||||
/*
|
||||
// В начале плагина (например в init)
|
||||
char meta_buf[122];
|
||||
char work_buf[66]; // 64 символа
|
||||
|
||||
buf_init(meta_buf, 120);
|
||||
buf_init(work_buf, 64); // или больше, если используешь для marquee
|
||||
|
||||
// Заполнение метаданных
|
||||
buf_clear(meta_buf);
|
||||
buf_append_str(meta_buf, "Artist - Super Long Track Name 2025 (Remix)");
|
||||
|
||||
// В цикле отрисовки:
|
||||
uint8_t cw = get_content_width(my_win);
|
||||
|
||||
// Бегущая строка (используем work_buf как временный)
|
||||
build_marquee(work_buf, meta_buf + 2, meta_buf[1], scroll_pos, cw);
|
||||
print_marquee(my_win, 3, work_buf, 0x0F); // белый
|
||||
|
||||
scroll_pos++;
|
||||
|
||||
// Обычная строка (время)
|
||||
buf_clear(work_buf);
|
||||
buf_append_str(work_buf, "Time: ");
|
||||
buf_append_u16_dec(work_buf, minutes);
|
||||
buf_append_char(work_buf, ':');
|
||||
buf_append_u16_dec(work_buf, seconds);
|
||||
print_line(my_win, 5, work_buf, 0x0A);
|
||||
*/
|
||||
|
||||
#endif // TXTLIB_H
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @file types.h
|
||||
* @brief Базовые типы данных для Z80/SDCC (sdcccall(1))
|
||||
* @version 2.0
|
||||
*
|
||||
* Самодостаточный заголовок: не зависит от других файлов проекта.
|
||||
* Подходит для любого Z80-проекта на SDCC.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* SDCC sdcccall(1) CALLING CONVENTION (Z80)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Аргумент | Тип | Регистр
|
||||
* ------------|-------------------|----------
|
||||
* 1-й | uint8_t / int8_t | A
|
||||
* 1-й | uint16_t / ptr | DE
|
||||
* 2-й и далее | любой | стек (правый первым)
|
||||
* Возврат | uint8_t | A (расширяется нулём в L)
|
||||
* Возврат | uint16_t / ptr | HL
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* РЕКОМЕНДАЦИИ ДЛЯ Z80
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* - uint32_t в hot-path -> десятки байт кода; избегать.
|
||||
* - volatile — только для переменных, разделяемых с ISR.
|
||||
* - __sfr / __xdata — НЕ использовать (это 8051, не Z80).
|
||||
* - Маленькие inline-функции быстрее, чем call + стек.
|
||||
* - Массивы до 256 элементов — индексировать uint8_t.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПРИМЕР
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* #include "types.h"
|
||||
*
|
||||
* static volatile uint8_t flag; // ISR -> main
|
||||
* static uint16_t counter;
|
||||
*
|
||||
* void timer_tick(void) {
|
||||
* if (++counter >= 2734) { counter = 0; flag = 1; }
|
||||
* }
|
||||
*/
|
||||
|
||||
#ifndef TYPES_H
|
||||
#define TYPES_H
|
||||
|
||||
/* ── Целочисленные типы фиксированной ширины ─────────────────────── */
|
||||
typedef unsigned char uint8_t;
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned int uint16_t; /* SDCC Z80: int = 16 бит */
|
||||
typedef signed int int16_t;
|
||||
typedef unsigned long uint32_t; /* SDCC Z80: long = 32 бит */
|
||||
typedef signed long int32_t;
|
||||
|
||||
/* ── Булев тип ───────────────────────────────────────────────────── */
|
||||
/* uint8_t = один байт; сравнения JR Z / JR NZ без масок. */
|
||||
typedef uint8_t bool_t;
|
||||
#define TRUE ((bool_t)1)
|
||||
#define FALSE ((bool_t)0)
|
||||
|
||||
/* ── NULL ────────────────────────────────────────────────────────── */
|
||||
#ifndef NULL
|
||||
#define NULL ((void *)0)
|
||||
#endif
|
||||
|
||||
/* ── Короткие псевдонимы ─────────────────────────────────────────── */
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef uint32_t u32;
|
||||
typedef int32_t i32;
|
||||
|
||||
#endif /* TYPES_H */
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file variant_cfg.h
|
||||
* @brief Единственный файл конфигурации варианта сборки.
|
||||
*
|
||||
* build_variants.ps1 перегенерирует этот файл целиком для каждого
|
||||
* варианта. Никакие другие исходники НЕ патчатся.
|
||||
*
|
||||
* build.bat читает VARIANT_POS_ENTRIES / VARIANT_POS_STEP /
|
||||
* VARIANT_DATA_LOC из этого файла (парсинг через for /f).
|
||||
*/
|
||||
#ifndef VARIANT_CFG_H
|
||||
#define VARIANT_CFG_H
|
||||
|
||||
/* ── Частота ISR (тиков/сек) ─────────────────────────────────────── */
|
||||
/* ISR_FREQ = 3 500 000 / VARIANT_POS_STEP */
|
||||
#define ISR_FREQ 683
|
||||
|
||||
/* Тиков ISR на TV-кадр (= 71680 / STEP) */
|
||||
/* 14@683, 28@1367, 56@2734 */
|
||||
#define ISR_TICKS_PER_FRAME 14
|
||||
|
||||
/* ── Маппинг VGM 44100→ISR тиков ─────────────────────────────────── */
|
||||
/* SHIFT = log2(44100/ISR_FREQ): 6@683, 5@1367, 4@2734 */
|
||||
#define VGM_SAMPLE_SHIFT 6
|
||||
#define VGM_SAMPLE_MASK 0x3F
|
||||
|
||||
/* ── Лимит команд между принудительными yield ──────────────────────── */
|
||||
#define VGM_FILL_CMD_BUDGET 16
|
||||
|
||||
/* ── Параметры pos_table (для build.bat → gen_pos_table.js) ──────── */
|
||||
#define VARIANT_POS_ENTRIES 14
|
||||
#define VARIANT_POS_STEP 5120
|
||||
|
||||
/* ── Адрес начала сегмента данных (для линковщика) ────────────────── */
|
||||
/* Плотная раскладка: полная версия spectrum.s + загрузчик OPL4.
|
||||
* Проверяется build.bat: _CODE должен закончиться раньше _DATA,
|
||||
* а последняя секция — раньше 0xC000. */
|
||||
#define VARIANT_DATA_LOC 0xB8D0
|
||||
|
||||
/* ── Спектроанализатор: скорости затухания (в TV-кадрах) ────────── */
|
||||
/* Меньше = быстрее затухание. 1 = каждый кадр (~20 мс). */
|
||||
/* Синхронизировать с EQU в spectrum.s! */
|
||||
#define DECAY_FRAMES_FAST 1 /* уровни >= 6 */
|
||||
#define DECAY_FRAMES_MED 2 /* уровни 3-5 */
|
||||
#define DECAY_FRAMES_SLOW 4 /* уровни 1-2 */
|
||||
|
||||
/* ── Отключение обработки коротких пауз 0x70-0x7F ────────────────── */
|
||||
/* Раскомментировать для «no short waits» варианта: */
|
||||
/* #define VGM_NO_SHORT_WAITS */
|
||||
|
||||
#endif /* VARIANT_CFG_H */
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @file version.h
|
||||
* @brief Версия приложения — редактируйте только этот файл
|
||||
*
|
||||
* Примеры:
|
||||
* #define APP_VERSION "v0.5"
|
||||
* #define APP_VERSION "v0.5-beta"
|
||||
* #define APP_VERSION "v1.0-rc1"
|
||||
*/
|
||||
#ifndef VERSION_H
|
||||
#define VERSION_H
|
||||
|
||||
#define APP_VERSION "v0.9.03-beta"
|
||||
|
||||
#endif /* VERSION_H */
|
||||
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
* @file vgm.h
|
||||
* @brief VGM-парсер для OPL1/OPL2/OPL3 — заголовок + потоковое чтение
|
||||
* @version 3.0
|
||||
*
|
||||
* Зависимости: types.h, isr.h (cmd_buf_a/b), wc_api.h (wc_mngcvpl).
|
||||
* Реализация: lib/vgm.c.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ОПИСАНИЕ
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Преобразует VGM-поток в ISR-команды (CMD_WRITE_B0/B1, CMD_WAIT,
|
||||
* CMD_END_BUF). VGM данные хранятся в VPL-страницах WC, доступных
|
||||
* через окно #C000.
|
||||
*
|
||||
* Поддерживаемые VGM-команды (OPL1 / OPL2 / OPL3 / OPL4 / AY / SAA):
|
||||
* 0x5A rr dd — YM3812 (OPL2) write → CMD_WRITE_B0
|
||||
* 0x5B rr dd — YM3526 (OPL1) write → CMD_WRITE_B0
|
||||
* 0x5C rr dd — Y8950 (OPL) write → CMD_WRITE_B0
|
||||
* 0x5E rr dd — YMF262 (OPL3) Bank 0 write → CMD_WRITE_B0
|
||||
* 0x5F rr dd — YMF262 (OPL3) Bank 1 write → CMD_WRITE_B1
|
||||
* 0xD0 pp aa dd — YMF278B (OPL4) write, port pp (бит7 = chip select, игн.):
|
||||
* pp&0x7F==0 → Bank 0 (как 0x5E) → CMD_WRITE_B0
|
||||
* pp&0x7F==1 → Bank 1 (как 0x5F) → CMD_WRITE_B1
|
||||
* pp&0x7F==2 → Wave-часть (#7E/#7F) → CMD_WRITE_WAVE
|
||||
* 0x55 rr dd — YM2203 PSG-часть (AY), reg 0-15 → CMD_WRITE_AY
|
||||
* 0xA0 rr dd — AY8910 (чип 1/2 по [7]) → CMD_WRITE_AY / CMD_WRITE_AY2
|
||||
* 0xBD rr dd — SAA1099 (чип 1/2 по [7]) → CMD_WRITE_SAA (bit7 = chip select)
|
||||
* 0x61 nn nn — Wait N samples (16-bit LE)
|
||||
* 0x62 — Wait 735 samples (1/60 сек)
|
||||
* 0x63 — Wait 882 samples (1/50 сек)
|
||||
* 0x66 — End of data
|
||||
* 0x67 ... — Data block (пропускается)
|
||||
* 0x70–0x7F — Wait 1–16 samples
|
||||
* Прочие — пропускаются по таблице VGM spec (1-байт / 2-байт / 3-байт аргументы)
|
||||
*
|
||||
* Масштабирование задержек:
|
||||
* VGM sample rate = 44100 Hz, ISR freq = 2734 Hz -> делим на 16.
|
||||
* Задержки < 16 сэмплов игнорируются (округление вниз).
|
||||
* Неизвестные команды пропускаются по таблице размеров VGM spec.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* VGM ЗАГОЛОВОК — ключевые смещения (little-endian)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Offset Size Описание Мин.версия
|
||||
* ------ ---- -------------------------------- ----------
|
||||
* 0x00 4 Сигнатура "Vgm " (0x206D6756) 1.00
|
||||
* 0x08 4 Версия (BCD): 0x0151 = v1.51 1.00
|
||||
* 0x14 4 GD3 offset (отн. 0x14) 1.00
|
||||
* 0x18 4 Total samples 1.00
|
||||
* 0x1C 4 Loop offset (отн. 0x1C) 1.00
|
||||
* 0x20 4 Loop samples 1.00
|
||||
* 0x34 4 Data offset (отн. 0x34) 1.50
|
||||
* 0x50 4 YM3812 (OPL2) clock 1.51
|
||||
* 0x54 4 YM3526 (OPL1) clock 1.51
|
||||
* 0x58 4 Y8950 (OPL) clock 1.51
|
||||
* 0x5C 4 YMF262 (OPL3) clock 1.51
|
||||
*
|
||||
* Для версий < 1.50: data offset фиксирован = 0x40.
|
||||
* Для версий < 1.51: поля чипов за пределами заголовка.
|
||||
*
|
||||
* ~93% OPL VGM файлов — YM3812 (OPL2), команда 0x5A.
|
||||
* ~5% — YMF262 (OPL3), команды 0x5E/0x5F.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПРИМЕР ИСПОЛЬЗОВАНИЯ
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* #include "vgm.h"
|
||||
* #include "isr.h"
|
||||
* #include "wc_api.h"
|
||||
*
|
||||
* // 1. Загрузить VGM в VPL-страницы
|
||||
* wc_mngcvpl(0);
|
||||
* wc_load512(0xC000, 32); // 16 КБ
|
||||
*
|
||||
* // 2. Разобрать заголовок
|
||||
* if (vgm_parse_header() != VGM_OK) return;
|
||||
*
|
||||
* // 3. Заполнить оба буфера ISR
|
||||
* vgm_fill_buffer(0); // буфер A
|
||||
* vgm_fill_buffer(1); // буфер B
|
||||
*
|
||||
* // 4. В main loop:
|
||||
* if (cur_buf != last_buf) {
|
||||
* vgm_fill_buffer(cur_buf ^ 1); // свободный
|
||||
* last_buf = cur_buf;
|
||||
* }
|
||||
* if (vgm_song_ended && !vgm_rewind_to_loop()) break;
|
||||
*/
|
||||
|
||||
#ifndef VGM_H
|
||||
#define VGM_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* ── Масштабирование частот PSG под реальное железо ─────────────────── */
|
||||
#define FREQ_MODE_NATIVE 0 /* HW clock ≈ VGM clock (±2%), без пересчёта */
|
||||
#define FREQ_MODE_TABLE 1 /* LUT table match (±5%) */
|
||||
|
||||
/** Текущий режим масштабирования (FREQ_MODE_xxx) */
|
||||
extern uint8_t vgm_freq_mode;
|
||||
|
||||
/** Базовый адрес LUT таблицы в Window 0 (#0000-#3FFF).
|
||||
* Используется в TABLE mode: freq_lut_base[period] → scaled period.
|
||||
* NULL (0) если режим не TABLE.
|
||||
* PSG: индекс 0..4095 (12-bit).
|
||||
* Таблица в plugin page N, подключённой через wc_mng0_pl(N). */
|
||||
extern uint16_t *freq_lut_base;
|
||||
|
||||
/** Clock (кГц) подобранной LUT таблицы (для отображения в UI).
|
||||
* 0 если режим NATIVE. */
|
||||
extern uint16_t vgm_freq_lut_khz;
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* ID чипов = смещение clock-поля в VGM-заголовке.
|
||||
* Имена определены только для поддерживаемых чипов.
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define VGM_OFF_SN76489 0x0C /* PSG SN76489 / SN76496 */
|
||||
#define VGM_OFF_YM2413 0x10 /* OPLL */
|
||||
#define VGM_OFF_YM2203 0x44 /* OPN (PC-88) */
|
||||
#define VGM_OFF_YM3812 0x50 /* OPL2 (AdLib / SB) */
|
||||
#define VGM_OFF_YM3526 0x54 /* OPL (ISA AdLib) */
|
||||
#define VGM_OFF_Y8950 0x58 /* MSX-AUDIO / OPL */
|
||||
#define VGM_OFF_YMF262 0x5C /* OPL3 (SB Pro 2) */
|
||||
#define VGM_OFF_YMF278B 0x60 /* OPL4 */
|
||||
#define VGM_OFF_AY8910 0x74 /* AY-3-8910 / YM2149 */
|
||||
#define VGM_OFF_SAA1099 0xC8 /* SAA1099 (SAM Coupé) */
|
||||
|
||||
/* ── Тип OPL-чипа (обратная совместимость) ───────────────────────── */
|
||||
#define VGM_CHIP_NONE 0
|
||||
#define VGM_CHIP_OPL 1 /* YM3526 (OPL1) + Y8950: cmd 0x5B/0x5C */
|
||||
#define VGM_CHIP_OPL2 2 /* YM3812 (OPL2): cmd 0x5A */
|
||||
#define VGM_CHIP_OPL3 3 /* YMF262 (OPL3): cmd 0x5E/5F */
|
||||
#define VGM_CHIP_OPL4 4 /* YMF278B (OPL4): cmd 0xD0 (FM порты 0/1 —
|
||||
* те же регистры, что и OPL3; порт 2 —
|
||||
* wave-часть через #7E/#7F, ZXM-MoonSound) */
|
||||
|
||||
/* Приоритет определения типа: YMF278B > YMF262 > YM3812 > YM3526/Y8950
|
||||
* Используется в build_playback_queue() (main.c) для выбора cmdblock init:
|
||||
* VGM_CHIP_OPL4 → CMDBLK_INIT_OPL4 (NEW=1 + NEW2=1, разблокирует wave-часть)
|
||||
* VGM_CHIP_OPL3 → CMDBLK_INIT_OPL3 (NEW=1, L/R через C0-C8)
|
||||
* иначе → CMDBLK_INIT_OPL2 (NEW=0, L+R авто, OPL2-compat) */
|
||||
|
||||
/* ── Коды результата ─────────────────────────────────────────────── */
|
||||
#define VGM_OK 0
|
||||
#define VGM_ERR_HEADER 1 /* Нет сигнатуры "Vgm " или повреждён */
|
||||
#define VGM_ERR_NOOPL 2 /* Нет OPL-чипа (clock = 0 для всех) */
|
||||
|
||||
/* ── Флаги чипа (vgm_chip_entry_t.flags) ─────────────────────────── */
|
||||
#define VGM_CF_DUAL 0x01 /* Bit 30 clock: два экземпляра чипа */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* Структура обнаруженного чипа
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
typedef struct {
|
||||
uint8_t id; /* VGM_OFF_xxx (header offset of clock) */
|
||||
uint8_t flags; /* VGM_CF_DUAL и пр. */
|
||||
uint16_t clock_khz; /* clock / 1000 (0–65535 кГц) */
|
||||
} vgm_chip_entry_t; /* 4 байта */
|
||||
|
||||
#define VGM_MAX_CHIPS 8 /* макс. чипов в vgm_chip_list[] */
|
||||
|
||||
/* ── Состояние парсера (глобальные переменные) ───────────────────── */
|
||||
|
||||
/** 1 = достигнут конец VGM-данных (команда 0x66 или конец файла) */
|
||||
extern volatile uint8_t vgm_song_ended;
|
||||
|
||||
/** 1 = пауза (буферы заполняются тишиной) */
|
||||
extern uint8_t vgm_paused;
|
||||
|
||||
/** Текущая VPL-страница, отображённая на #C000 */
|
||||
extern uint8_t vgm_cur_page;
|
||||
|
||||
/** Указатель чтения VGM (#C000–#FFFF) */
|
||||
extern uint16_t vgm_read_ptr;
|
||||
|
||||
/** Номер последней VPL-страницы (0-based) */
|
||||
extern uint8_t vgm_end_page;
|
||||
|
||||
/** Адрес конца данных в последней странице */
|
||||
extern uint16_t vgm_end_addr;
|
||||
|
||||
/** Версия VGM (BCD): 0x0151 = v1.51, 0x0171 = v1.71 */
|
||||
extern uint16_t vgm_version;
|
||||
|
||||
/** Тип обнаруженного OPL-чипа (VGM_CHIP_xxx) */
|
||||
extern uint8_t vgm_chip_type;
|
||||
|
||||
/** Массив обнаруженных чипов (для вывода информации о файле) */
|
||||
extern uint8_t vgm_chip_count;
|
||||
extern vgm_chip_entry_t vgm_chip_list[VGM_MAX_CHIPS];
|
||||
|
||||
/** Адрес точки петли (#C000–#FFFF); 0 = нет петли */
|
||||
extern uint16_t vgm_loop_addr;
|
||||
|
||||
/** VPL-страница точки петли */
|
||||
extern uint8_t vgm_loop_page;
|
||||
|
||||
/** Общая длительность воспроизведения (секунды), с учётом loop rewinds */
|
||||
extern uint16_t vgm_total_seconds;
|
||||
|
||||
/** Флаг: использовать ли loop при воспроизведении.
|
||||
* Устанавливается в vgm_parse_header() с учётом loop policy. */
|
||||
extern uint8_t vgm_loop_enabled;
|
||||
|
||||
/* ── Loop/duration policy (читаются из INI, устанавливаются main.c) ── */
|
||||
extern uint8_t cfg_loop_rewinds; /* кол-во повторов петли (default 1) */
|
||||
extern uint8_t cfg_min_duration; /* ниже этой длительности (сек) петля запрещена (default 10) */
|
||||
extern uint16_t cfg_max_duration; /* выше этой длительности (сек) петля запрещена (default 240) */
|
||||
|
||||
/* ── GD3 метаданные (только English, ASCII) ─────────────────────── */
|
||||
#define VGM_GD3_LEN 48 /* макс. длина одного поля (+1 для '\0') */
|
||||
|
||||
extern char vgm_gd3_track[VGM_GD3_LEN]; /* Название трека (EN) */
|
||||
extern char vgm_gd3_game[VGM_GD3_LEN]; /* Название игры (EN) */
|
||||
extern char vgm_gd3_system[VGM_GD3_LEN]; /* Название системы (EN) */
|
||||
extern char vgm_gd3_author[VGM_GD3_LEN]; /* Автор (EN) */
|
||||
|
||||
/* ── Функции ─────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Разобрать заголовок VGM из #C000 (страница 0 должна быть загружена).
|
||||
* Заполняет vgm_read_ptr, vgm_cur_page, vgm_loop_addr/page,
|
||||
* vgm_version, vgm_chip_type, vgm_chip_list[]/vgm_chip_count.
|
||||
* @return VGM_OK (0) или VGM_ERR_HEADER (1)
|
||||
*/
|
||||
uint8_t vgm_parse_header(void);
|
||||
|
||||
/**
|
||||
* Заполнить командный буфер buf_idx (0 = A, 1 = B).
|
||||
*
|
||||
* Поддерживаемые VGM-команды:
|
||||
* 0x5A/0x5B/0x5C/0x5E → CMD_WRITE_B0 (OPL1/OPL2/Y8950/OPL3 bank 0)
|
||||
* 0x5F → CMD_WRITE_B1 (OPL3 bank 1)
|
||||
* 0xD0 (port 0/1/2) → CMD_WRITE_B0 / CMD_WRITE_B1 / CMD_WRITE_WAVE (YMF278B/OPL4)
|
||||
* 0x55 → CMD_WRITE_AY (YM2203 PSG-часть, reg 0-15)
|
||||
* 0xA0 → CMD_WRITE_AY / CMD_WRITE_AY2 (AY8910, dual)
|
||||
* 0xBD → CMD_WRITE_SAA (SAA1099, bit7 = chip select)
|
||||
* 0x61/0x62/0x63/0x70-0x7F → CMD_WAIT
|
||||
* 0x66 → end; 0x67 → data block skip
|
||||
*
|
||||
* Гарантирует CMD_END_BUF в конце буфера.
|
||||
* @param buf_idx 0 = cmd_buf_a, 1 = cmd_buf_b
|
||||
*/
|
||||
void vgm_fill_buffer(uint8_t buf_idx);
|
||||
|
||||
/**
|
||||
* Перемотать VGM на точку петли (если установлена).
|
||||
* Сбрасывает vgm_song_ended = 0 при успехе.
|
||||
* @return 1 = петля есть (перемотано), 0 = нет петли
|
||||
*/
|
||||
uint8_t vgm_rewind_to_loop(void);
|
||||
|
||||
/**
|
||||
* Разобрать GD3-тег (метаданные) из VGM файла.
|
||||
* Вызывать после vgm_parse_header().
|
||||
* Заполняет vgm_gd3_track/game/system/author (ASCII, EN only).
|
||||
* Переключает VPL-страницу — после вызова нужно восстановить
|
||||
* vgm_cur_page через wc_mngcvpl().
|
||||
*/
|
||||
void vgm_parse_gd3(void);
|
||||
|
||||
/**
|
||||
* Получить имя чипа по header offset (VGM_OFF_xxx).
|
||||
* @return указатель на строку (ROM), например "YM3812", "AY8910".
|
||||
* Для неизвестных чипов возвращает NULL.
|
||||
*/
|
||||
const char *vgm_chip_name(uint8_t id);
|
||||
|
||||
/* ─── VGM-format command blocks (cmdblocks page) ────────────────── */
|
||||
|
||||
/** Plugin page содержащая VGM-format command blocks */
|
||||
#define CMDBLK_PAGE 5
|
||||
|
||||
/** Индексы блоков в таблице указателей по адресу 0xC000 */
|
||||
#define CMDBLK_INIT_OPL3 0 /* OPL3 init: NEW=1, 4-op=0, test=0 */
|
||||
#define CMDBLK_INIT_OPL2 1 /* OPL2 compat: NEW=0, 4-op=0, test=0 */
|
||||
#define CMDBLK_SILENCE_OPL 2 /* OPL silence: KeyOff+TL+waveform+BD */
|
||||
#define CMDBLK_SILENCE_AY 3 /* AY chip 1: mixer off, vol=0 */
|
||||
#define CMDBLK_SILENCE_AY2 4 /* AY chip 2 (TS): mixer off, vol=0 */
|
||||
#define CMDBLK_SILENCE_SAA 5 /* SAA1099 chip 1: reset + amp=0 */
|
||||
#define CMDBLK_SILENCE_SAA2 6 /* SAA1099 chip 2: reset + amp=0 */
|
||||
#define CMDBLK_SAA_CLK_ON 7 /* MultiSound: SAA clock enable */
|
||||
#define CMDBLK_SAA_CLK_OFF 8 /* MultiSound: SAA clock disable */
|
||||
#define CMDBLK_SILENCE_YM2203 9 /* YM2203 chip 1: SSG+FM silence */
|
||||
#define CMDBLK_SILENCE_YM2203_2 10 /* YM2203 chip 2 (TS): SSG+FM silence */
|
||||
#define CMDBLK_SAA_INIT 11 /* SAA1099 chip 1: Sound Enable ON */
|
||||
#define CMDBLK_SAA2_INIT 12 /* SAA1099 chip 2: Sound Enable ON+clk */
|
||||
#define CMDBLK_INIT_OPL4 13 /* OPL4 init: NEW=1 + NEW2=1 (wave unlock) */
|
||||
#define CMDBLK_SILENCE_WAVE 14 /* OPL4 wave-часть silence: 24 канала KeyOff+Damp+TL max */
|
||||
|
||||
/* ─── Очередь высокоуровневых команд (HL queue) ───────────────── */
|
||||
|
||||
/** Типы HL-команд */
|
||||
#define HLCMD_CMDBLK 1 /* Выполнить VGM-format cmdblock (param = CMDBLK_xxx) */
|
||||
#define HLCMD_PLAY 2 /* Воспроизведение VGM с текущей позиции до 0x66 */
|
||||
#define HLCMD_LOOP 3 /* Перемотка к loop-точке, затем play (морфируется→PLAY) */
|
||||
#define HLCMD_ISR_DONE 4 /* Записать CMD_ISR_DONE в буфер, ISR замирает */
|
||||
|
||||
#define HL_QUEUE_MAX 16
|
||||
|
||||
typedef struct { uint8_t cmd, param; } hl_entry_t;
|
||||
|
||||
/** Глобальные переменные HL queue — заполняются в main.c (build_playback_queue),
|
||||
* используются внутри vgm_fill_buffer(). */
|
||||
extern hl_entry_t vgm_hl_queue[HL_QUEUE_MAX];
|
||||
extern uint8_t vgm_hl_len; /* всего записей */
|
||||
extern uint8_t vgm_hl_pos; /* текущая запись */
|
||||
extern uint8_t vgm_hl_abort_pos; /* переход при abort пользователя */
|
||||
extern uint8_t vgm_loop_count; /* инкрементируется при каждом LOOP */
|
||||
|
||||
/* ─── VGZ (gzip-сжатый VGM) ─────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Копировать n_pages страниц из мегабуфера (#20+) в TAP (#A1+).
|
||||
* DI на время работы, восстанавливает Win 0 / Win 3 и EI при выходе.
|
||||
*
|
||||
* @param n_pages количество страниц (макс. 31 — размер TAP области)
|
||||
*/
|
||||
void copy_pages_to_tap(uint8_t n_pages);
|
||||
|
||||
/**
|
||||
* Распаковать VGZ из TAP-страниц в мегабуфер.
|
||||
* Вызывается из C — ассемблерная обёртка (inflate_call.s).
|
||||
*
|
||||
* @param src_page первая физ. страница источника (TAP, напр. 0xA1)
|
||||
* @param dst_page первая физ. страница назначения (мегабуфер, напр. 0x20)
|
||||
* @return 0 — успех, 1 — ошибка (неверные данные / не gzip)
|
||||
*/
|
||||
uint8_t inflate_vgz(uint8_t src_page, uint8_t dst_page);
|
||||
|
||||
#endif /* VGM_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* @file keys.c
|
||||
* @brief PS/2 клавиатура через TSConfig CMOS FIFO (порты #EFF7/#DFF7/#BFF7)
|
||||
* @see inc/keys.h
|
||||
*
|
||||
* Аппаратный PS/2 FIFO буфер в FPGA TSConfig, регистр CMOS #F0.
|
||||
*
|
||||
* ps2_poll() за один вызов полностью сливает FIFO (tight loop).
|
||||
* Если среди прочитанных байтов есть make-код нашей клавиши — возвращает
|
||||
* последний найденный KEY_*. Стая байтов никогда не копится.
|
||||
*
|
||||
* Единственный бит состояния — skip flag (после F0 нужно пропустить 1 байт).
|
||||
* E0 (extended prefix) не требует skip: сам E0 не совпадает с нашими кодами,
|
||||
* а release extended (E0 F0 SC) корректно обрабатывается через F0→skip.
|
||||
*
|
||||
* Опрос ~1 раз в TV-кадр (rate-limited по isr_tick_ctr).
|
||||
* ps2_poll полностью на asm.
|
||||
*/
|
||||
|
||||
#include "../inc/types.h"
|
||||
#include "../inc/keys.h"
|
||||
#include "../inc/isr.h" /* isr_tick_ctr (volatile uint8_t) */
|
||||
#include "../inc/variant_cfg.h" /* ISR_TICKS_PER_FRAME */
|
||||
|
||||
/* Единственная FSM-переменная: skip flag (0 или 1). */
|
||||
static uint8_t ps2_state; /* 0 = normal, 1 = skip next byte */
|
||||
static uint8_t ps2_last_tick;
|
||||
|
||||
/* ── ps2_init: активация PS/2 FIFO + одноразовый слив ────────────── */
|
||||
void ps2_init(void)
|
||||
{
|
||||
__asm
|
||||
; Разрешить PS/2 порты (бит 7 #EFF7)
|
||||
ld bc, #0xEFF7
|
||||
ld a, #0x80
|
||||
out (c), a
|
||||
; Выбрать CMOS reg #F0, записать 2 (активация), re-select #F0
|
||||
ld bc, #0xDFF7
|
||||
ld a, #0xF0
|
||||
out (c), a
|
||||
ld bc, #0xBFF7
|
||||
ld a, #0x02
|
||||
out (c), a
|
||||
ld bc, #0xDFF7
|
||||
ld a, #0xF0
|
||||
out (c), a
|
||||
; Слив FIFO (tight loop, макс 256 итераций)
|
||||
ld bc, #0xBFF7
|
||||
ld e, #0
|
||||
_pi_drain:
|
||||
in a, (c)
|
||||
or a, a
|
||||
jr z, _pi_done ; 0x00 = пусто
|
||||
inc a
|
||||
jr z, _pi_ovf ; 0xFF = overflow
|
||||
dec e
|
||||
jr nz, _pi_drain
|
||||
jr _pi_done
|
||||
_pi_ovf:
|
||||
ld bc, #0xDFF7
|
||||
ld a, #0x0C
|
||||
out (c), a
|
||||
ld bc, #0xBFF7
|
||||
ld a, #0x01
|
||||
out (c), a
|
||||
ld bc, #0xDFF7
|
||||
ld a, #0xF0
|
||||
out (c), a
|
||||
_pi_done:
|
||||
__endasm;
|
||||
ps2_state = 0;
|
||||
ps2_last_tick = isr_tick_ctr;
|
||||
}
|
||||
|
||||
/* ── ps2_deinit: слив FIFO перед выходом ──────────────────────────── */
|
||||
void ps2_deinit(void)
|
||||
{
|
||||
__asm
|
||||
ld bc, #0xDFF7
|
||||
ld a, #0xF0
|
||||
out (c), a
|
||||
ld bc, #0xBFF7
|
||||
ld e, #0
|
||||
_pd_loop:
|
||||
in a, (c)
|
||||
or a, a
|
||||
jr z, _pd_done ; 0x00 = пусто
|
||||
inc a
|
||||
jr z, _pd_ovf ; 0xFF = overflow
|
||||
dec e
|
||||
jr nz, _pd_loop
|
||||
jr _pd_done
|
||||
_pd_ovf:
|
||||
ld bc, #0xDFF7
|
||||
ld a, #0x0C
|
||||
out (c), a
|
||||
ld bc, #0xBFF7
|
||||
ld a, #0x01
|
||||
out (c), a
|
||||
ld bc, #0xDFF7
|
||||
ld a, #0xF0
|
||||
out (c), a
|
||||
_pd_done:
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* ── ps2_poll: tight-drain FIFO, rate-limited, полностью asm ──────── */
|
||||
uint8_t ps2_poll(void) __naked
|
||||
{
|
||||
__asm
|
||||
|
||||
;; ── Rate limit: ~1 TV-кадр ────────────────────────
|
||||
ld a, (_isr_tick_ctr)
|
||||
ld hl, #_ps2_last_tick
|
||||
ld e, a
|
||||
sub a, (hl)
|
||||
cp a, #ISR_TICKS_PER_FRAME
|
||||
jp c, _pp_none
|
||||
ld (hl), e
|
||||
|
||||
;; ── Подготовка: выбрать FIFO, init регистров ──────
|
||||
ld bc, #0xDFF7
|
||||
ld a, #0xF0
|
||||
out (c), a
|
||||
ld bc, #0xBFF7 ; BC = порт FIFO на весь цикл
|
||||
ld a, (_ps2_state)
|
||||
ld d, a ; D = skip flag (от пред. вызова)
|
||||
ld e, #0 ; E = результат (KEY_NONE)
|
||||
|
||||
;; ── Цикл: читаем FIFO до пустого ─────────────────
|
||||
_pp_loop:
|
||||
in a, (c) ; BC = #BFF7
|
||||
or a, a
|
||||
jr z, _pp_done ; 0x00 = пусто → выход
|
||||
inc a
|
||||
jr z, _pp_ovf ; 0xFF = overflow
|
||||
dec a ; восстановить байт
|
||||
|
||||
bit 0, d
|
||||
jr nz, _pp_clr_skip ; skip-флаг → пропустить байт
|
||||
|
||||
cp a, #0xF0
|
||||
jr z, _pp_set_skip ; F0 → пропустить след. байт
|
||||
|
||||
;; ── Трансляция scan code → KEY_* ──────────────────
|
||||
cp a, #0x76 ; ESC
|
||||
jr z, _pp_k_esc
|
||||
cp a, #0x29 ; SPACE
|
||||
jr z, _pp_k_next
|
||||
cp a, #0x4D ; P
|
||||
jr z, _pp_k_prev
|
||||
cp a, #0x16 ; 1
|
||||
jr z, _pp_k_saa1
|
||||
cp a, #0x1E ; 2
|
||||
jr z, _pp_k_saa2
|
||||
jr _pp_loop ; неизвестная → продолжаем слив
|
||||
|
||||
_pp_k_esc: ld e, #KEY_ESC
|
||||
jr _pp_loop
|
||||
_pp_k_next: ld e, #KEY_NEXT
|
||||
jr _pp_loop
|
||||
_pp_k_prev: ld e, #KEY_PREV
|
||||
jr _pp_loop
|
||||
_pp_k_saa1: ld e, #KEY_SAA1
|
||||
jr _pp_loop
|
||||
_pp_k_saa2: ld e, #KEY_SAA2
|
||||
jr _pp_loop
|
||||
|
||||
_pp_set_skip:
|
||||
ld d, #1
|
||||
jr _pp_loop
|
||||
|
||||
_pp_clr_skip:
|
||||
ld d, #0
|
||||
jr _pp_loop
|
||||
|
||||
;; ── Overflow: сброс FIFO ─────────────────────────
|
||||
_pp_ovf:
|
||||
ld bc, #0xDFF7
|
||||
ld a, #0x0C
|
||||
out (c), a
|
||||
ld bc, #0xBFF7
|
||||
ld a, #0x01
|
||||
out (c), a
|
||||
ld bc, #0xDFF7
|
||||
ld a, #0xF0
|
||||
out (c), a
|
||||
ld d, #0 ; сбросить skip
|
||||
;; fall-through
|
||||
|
||||
;; ── Готово: сохранить skip, вернуть KEY ───────────
|
||||
_pp_done:
|
||||
ld a, d
|
||||
ld (_ps2_state), a ; skip flag для след. вызова
|
||||
ld a, e ; A = KEY_* (sdcccall(1))
|
||||
ret
|
||||
|
||||
_pp_none:
|
||||
xor a, a ; KEY_NONE = 0
|
||||
ret
|
||||
|
||||
__endasm;
|
||||
}
|
||||
+2017
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
@echo off
|
||||
REM ===========================================================================
|
||||
REM build_lib.bat — Сборка статической библиотеки wc_api.lib
|
||||
REM
|
||||
REM Каждая функция WC API — отдельный .rel модуль.
|
||||
REM Линковщик SDCC подтягивает из .lib только модули с нужными символами.
|
||||
REM ===========================================================================
|
||||
|
||||
setlocal
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
set ASFLAGS=-plosff
|
||||
set BUILDDIR=..\..\build\wc_api
|
||||
set LIBFILE=..\..\build\wc_api.lib
|
||||
|
||||
REM --- Папка для .rel файлов ---
|
||||
if not exist "%BUILDDIR%" mkdir "%BUILDDIR%"
|
||||
|
||||
REM --- Сборка всех .s → .rel ---
|
||||
for %%f in (*.s) do (
|
||||
sdasz80 %ASFLAGS% "%BUILDDIR%\%%~nf.rel" "%%f" >nul
|
||||
if errorlevel 1 ( echo FAIL %%f && exit /b 1 )
|
||||
)
|
||||
|
||||
REM --- Удалить старую библиотеку ---
|
||||
if exist "%LIBFILE%" del "%LIBFILE%"
|
||||
|
||||
REM --- Создать .lib архив (Windows cmd не раскрывает *.rel для sdar) ---
|
||||
setlocal enabledelayedexpansion
|
||||
set RELFILES=
|
||||
for %%f in ("%BUILDDIR%\*.rel") do set RELFILES=!RELFILES! "%%f"
|
||||
sdar -rc "%LIBFILE%" %RELFILES% >nul
|
||||
if errorlevel 1 ( echo FAIL sdar && exit /b 1 )
|
||||
endlocal
|
||||
|
||||
echo wc_api.lib OK
|
||||
exit /b 0
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_adir
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_adir
|
||||
_wc_adir::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x38
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,13 @@
|
||||
.module wc_chtosep
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_chtosep
|
||||
_wc_chtosep::
|
||||
push ix
|
||||
ld b, d
|
||||
ld c, e ; BC = bufend (from DE)
|
||||
ex de, hl ; DE = buf (from HL)
|
||||
ld a, #0x34
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,12 @@
|
||||
.module wc_curser
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_curser
|
||||
_wc_curser::
|
||||
push ix
|
||||
push hl
|
||||
pop ix
|
||||
ld a, #0x07
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,12 @@
|
||||
.module wc_cursor
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_cursor
|
||||
_wc_cursor::
|
||||
push ix
|
||||
push hl
|
||||
pop ix
|
||||
ld a, #0x06
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_delfl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_delfl
|
||||
_wc_delfl::
|
||||
push ix
|
||||
ld a, #0x4B
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_dmapl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_dmapl
|
||||
_wc_dmapl::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x0D
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,15 @@
|
||||
.module wc_fentry
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_fentry
|
||||
_wc_fentry::
|
||||
push ix
|
||||
; HL = name (sdcccall(1); WC нужен HL — уже на месте!)
|
||||
ld a, #0x3B
|
||||
call WC_ENTRY
|
||||
ld a, #0
|
||||
jr z, _wc_fentry_end
|
||||
inc a
|
||||
_wc_fentry_end:
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,22 @@
|
||||
.module wc_findnext
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_findnext
|
||||
_wc_findnext::
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
ld a, 4(ix) ; A = flags (from stack)
|
||||
ex af, af' ; A' = flags
|
||||
ex de, hl ; DE = entry_buf (SDCC HL → WC DE)
|
||||
ld a, #0x3A
|
||||
call WC_ENTRY
|
||||
ld a, #0
|
||||
jr z, _wc_findnext_end
|
||||
inc a
|
||||
_wc_findnext_end:
|
||||
pop ix
|
||||
; callee cleans 1 byte (flags)
|
||||
pop hl
|
||||
inc sp
|
||||
jp (hl)
|
||||
@@ -0,0 +1,25 @@
|
||||
.module wc_gadrw
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gadrw
|
||||
_wc_gadrw::
|
||||
push ix
|
||||
push hl ; save win (HL = 1st arg)
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
ld d, 6(ix) ; D = y
|
||||
ld e, 7(ix) ; E = x
|
||||
ld l, 0(ix)
|
||||
ld h, 1(ix) ; HL = win
|
||||
push hl
|
||||
pop ix ; IX = win
|
||||
ld a, #0x05
|
||||
call WC_ENTRY ; HL = result addr
|
||||
ex de, hl ; DE = result (sdcccall(1) uint16)
|
||||
pop hl ; remove saved win (use HL, keep DE)
|
||||
pop ix ; restore caller IX
|
||||
; callee cleans 2 bytes (y+x); preserve DE = return value
|
||||
pop hl ; HL = return address
|
||||
inc sp
|
||||
inc sp ; discard y(1)+x(1)
|
||||
jp (hl) ; return, DE = result
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_gdir
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gdir
|
||||
_wc_gdir::
|
||||
push ix
|
||||
ld a, #0x3F
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_gedpl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gedpl
|
||||
_wc_gedpl::
|
||||
push ix
|
||||
ld a, #0x0F
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_gfile
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gfile
|
||||
_wc_gfile::
|
||||
push ix
|
||||
ld a, #0x3E
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_gipagpl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gipagpl
|
||||
_wc_gipagpl::
|
||||
push ix
|
||||
ld a, #0x32
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_gvmod
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gvmod
|
||||
_wc_gvmod::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x42
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_gvsgp
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gvsgp
|
||||
_wc_gvsgp::
|
||||
push ix
|
||||
ld c, a
|
||||
ld a, #0x47
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,17 @@
|
||||
.module wc_gvtl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gvtl
|
||||
_wc_gvtl::
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
ld c, 4(ix) ; C = page
|
||||
ld b, a ; B = plane
|
||||
ld a, #0x46
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
; callee cleans 1 byte (page)
|
||||
pop hl
|
||||
inc sp
|
||||
jp (hl)
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_gvtm
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gvtm
|
||||
_wc_gvtm::
|
||||
push ix
|
||||
ld c, a
|
||||
ld a, #0x45
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_gxoff
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gxoff
|
||||
_wc_gxoff::
|
||||
push ix
|
||||
ld a, #0x44
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_gyoff
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_gyoff
|
||||
_wc_gyoff::
|
||||
push ix
|
||||
ld a, #0x43
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_int_pl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_int_pl
|
||||
_wc_int_pl::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x56
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,13 @@
|
||||
.module wc_int_pl_handler
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_int_pl_handler
|
||||
_wc_int_pl_handler::
|
||||
push ix
|
||||
; HL = handler_addr (sdcccall(1); WC тоже нужен HL)
|
||||
ld a, #0xFF
|
||||
ex af, af' ; A' = 0xFF
|
||||
ld a, #0x56
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,19 @@
|
||||
.module wc_istr
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_istr
|
||||
_wc_istr::
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
ld a, 4(ix) ; A = mode (from stack)
|
||||
ex af, af' ; A' = mode
|
||||
push hl
|
||||
pop ix ; IX = win (HL = 1st arg)
|
||||
ld a, #0x09
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
; callee cleans 1 byte (mode)
|
||||
pop hl
|
||||
inc sp
|
||||
jp (hl)
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_kbscn
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_kbscn
|
||||
_wc_kbscn::
|
||||
ex af, af' ; A' = mode
|
||||
push ix
|
||||
ld a, #0x2A
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_alt
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_alt
|
||||
_wc_key_alt::
|
||||
push ix
|
||||
ld a, #0x27
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_any
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_any
|
||||
_wc_key_any::
|
||||
push ix
|
||||
ld a, #0x2D
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_bspc
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_bspc
|
||||
_wc_key_bspc::
|
||||
push ix
|
||||
ld a, #0x18
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_caps
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_caps
|
||||
_wc_key_caps::
|
||||
push ix
|
||||
ld a, #0x2C
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_ctrl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_ctrl
|
||||
_wc_key_ctrl::
|
||||
push ix
|
||||
ld a, #0x29
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_del
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_del
|
||||
_wc_key_del::
|
||||
push ix
|
||||
ld a, #0x2B
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_down
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_down
|
||||
_wc_key_down::
|
||||
push ix
|
||||
ld a, #0x12
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_end
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_end
|
||||
_wc_key_end::
|
||||
push ix
|
||||
ld a, #0x1C
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_enter
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_enter
|
||||
_wc_key_enter::
|
||||
push ix
|
||||
ld a, #0x16
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_esc
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_esc
|
||||
_wc_key_esc::
|
||||
push ix
|
||||
ld a, #0x17
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_f1
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_f1
|
||||
_wc_key_f1::
|
||||
push ix
|
||||
ld a, #0x1D
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_f10
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_f10
|
||||
_wc_key_f10::
|
||||
push ix
|
||||
ld a, #0x26
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_f2
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_f2
|
||||
_wc_key_f2::
|
||||
push ix
|
||||
ld a, #0x1E
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_f3
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_f3
|
||||
_wc_key_f3::
|
||||
push ix
|
||||
ld a, #0x1F
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_f4
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_f4
|
||||
_wc_key_f4::
|
||||
push ix
|
||||
ld a, #0x20
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_f5
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_f5
|
||||
_wc_key_f5::
|
||||
push ix
|
||||
ld a, #0x21
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_f6
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_f6
|
||||
_wc_key_f6::
|
||||
push ix
|
||||
ld a, #0x22
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_f7
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_f7
|
||||
_wc_key_f7::
|
||||
push ix
|
||||
ld a, #0x23
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_f8
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_f8
|
||||
_wc_key_f8::
|
||||
push ix
|
||||
ld a, #0x24
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_f9
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_f9
|
||||
_wc_key_f9::
|
||||
push ix
|
||||
ld a, #0x25
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_home
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_home
|
||||
_wc_key_home::
|
||||
push ix
|
||||
ld a, #0x1B
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_ins
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_ins
|
||||
_wc_key_ins::
|
||||
push ix
|
||||
ld a, #0x55
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_left
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_left
|
||||
_wc_key_left::
|
||||
push ix
|
||||
ld a, #0x13
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_pgdn
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_pgdn
|
||||
_wc_key_pgdn::
|
||||
push ix
|
||||
ld a, #0x1A
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_pgup
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_pgup
|
||||
_wc_key_pgup::
|
||||
push ix
|
||||
ld a, #0x19
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_right
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_right
|
||||
_wc_key_right::
|
||||
push ix
|
||||
ld a, #0x14
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_shift
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_shift
|
||||
_wc_key_shift::
|
||||
push ix
|
||||
ld a, #0x28
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_space
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_space
|
||||
_wc_key_space::
|
||||
push ix
|
||||
ld a, #0x10
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_tab
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_tab
|
||||
_wc_key_tab::
|
||||
push ix
|
||||
ld a, #0x15
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_up
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_up
|
||||
_wc_key_up::
|
||||
push ix
|
||||
ld a, #0x11
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_wait_any
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_wait_any
|
||||
_wc_key_wait_any::
|
||||
push ix
|
||||
ld a, #0x2F
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_key_wait_release
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_key_wait_release
|
||||
_wc_key_wait_release::
|
||||
push ix
|
||||
ld a, #0x2E
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,16 @@
|
||||
.module wc_load256
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_load256
|
||||
_wc_load256::
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
ld b, 4(ix) ; B = blocks (1 byte on stack)
|
||||
; HL = dest (already in HL)
|
||||
ld a, #0x3C
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
pop hl ; HL = return address
|
||||
inc sp ; discard 1-byte 'blocks' arg
|
||||
jp (hl) ; return (callee cleans stack arg)
|
||||
@@ -0,0 +1,16 @@
|
||||
.module wc_load512
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_load512
|
||||
_wc_load512::
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
ld b, 4(ix) ; B = blocks (1 byte on stack)
|
||||
; HL = dest (sdcccall(1) first uint16 → HL)
|
||||
ld a, #0x30
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
pop hl ; HL = return address
|
||||
inc sp ; discard 1-byte 'blocks' arg
|
||||
jp (hl) ; return (callee cleans stack arg)
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_loadnone
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_loadnone
|
||||
_wc_loadnone::
|
||||
push ix
|
||||
ld b, a
|
||||
ld a, #0x3D
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,34 @@
|
||||
.module wc_mezz
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_mezz
|
||||
_wc_mezz::
|
||||
push ix
|
||||
push hl ; save win (HL = 1st arg)
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
ld a, 6(ix) ; A = msg_num
|
||||
ld l, 7(ix)
|
||||
ld h, 8(ix) ; HL = str
|
||||
ld d, 9(ix) ; D = y
|
||||
ld e, 10(ix) ; E = x
|
||||
exx
|
||||
ex af, af' ; A' = msg_num
|
||||
ld l, 0(ix)
|
||||
ld h, 1(ix) ; HL = win
|
||||
push hl
|
||||
pop ix ; IX = win
|
||||
exx ; HL=str, D=y, E=x
|
||||
ld a, #0x0C
|
||||
call WC_ENTRY
|
||||
ld a, d ; return: next row
|
||||
pop hl ; remove saved win
|
||||
pop ix
|
||||
; callee cleans 5 bytes
|
||||
pop hl ; HL = return address
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
jp (hl)
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_mkdir
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_mkdir
|
||||
_wc_mkdir::
|
||||
push ix
|
||||
ld a, #0x49
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,10 @@
|
||||
.module wc_mkfile
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_mkfile
|
||||
_wc_mkfile::
|
||||
push ix
|
||||
ld a, #0x48
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_mng0_pl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_mng0_pl
|
||||
_wc_mng0_pl::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x4E
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_mng0vpl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_mng0vpl
|
||||
_wc_mng0vpl::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x50
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_mng8_pl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_mng8_pl
|
||||
_wc_mng8_pl::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x4F
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_mng8vpl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_mng8vpl
|
||||
_wc_mng8vpl::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x51
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_mngc_pl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_mngc_pl
|
||||
_wc_mngc_pl::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x00
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_mngcvpl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_mngcvpl
|
||||
_wc_mngcvpl::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x41
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,11 @@
|
||||
.module wc_mngv_pl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_mngv_pl
|
||||
_wc_mngv_pl::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x40
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,18 @@
|
||||
.module wc_nork
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_nork
|
||||
_wc_nork::
|
||||
push ix
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
ld a, 4(ix) ; A = val (from stack)
|
||||
ex af, af' ; A' = val
|
||||
; HL = addr (1st arg, already in HL)
|
||||
ld a, #0x0A
|
||||
call WC_ENTRY
|
||||
pop ix
|
||||
; callee cleans 1 byte (val)
|
||||
pop hl
|
||||
inc sp
|
||||
jp (hl)
|
||||
@@ -0,0 +1,15 @@
|
||||
.module wc_prm_pl
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_prm_pl
|
||||
_wc_prm_pl::
|
||||
ex af, af'
|
||||
push ix
|
||||
ld a, #0x53
|
||||
call WC_ENTRY
|
||||
; Z=нет параметра → вернуть 0xFF, иначе A=номер опции
|
||||
jr nz, _wc_prm_pl_end
|
||||
ld a, #0xFF
|
||||
_wc_prm_pl_end:
|
||||
pop ix
|
||||
ret
|
||||
@@ -0,0 +1,34 @@
|
||||
.module wc_prsrw
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_prsrw
|
||||
_wc_prsrw::
|
||||
push ix
|
||||
push hl ; save win (HL = 1st arg)
|
||||
push de ; save str (DE = 2nd arg)
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
ld b, 11(ix) ; B = len_hi
|
||||
ld c, 10(ix) ; C = len_lo
|
||||
ld d, 8(ix) ; D = y
|
||||
ld e, 9(ix) ; E = x
|
||||
ld l, 0(ix)
|
||||
ld h, 1(ix) ; HL = str
|
||||
exx
|
||||
ld l, 2(ix)
|
||||
ld h, 3(ix) ; HL = win
|
||||
push hl
|
||||
pop ix ; IX = win
|
||||
exx ; BC=len, DE=y|x, HL=str
|
||||
ld a, #0x03
|
||||
call WC_ENTRY
|
||||
pop de ; remove saved str
|
||||
pop hl ; remove saved win
|
||||
pop ix ; restore caller IX
|
||||
; callee cleans: y(1)+x(1)+len(2) = 4 bytes
|
||||
pop hl ; HL = return address
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
jp (hl)
|
||||
@@ -0,0 +1,50 @@
|
||||
.module wc_prsrw_attr
|
||||
.area _CODE
|
||||
WC_ENTRY = 0x6006
|
||||
.globl _wc_prsrw_attr
|
||||
_wc_prsrw_attr::
|
||||
push ix
|
||||
push hl ; save win (HL = 1st arg)
|
||||
push de ; save str (DE = 2nd arg)
|
||||
ld ix, #0
|
||||
add ix, sp
|
||||
|
||||
; ── вызов #03 (prsrw) ──────────────────────────────────
|
||||
ld b, 11(ix) ; B = len_hi
|
||||
ld c, 10(ix) ; C = len_lo
|
||||
ld d, 8(ix) ; D = y
|
||||
ld e, 9(ix) ; E = x
|
||||
ld l, 0(ix)
|
||||
ld h, 1(ix) ; HL = str
|
||||
exx
|
||||
ld l, 2(ix)
|
||||
ld h, 3(ix) ; HL = win
|
||||
ld a, 12(ix) ; A = attr (IX всё ещё → фрейм)
|
||||
push af ; сохраняем attr на стеке
|
||||
push hl
|
||||
pop ix ; IX = win
|
||||
exx ; BC=len, DE=y|x, HL=str; HL'=win
|
||||
ld a, #0x03
|
||||
call WC_ENTRY
|
||||
|
||||
; ── вызов #04 (priat) ──────────────────────────────────
|
||||
pop af ; A = attr
|
||||
ex af, af' ; A' = attr
|
||||
exx ; HL = win (из HL'), BC=len, DE=y|x
|
||||
push hl
|
||||
pop ix ; IX = win
|
||||
exx ; restore BC=len, DE=y|x
|
||||
ld a, #0x04
|
||||
call WC_ENTRY
|
||||
|
||||
pop de ; remove saved str
|
||||
pop hl ; remove saved win
|
||||
pop ix ; restore caller IX
|
||||
; callee cleans 5 bytes: y(1)+x(1)+len(2)+attr(1)
|
||||
pop hl ; HL = return address
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
jp (hl)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user