Финальная тема won: кольцевой стриминг с диска
Последний неозвученный кусок игры. 115 секунд и 1,2 МБ в память не влезают никак, поэтому тема играется кольцом из шести страниц (96 КБ = 9 с): насос идёт по кругу, а pop_music_service дочитывает файл в те страницы, которые насос уже прошёл. Страница звучит 1,5 с и читается 33 мс — запас сорокакратный; дистанция считается без отдельных счётчиков, потому что страница это ровно 128 блоков насоса. Три вещи, всплывшие при живой проверке: - кольцо обязано сниматься при любом обычном запуске трека, иначе следующая тема играет по кругу первых шести страниц (поймано на титрах сразу после победы); - живые сцены комнаты принцессы теперь открывают CBL сами (cut_begin): pop_ending_show глушит звук первым действием, и «arrived to princess» не звучал вовсе; - тема дослушивается до конца с возможностью прервать клавишей — как seg001:637. У оригинала там стоит ввод имени в таблицу рекордов; когда он появится, ожидание переедет за него (задача HOF-ENTRY). Проверено в MAME на сборке LEVEL=14: после встречи с принцессой звучит тема победы (id 56, кольцо 6), курсор уходит далеко за размер кольца — подкачка успевает. Host-тесты: 15 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,34 @@ static void slot_release(uint8_t n)
|
||||
slot_blocks[n] = 0;
|
||||
}
|
||||
|
||||
/* --- ПОТОКОВЫЙ ТРЕК: КОЛЬЦО СТРАНИЦ ---------------------------------- *
|
||||
*
|
||||
* Финальная тема (`won`, 115 с) — это 1,2 МБ и 78 страниц EMM. Держать её
|
||||
* в памяти нельзя ни при каком бюджете, поэтому она играется из НЕСКОЛЬКИХ
|
||||
* страниц по кругу: насос идёт по кольцу (pop_mus_ring), а загрузчик
|
||||
* дочитывает файл в те страницы, которые насос уже прошёл.
|
||||
*
|
||||
* Кто кого догоняет. Страница звучит 1,5 с, а читается 33 мс — запас
|
||||
* сорокакратный, так что «не успеть» можно только застряв на секунду.
|
||||
* Дистанцию считаем без отдельных счётчиков: страница ровно 128 блоков
|
||||
* насоса, поэтому проигранных страниц = (всего блоков - осталось) / 128.
|
||||
* Пока прочитано меньше, чем проиграно плюс размер кольца, — в кольце есть
|
||||
* свободный слот, и следующую страницу можно читать.
|
||||
*
|
||||
* Файл читаем ПОСЛЕДОВАТЕЛЬНО, без lseek: страницы идут по порядку, и fd
|
||||
* сам стоит там, где надо. */
|
||||
#define RING_PAGES 6 /* 96 КБ = 9 с звучания, запас с лихвой */
|
||||
|
||||
static uint8_t ring_on; /* идёт потоковый трек */
|
||||
static uint8_t ring_blk; /* EMM-блок кольца */
|
||||
static uint8_t ring_fd_open;
|
||||
static int ring_fd;
|
||||
static uint8_t ring_file_pages; /* страниц в файле */
|
||||
static uint8_t ring_loaded; /* сколько уже прочитано */
|
||||
static uint16_t ring_total; /* длина трека в блоках насоса */
|
||||
|
||||
void pop_music_ring_stop(void) __banked;
|
||||
|
||||
void pop_music_stop(void) __banked
|
||||
{
|
||||
pop_mus_left = 0;
|
||||
@@ -69,6 +97,7 @@ void pop_music_stop(void) __banked
|
||||
|
||||
void pop_music_free(void) __banked
|
||||
{
|
||||
if (ring_on) pop_music_ring_stop();
|
||||
if (ld_busy) { close(ld_fd); ld_busy = 0; }
|
||||
pop_mus_left = 0;
|
||||
slot_release(0);
|
||||
@@ -96,6 +125,7 @@ int8_t pop_music_load_begin(uint8_t id) __banked
|
||||
char name[12];
|
||||
uint8_t pages, n;
|
||||
|
||||
if (ring_on) pop_music_ring_stop(); /* потоковый трек и слоты не дружат */
|
||||
if (ld_busy) load_abort();
|
||||
if (id >= POP_MUS_COUNT) return -1;
|
||||
pages = pop_mus_tbl[id].pages;
|
||||
@@ -174,6 +204,7 @@ int8_t pop_music_stream(uint8_t id) __banked
|
||||
{
|
||||
uint8_t i, n;
|
||||
|
||||
if (ring_on) pop_music_ring_stop(); /* см. pop_music_play */
|
||||
if (pop_music_load_begin(id) != 0) return -1;
|
||||
if (pop_music_load_step() < 0) return -1; /* первая страница */
|
||||
n = ld_slot;
|
||||
@@ -197,6 +228,10 @@ void pop_music_play(void) __banked
|
||||
uint8_t n, i;
|
||||
|
||||
if (!mus_ready) return;
|
||||
/* СНЯТЬ КОЛЬЦО. Если до этого играл потоковый трек (финал), насос всё
|
||||
* ещё заворачивал бы страницы по кругу шести — обычный трек звучал бы
|
||||
* своей первой третью без конца (поймано на титрах после победы). */
|
||||
if (ring_on) pop_music_ring_stop();
|
||||
n = (uint8_t)(mus_ready - 1);
|
||||
/* Насос обязан замолчать ДО подмены таблицы страниц: иначе он успеет
|
||||
* взять порцию по новому индексу из старого курсора. */
|
||||
@@ -215,6 +250,84 @@ void pop_music_play(void) __banked
|
||||
pop_mus_left = slot_blocks[n];
|
||||
}
|
||||
|
||||
|
||||
static void ring_close_file(void)
|
||||
{
|
||||
if (ring_fd_open) { close(ring_fd); ring_fd_open = 0; }
|
||||
}
|
||||
|
||||
void pop_music_ring_stop(void) __banked
|
||||
{
|
||||
pop_mus_left = 0;
|
||||
pop_mus_ring = 0;
|
||||
ring_close_file();
|
||||
if (ring_blk) { mem_free_block(ring_blk); ring_blk = 0; }
|
||||
ring_on = 0;
|
||||
}
|
||||
|
||||
int8_t pop_music_ring_start(uint8_t id) __banked
|
||||
{
|
||||
char name[12];
|
||||
uint8_t i, page;
|
||||
|
||||
if (id >= POP_MUS_COUNT || !pop_mus_tbl[id].pages) return -1;
|
||||
pop_music_free(); /* обычные слоты кольцу не нужны */
|
||||
pop_music_ring_stop();
|
||||
mus_name(name, id);
|
||||
ring_fd = open(name, O_RDONLY);
|
||||
if (ring_fd < 0) return -1;
|
||||
ring_fd_open = 1;
|
||||
ring_blk = mem_alloc_pages(RING_PAGES);
|
||||
if (!ring_blk) { ring_close_file(); return -1; }
|
||||
ring_file_pages = pop_mus_tbl[id].pages;
|
||||
ring_total = pop_mus_tbl[id].blocks;
|
||||
/* Заполняем кольцо целиком: старт с полным буфером даёт девять секунд
|
||||
* форы, и первая же тяжёлая сцена финала не догонит насос. */
|
||||
for (i = 0; i < RING_PAGES; i++) {
|
||||
page = mem_get_page(ring_blk, i);
|
||||
pop_mus_page[i] = page;
|
||||
if (!page ||
|
||||
(i < ring_file_pages && bank_read_page(ring_fd, page, 0, 16384) < 0)) {
|
||||
pop_music_ring_stop();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
ring_loaded = (uint8_t)(ring_file_pages < RING_PAGES ? ring_file_pages
|
||||
: RING_PAGES);
|
||||
if (ring_loaded >= ring_file_pages) ring_close_file();
|
||||
ring_on = 1;
|
||||
pop_mus_id = id;
|
||||
pop_mus_pg = 0;
|
||||
pop_mus_ptr = 0;
|
||||
pop_mus_ring = RING_PAGES; /* насос пойдёт по кругу */
|
||||
pop_mus_left = ring_total;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Один шаг подкачки: страница файла в слот, который насос уже прошёл. */
|
||||
static void ring_step(void)
|
||||
{
|
||||
uint16_t played;
|
||||
uint8_t played_pg, page;
|
||||
|
||||
if (!ring_on) return;
|
||||
if (!pop_mus_left) { /* трек доиграл — отпустить память */
|
||||
pop_music_ring_stop();
|
||||
return;
|
||||
}
|
||||
if (ring_loaded >= ring_file_pages) return; /* файл прочитан весь */
|
||||
played = (uint16_t)(ring_total - pop_mus_left);
|
||||
played_pg = (uint8_t)(played >> 7); /* 128 блоков на страницу */
|
||||
if ((uint8_t)(ring_loaded - played_pg) >= RING_PAGES) return; /* полно */
|
||||
page = pop_mus_page[ring_loaded % RING_PAGES];
|
||||
if (!page || bank_read_page(ring_fd, page, 0, 16384) < 0) {
|
||||
pop_music_ring_stop();
|
||||
return;
|
||||
}
|
||||
ring_loaded++;
|
||||
if (ring_loaded >= ring_file_pages) ring_close_file();
|
||||
}
|
||||
|
||||
void pop_music_service(void) __banked
|
||||
{
|
||||
/* Один вызов на кадр из ЛЮБОГО цикла — игрового, интро, катсцены.
|
||||
@@ -225,6 +338,7 @@ void pop_music_service(void) __banked
|
||||
* демо проигрывает записанные нажатия, и любой музыкальный повод —
|
||||
* подобранный меч, зелье — зазвучал бы там, где оригинал играет одни
|
||||
* эффекты. */
|
||||
if (ring_on) { ring_step(); return; } /* потоковый трек — свой путь */
|
||||
if (!pop_mus_want || pop_app_state == POP_APP_DEMO) {
|
||||
/* Выключили на ходу: оборвать поток и замолчать. Эффекты при этом
|
||||
* продолжают звучать — у них свой тумблер. */
|
||||
|
||||
Reference in New Issue
Block a user