660 lines
25 KiB
ArmAsm
660 lines
25 KiB
ArmAsm
;==============================================================================
|
|
; @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 _isr_final_pending
|
|
.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_FINALIZE = 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_FINALIZE
|
|
jp z, _isr_cmd_finalize
|
|
; 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:
|
|
ld a, #1
|
|
ld (_isr_done), a
|
|
jr _isr_cmd_freeze
|
|
|
|
;--- CMD_FINALIZE: музыкальный поток полностью исполнен -----------------
|
|
; Замереть на маркере и попросить main собрать shutdown как при ESC.
|
|
_isr_cmd_finalize:
|
|
ld a, #1
|
|
ld (_isr_final_pending), a
|
|
|
|
_isr_cmd_freeze:
|
|
; DE уже указывает на первый параметр: вернуть read_ptr на opcode.
|
|
ex de, hl
|
|
dec hl
|
|
ld (_isr_read_ptr), hl
|
|
jp _isr_exit
|
|
|
|
;==============================================================================
|
|
; 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, готов к завершению
|
|
_isr_final_pending:: .db 0 ; 1 = последний VGM 0x66 уже исполнен
|
|
_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
|