Unify OPL4 loading and fix progress page corruption

This commit is contained in:
2026-10-04 22:09:06 +03:00
parent bafb2f369c
commit f28caf5cd0
6 changed files with 242 additions and 335 deletions
+20 -322
View File
@@ -128,17 +128,11 @@ static uint16_t fb_scaled; /* temp: масштабированная часто
static uint8_t fb_reg; /* temp: AY рег. (0xA0) / YMF278B port (0xD0) /
* тип data-block (0x67) — ветки взаимоисключающие */
/* ── Патч заголовков wave-таблицы после загрузки блока 0x84 (ROM Image) ──
* См. wave_upload_block(): референсный NedoOS-драйвер (vgm/opl4.asm,
* opl4loadromdatablock) после заливки ROM-блока, чей адрес попадает в
* первые 64КБ RAM-региона (где живёт таблица заголовков сэмплов, 128
* записей по 12 байт), принудительно взводит бит RAM-адресации (#20) в
* первом байте КАЖДОЙ записи — иначе встроенные в заголовки адреса
* сэмплов остаются ROM-относительными, и чип читает не тот PCM-семпл. */
static uint8_t hdr_recs; /* оставшиеся записи таблицы (0-128); 0=не патчим */
static uint8_t hdr_sub; /* позиция внутри 12-байтной записи (0-11) */
static uint8_t opl4_rom_loaded; /* 0x84 ROM image перенесён в SRAM; номера
* волн надо отображать в диапазон 384-511 */
/* Взводится холодным загрузчиком (через vgm_opl4_preloaded_rom), когда он
* перенёс в SRAM блок типа 0x84 (ROM Image) и пропатчил бит RAM-адресации
* в 128 заголовках wave-таблицы. Тогда исходные номера волн из VGM надо
* отображать в диапазон SRAM-заголовков 384-511 (см. обработчик 0xD0). */
static uint8_t opl4_rom_loaded;
/* VGM_FILL_CMD_BUDGET определён в variant_cfg.h (через isr.h) */
@@ -827,307 +821,16 @@ static void asm_read_2bytes(void) __naked
__endasm;
}
/* ─────────────────────────────────────────────────────────────────────
* OPL4 (ZXM-MoonSound) — прямая (не через cmd-buffer) запись в wave-порты
* #7E (выбор регистра) / #7F (данные), плюс переключение turbo-режима.
* Используются ТОЛЬКО в wave_upload_block() — разовая синхронная заливка
* PCM-сэмплов при загрузке трека, до старта ISR-плейбека (не hot-path).
* ───────────────────────────────────────────────────────────────────── */
/* out (n),a — короткая форма (2 байта). Старший байт адреса (A15-A8)
* при этом равен A (значению-аргументу), но плата его не видит: линия
* CA[7..0] у CPLD DD2 — единственный адресный вход декодера портов
* #7E/#7F/#C4-#C7 (8 бит), верхняя половина шины к нему не подключена
* (см. схему ZXM-MoonSound, dd2.tdf) — как и в родном moon_driver.asm.
/* Заливка PCM-сэмплов в SRAM карты живёт целиком в холодном загрузчике
* (asm/opl4_loader.asm на оверлейной странице 5): он вызывается из
* load_vgm() ДО старта воспроизведения, заливает блоки 0x67 типа
* 0x84/0x87 и вырезает их из потока. Поэтому сюда, в основной бюджет
* 0x8000-0xBFFF, эти 376 байт больше не входят, а обработчик 0x67 ниже
* просто пропускает блок: к началу воспроизведения его уже нет.
*
* Перед каждой записью — опрос busy-флага (бит0 порта #C4 — тот же
* порт, что и FM Bank1 addr-select, но на чтение отдаёт статус чипа).
* Без этого ожидания запись может быть потеряна чипом (особенно в
* режиме прямого доступа к памяти) — подтверждено рабочим NedoOS-
* драйвером (common/moonsound.asm: opl4_wait), а не только datasheet. */
static void chip_wait(void) __naked {
__asm
push af
cw_loop:
in a, (0xC4)
rrca
jr c, cw_loop
pop af
ret
__endasm;
}
static void wave_out_reg(uint8_t reg) __naked {
__asm
call _chip_wait
out (0x7E), a
ret
__endasm;
(void)reg;
}
static void wave_out_dat(uint8_t val) __naked {
__asm
call _chip_wait
out (0x7F), a
ret
__endasm;
(void)val;
}
/* Быстрая заливка fb_w байт из текущей VPL-страницы. В отличие от
* asm_read_byte()+wave_out_dat() указатели и счётчик держатся в регистрах;
* обязательный busy-wait перед каждым байтом сохранён. Вызывающий код не
* даёт циклу пересечь границу страницы. */
static void wave_upload_fast(void) __naked {
__asm
ld hl, (_fb_rp)
ld bc, (_fb_w)
wuf_loop:
in a, (0xC4)
rrca
jr c, wuf_loop
ld a, (hl)
out (0x7F), a
inc hl
dec bc
ld a, b
or a, c
jr nz, wuf_loop
ld (_fb_rp), hl
ret
__endasm;
}
/* ─────────────────────────────────────────────────────────────────────
* wave_upload_block — залить payload VGM data-block 0x67 типа 0x84
* (YMF278B ROM Image) / 0x87 (RAM Image) в SRAM карты ZXM-MoonSound.
*
* Вызывается из обработчика 0x67 в vgm_fill_buffer() ДО начала реальной
* ISR-подачи звука (все такие блоки в реальных файлах идут одним пакетом
* в самом начале потока) — поэтому синхронная блокирующая заливка здесь
* безопасна: играть ещё нечему.
*
* Формат payload (VGM spec, "ROM/RAM Image" data block, offset от начала
* data-payload, т.е. после opcode+0x66+type+4-байт общего размера):
* [0..3] ROM/RAM Size (информационное, не используется)
* [4..7] Start Address — смещение внутри SRAM карты (0-based)
* [8..] сами байты сэмпла/инструмент-заголовков
*
* Адресация чипа YMF278B: 2МБ встроенный ROM (General MIDI) + 1МБ SRAM,
* физически идущие в чипе одним адресным пространством — SRAM начинается
* сразу по границе 0x200000 (аппаратный CS чипа по адресному биту A21,
* см. datasheet, от платы не зависит). wavetblhdr=4 (4×0x80000=0x200000)
* выставлен так, чтобы номер сэмпла (wave) 384 указывал ровно на начало
* этой SRAM — тем самым абсолютный адрес = 0x200000 + Start Address.
*
* len = полный размер data-block (4-байтовое поле "size" из потока),
* включает 8-байтовый под-заголовок выше.
* ───────────────────────────────────────────────────────────────────── */
static void wave_upload_block(void) __naked
{
__asm
; ROM/RAM Size (4 bytes) is informational only.
call _asm_read_2bytes
call _asm_read_2bytes
; NEW2: FM bank 1, register 05h = 03h. Wave registers ignore writes
; until this bit is enabled, and the command ISR has not started yet.
call _chip_wait
ld a, #0x05
out (0xC6), a
call _chip_wait
ld a, #0x03
out (0xC7), a
; VGM start address is little-endian; YMF278B wants high/mid/low.
call _asm_read_byte
ld (_fb_b2), a
call _asm_read_byte
ld (_fb_b1), a
call _asm_read_byte
ld (_fb_ch), a
call _asm_read_byte ; unused top byte of the 32-bit address
; Patch 128 twelve-byte headers only for a ROM image beginning at 0.
xor a
ld (_hdr_recs), a
ld (_hdr_sub), a
ld a, (_fb_reg)
cp a, #0x84
jr nz, wub_header_ready
ld a, (_fb_ch)
ld hl, #_fb_b1
or a, (hl)
inc hl
or a, (hl)
jr nz, wub_header_ready
ld a, #0x80
ld (_hdr_recs), a
wub_header_ready:
; Memory address = 200000h + start address. Register order matters.
ld a, #0x03
call _wave_out_reg
ld a, (_fb_ch)
add a, #0x20
call _wave_out_dat
ld a, #0x04
call _wave_out_reg
ld a, (_fb_b1)
call _wave_out_dat
ld a, #0x05
call _wave_out_reg
ld a, (_fb_b2)
call _wave_out_dat
; 11h = memory access mode, WT header base 200000h; 06h = data port.
ld a, #0x02
call _wave_out_reg
ld a, #0x11
call _wave_out_dat
ld a, #0x06
call _wave_out_reg
; fb_t32 contains the whole block payload, including its 8-byte header.
ld hl, (_fb_t32)
ld de, #0x0008
or a
sbc hl, de
ld (_fb_t32), hl
jr nc, wub_len_ready
ld hl, (_fb_t32 + 2)
dec hl
ld (_fb_t32 + 2), hl
wub_len_ready:
ld a, (_hdr_recs)
or a
jr z, wub_fast_test
ld a, #0x01
ld (_opl4_rom_loaded), a
; Patch address bit 21 in byte 0 of each of the first 128 headers.
wub_patch_test:
ld a, (_hdr_recs)
or a
jr z, wub_fast_test
ld hl, #_fb_t32
ld a, (hl)
inc hl
or a, (hl)
inc hl
or a, (hl)
inc hl
or a, (hl)
jp z, wub_finish
call _asm_read_byte
ld e, a
ld a, (_hdr_sub)
or a
jr nz, wub_patch_count
ld a, e
or a, #0x20
ld e, a
wub_patch_count:
ld a, (_hdr_sub)
inc a
cp a, #0x0C
jr nz, wub_store_sub
xor a
ld (_hdr_sub), a
ld hl, #_hdr_recs
dec (hl)
jr wub_patch_write
wub_store_sub:
ld (_hdr_sub), a
wub_patch_write:
ld a, e
call _wave_out_dat
; --fb_t32. The high word changes only when the low word wraps.
ld hl, (_fb_t32)
dec hl
ld (_fb_t32), hl
ld a, h
and a, l
inc a
jr nz, wub_patch_test
ld hl, (_fb_t32 + 2)
dec hl
ld (_fb_t32 + 2), hl
jr wub_patch_test
; Upload the rest in chunks that never cross a mapped 16K page.
wub_fast_test:
ld hl, #_fb_t32
ld a, (hl)
inc hl
or a, (hl)
inc hl
or a, (hl)
inc hl
or a, (hl)
jr z, wub_finish
; fb_w = 0 - fb_rp: number of bytes through FFFFh inclusive.
ld hl, (_fb_rp)
xor a
sub a, l
ld e, a
ld a, #0x00
sbc a, h
ld d, a
ld (_fb_w), de
; If the remaining 32-bit length is smaller, use its low word.
ld hl, (_fb_t32 + 2)
ld a, h
or a, l
jr nz, wub_fast_upload
ld hl, (_fb_t32)
or a
sbc hl, de
jr nc, wub_fast_upload
ld hl, (_fb_t32)
ld (_fb_w), hl
wub_fast_upload:
call _wave_upload_fast
; fb_t32 -= fb_w, propagating carry into the high word.
ld hl, (_fb_t32)
ld de, (_fb_w)
or a
sbc hl, de
ld (_fb_t32), hl
jr nc, wub_after_subtract
ld hl, (_fb_t32 + 2)
dec hl
ld (_fb_t32 + 2), hl
wub_after_subtract:
; wave_upload_fast leaves fb_rp at zero exactly on a page boundary.
ld hl, (_fb_rp)
ld a, h
or a, l
jr nz, wub_fast_test
ld hl, #_fb_cpg
inc (hl)
ld a, (hl)
call _wc_mngcvpl
ld hl, #0xC000
ld (_fb_rp), hl
jr wub_fast_test
wub_finish:
ld a, #0x02
call _wave_out_reg
ld a, #0x10 ; sound generation, WT base 200000h
call _wave_out_dat
ret
__endasm;
}
* ВАЖНО: загрузчик снимает только непрерывную серию блоков в САМОМ
* начале потока. Блок сэмплов в середине трека будет пропущен, а не
* залит (проверено: ни в одном файле Moonsound_pack такого нет). */
/* ─────────────────────────────────────────────────────────────────────
* asm_emit_skip_spec — запись CMD_SKIP_TICKS + spec_mask в cmd buffer
*
@@ -1920,17 +1623,12 @@ ym2_fm_passthru:;
asm_read_2bytes();
fb_t32 |= ((uint32_t)fb_b1 << 16) | ((uint32_t)fb_b2 << 24);
if (fb_reg == 0x84 || fb_reg == 0x87) {
/* YMF278B ROM/RAM Image — залить payload в SRAM карты */
wave_upload_block();
} else {
/* сохранить → vgm_skip → восстановить */
vgm_read_ptr = (uint16_t)fb_rp;
vgm_cur_page = fb_cpg;
vgm_skip(fb_t32);
fb_rp = (uint8_t *)vgm_read_ptr;
fb_cpg = vgm_cur_page;
}
/* сохранить → vgm_skip → восстановить */
vgm_read_ptr = (uint16_t)fb_rp;
vgm_cur_page = fb_cpg;
vgm_skip(fb_t32);
fb_rp = (uint8_t *)vgm_read_ptr;
fb_cpg = vgm_cur_page;
continue;
}