diff --git a/DiskRef/WC/VGMPLAY.WMF b/DiskRef/WC/VGMPLAY.WMF index a0e1a30..7a9d79b 100644 Binary files a/DiskRef/WC/VGMPLAY.WMF and b/DiskRef/WC/VGMPLAY.WMF differ diff --git a/VGMPLAY.WMF b/VGMPLAY.WMF index a0e1a30..7a9d79b 100644 Binary files a/VGMPLAY.WMF and b/VGMPLAY.WMF differ diff --git a/src_sdcc/asm/isr.s b/src_sdcc/asm/isr.s index ff7ad17..eaacb32 100644 --- a/src_sdcc/asm/isr.s +++ b/src_sdcc/asm/isr.s @@ -29,6 +29,7 @@ .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 @@ -56,7 +57,7 @@ WC_IM2_VEC = 0x5BFF CMD_WRITE_AY = 0x00 CMD_INC_SEC = 0x10 CMD_WRITE_AY2 = 0x20 -CMD_CALL_WC = 0x30 +CMD_FINALIZE = 0x30 CMD_WRITE_B0 = 0x40 CMD_SKIP_TICKS= 0x50 CMD_WRITE_SAA = 0x60 @@ -301,8 +302,8 @@ _isr_cmd_loop: jp z, _isr_cmd_done cp #CMD_INC_SEC jp z, _isr_inc_sec - cp #CMD_CALL_WC - jp z, _isr_call_wc + cp #CMD_FINALIZE + jp z, _isr_cmd_finalize ; Unknown command: skip 3 bytes, continue inc de inc de @@ -589,29 +590,23 @@ _skip_spec_next: ; 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) + 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 - 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) ; @@ -641,6 +636,7 @@ _isr_play_seconds:: .dw 0 ; инкрементируется CMD_INC_SE _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) diff --git a/src_sdcc/inc/isr.h b/src_sdcc/inc/isr.h index 8d3b3ed..3d29e47 100644 --- a/src_sdcc/inc/isr.h +++ b/src_sdcc/inc/isr.h @@ -34,7 +34,7 @@ * 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_FINALIZE │ 0 │ 0 │ 0 │ Конец VGM → shutdown в main * 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 @@ -81,7 +81,7 @@ #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_FINALIZE 0x30 /* Конец VGM: остановить ISR и уведомить main */ #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 */ @@ -136,6 +136,9 @@ extern volatile uint16_t isr_play_seconds; * Main loop очищает при старте воспроизведения. */ extern volatile uint8_t isr_done; +/** ISR дошёл до последнего 0x66; main должен запустить shutdown. */ +extern volatile uint8_t isr_final_pending; + /** Командные буферы A и B. Заполняются main loop, читаются ISR. */ extern uint8_t cmd_buf_a[CMD_BUF_SIZE]; extern uint8_t cmd_buf_b[CMD_BUF_SIZE]; diff --git a/src_sdcc/lib/vgm.c b/src_sdcc/lib/vgm.c index e246b96..ae168fe 100644 --- a/src_sdcc/lib/vgm.c +++ b/src_sdcc/lib/vgm.c @@ -1755,6 +1755,16 @@ ym2_fm_passthru:; vgm_wait_accum = fb_wacc; } vgm_hl_pos++; + /* Финальная silence-цепочка должна начинаться с пустого + * ISR-буфера. Иначе большой cmdblock может быть разрезан + * границей буфера и при следующем fill стартует сначала. */ + if (fb_is_vgm && vgm_hl_pos == vgm_hl_abort_pos) { + /* ISR замрёт здесь после исполнения всего музыкального + * хвоста. Main затем запустит shutdown как при ESC. */ + fb_wp[0] = CMD_FINALIZE; + fb_wp += 4; + goto finish; + } goto next_hl; } diff --git a/src_sdcc/main.c b/src_sdcc/main.c index 0e7c044..c4922b0 100644 --- a/src_sdcc/main.c +++ b/src_sdcc/main.c @@ -675,6 +675,7 @@ void start_playback(void) isr_active_buf = 0; isr_wait_ctr = 0; isr_done = 0; + isr_final_pending = 0; /* MultiSound ctrl: включить FM/SAA только для присутствующих чипов. * 0xF2 = FM ON + SAA ON + normal read + chip1 (base). @@ -763,6 +764,7 @@ void stop_playback(void) s_buf_ready[1] = 0; s_last_active_buf = 0; isr_done = 0; + isr_final_pending = 0; ints_enable(); } @@ -865,18 +867,6 @@ void update_playback_info(void) sec = isr_play_seconds; - /* Страховка для VGM без разрешённого loop. Нормальный путь - * заканчивается на opcode 0x66; если нестандартный хвост до него не - * дошёл, не оставляем OPL4 удерживать последнюю ноту бесконечно. - * Две секунды запаса покрывают округление total_samples и уже - * поставленные в ISR команды. */ - if (!vgm_loop_enabled && vgm_total_seconds && - sec >= (uint16_t)(vgm_total_seconds + 2u)) - { - vgm_hl_pos = vgm_hl_abort_pos; - instant_abort(); - } - sec_changed = (sec != s_last_displayed_sec); loop_changed = (vgm_loop_count != s_last_loop_count); @@ -1130,6 +1120,15 @@ void main(void) /* ── 1. Буфер: максимальный приоритет ──────────────── */ update_buffer(); + /* ISR исполнил весь музыкальный хвост и замер на FINALIZE. + * Пересобрать shutdown с начала чистого буфера — тот же путь, + * который используется для надёжного выхода по ESC. */ + if (isr_final_pending) { + isr_final_pending = 0; + vgm_hl_pos = vgm_hl_abort_pos; + instant_abort(); + } + /* ISR замер на CMD_ISR_DONE → чистый выход */ if (isr_done) break;