Финальная тема 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:
2026-08-26 12:32:40 +03:00
parent 5d912f0f3f
commit faec9a7d3a
10 changed files with 256 additions and 17 deletions
+31 -5
View File
@@ -930,8 +930,34 @@ load_intro у оригинала и так начинается с ожидан
«ступень на каждый кадр» звал пересчёт сто раз и растягивал fade до «ступень на каждый кадр» звал пересчёт сто раз и растягивал fade до
десяти с лишним секунд. десяти с лишним секунд.
**Чего пока нет.** Финальный `won` (56): 115 с, 1,2 МБ, 78 страниц EMM — ## 8. ПОТОКОВЫЙ ТРЕК: ФИНАЛЬНАЯ ТЕМА (2026-08-26)
в `POP_MUS_PAGES` (20) он не помещается даже теоретически. Ему нужен
КОЛЬЦЕВОЙ стриминг: держать в памяти несколько страниц и дочитывать в те, `won` (56) — 115 с, 1,2 МБ, 78 страниц EMM. В память он не влезает ни при
что насос уже прошёл. Это отдельная задача — курсор насоса придётся каком бюджете, поэтому играется КОЛЬЦОМ из шести страниц (96 КБ = 9 с): насос
научить заворачиваться, а загрузчик — держать дистанцию от него. идёт по кругу, а `pop_music_service` дочитывает файл в те страницы, которые
насос уже прошёл.
**Кто кого догоняет.** Страница звучит 1,5 с, а читается 33 мс — запас
сорокакратный. Дистанция считается без отдельных счётчиков: страница ровно
128 блоков насоса, поэтому проигранных страниц = (всего блоков − осталось)
/ 128. Пока прочитано меньше, чем проиграно плюс размер кольца, в кольце
есть свободный слот. Файл читается ПОСЛЕДОВАТЕЛЬНО, без `lseek`.
**Что пришлось учесть.**
* Насос заворачивает страницу только при `pop_mus_ring != 0`; конец трека
по-прежнему определяет `left`. Обычный трек этой ветки не касается.
* Кольцо обязано сниматься при любом обычном запуске (`pop_music_play`,
`_stream`, `_load_begin`): иначе следующий трек играет по кругу первых
шести страниц — поймано на титрах сразу после победы.
* Живые сцены комнаты принцессы открывают CBL сами (`cut_begin`):
`pop_ending_show` глушит звук первым действием, и «arrived to princess»
(26) не звучал вовсе.
* Тема дослушивается до конца (прерывается клавишей), как `while
(check_sound_playing() && !key_test_quit())` в seg001:637. У оригинала
между титрами и этим ожиданием стоит ввод имени в таблицу рекордов —
когда он появится у нас, ожидание переедет за него (задача HOF-ENTRY).
Проверено в MAME на сборке `LEVEL=14`: после встречи с принцессой звучит
тема победы (`pop_mus_id` = 56, `pop_mus_ring` = 6), курсор уходит далеко
за размер кольца — то есть подкачка успевает.
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -214,7 +214,7 @@ SND_DATA := $(SND_ARC)
# игра 24 25 28 29 30 32 33 37 39 41 43 # игра 24 25 28 29 30 32 33 37 39 41 43
# Нет только 56 (won, 115 с / 1,2 МБ) — финалу нужен стриминг с диска. # Нет только 56 (won, 115 с / 1,2 МБ) — финалу нужен стриминг с диска.
MUS_DIR := $(CURDIR)/../poc/res/music MUS_DIR := $(CURDIR)/../poc/res/music
MUS_IDS := 24 25 26 27 28 29 30 32 33 35 36 37 39 40 41 43 50 52 53 54 55 MUS_IDS := 24 25 26 27 28 29 30 32 33 35 36 37 39 40 41 43 50 52 53 54 55 56
MUS_DATA := $(foreach i,$(MUS_IDS),$(MUS_DIR)/m$(i).bin) MUS_DATA := $(foreach i,$(MUS_IDS),$(MUS_DIR)/m$(i).bin)
# UI: два исходных шрифта SDLPoP в одном EMM-атласе. Файл короче страницы: # UI: два исходных шрифта SDLPoP в одном EMM-атласе. Файл короче страницы:
+36 -5
View File
@@ -78,7 +78,10 @@
это максимум из встречавшихся; 12/22/24 — skill 2/3/3). это максимум из встречавшихся; 12/22/24 — skill 2/3/3).
<a id="l9-invert"></a> <a id="l9-invert"></a>
### L9-INVERT. Зелье ПЕРЕВОРОТА (уровень 9, комнаты 7 и 10) ### L9-INVERT. Зелье ПЕРЕВОРОТА — ЗАКРЫТА 2026-08-26
> Проверено пользователем в живой игре.
> **Подробный план реализации — [`../docs/l9_invert_plan.md`](../docs/l9_invert_plan.md)** > **Подробный план реализации — [`../docs/l9_invert_plan.md`](../docs/l9_invert_plan.md)**
> (задачи libbgi, задачи приложения, порядок работ, риски, открытые вопросы). > (задачи libbgi, задачи приложения, порядок работ, риски, открытые вопросы).
@@ -379,7 +382,10 @@ x = 106; разбор — [`../docs/perf_l11_room15.md`](../docs/perf_l11_room15
(6 126 тактов фиксированной накладной на любой блит, независимо от размера). (6 126 тактов фиксированной накладной на любой блит, независимо от размера).
### <a id="l12-shadow"></a>L12-SHADOW. Уровень 12: встреча с тенью, бой, слияние ### <a id="l12-shadow"></a>L12-SHADOW. Уровень 12: тень, бой, слияние — ЗАКРЫТА 2026-08-26
> Проверено пользователем в живой игре.
> **Статус 2026-08-13: код написан, хост-тесты зелёные (45 проверок, > **Статус 2026-08-13: код написан, хост-тесты зелёные (45 проверок,
> `tests-host/t_shadow.c`), ЖИВОЙ ПРОВЕРКИ В MAME ЕЩЁ НЕ БЫЛО.** Для неё > `tests-host/t_shadow.c`), ЖИВОЙ ПРОВЕРКИ В MAME ЕЩЁ НЕ БЫЛО.** Для неё
@@ -497,7 +503,10 @@ POP_RD_LOOSE, 1)` — полную перерисовку тайла, десят
берётся вместе с остальной шлифовкой переходов. берётся вместе с остальной шлифовкой переходов.
<a id="l13-jaffar"></a> <a id="l13-jaffar"></a>
### L13-JAFFAR. Уровень 13: Джафар, победа, выход, падающая гряда ### L13-JAFFAR. Уровень 13: Джафар, победа, выход — ЗАКРЫТА 2026-08-26
> Проверено пользователем в живой игре.
> **Статус 2026-08-13: код написан, хост-тесты зелёные (44 проверки, > **Статус 2026-08-13: код написан, хост-тесты зелёные (44 проверки,
> `tests-host/t_jaffar.c`), ЖИВОЙ ПРОВЕРКИ В MAME ЕЩЁ НЕ БЫЛО.** > `tests-host/t_jaffar.c`), ЖИВОЙ ПРОВЕРКИ В MAME ЕЩЁ НЕ БЫЛО.**
@@ -550,7 +559,10 @@ POP_RD_LOOSE, 1)` — полную перерисовку тайла, десят
6. комната 23 — уровень молча становится 13-м, HP НЕ сбрасывается. 6. комната 23 — уровень молча становится 13-м, HP НЕ сбрасывается.
### <a id="l7-feather"></a>L7-FEATHER. Зелье МЕДЛЕННОГО ПАДЕНИЯ (уровень 7, комната 1) ### <a id="l7-feather"></a>L7-FEATHER. Зелье МЕДЛЕННОГО ПАДЕНИЯ — ЗАКРЫТА 2026-08-26
> Проверено пользователем в живой игре.
> **Статус 2026-08-12: код написан, ждёт живой проверки в MAME.** Сделаны все > **Статус 2026-08-12: код написан, ждёт живой проверки в MAME.** Сделаны все
> шесть шагов ниже плюс синие пузырьки зелья «−HP» (тип 5) — они всплыли по > шесть шагов ниже плюс синие пузырьки зелья «−HP» (тип 5) — они всплыли по
@@ -1280,7 +1292,24 @@ N навык M» — отдельная надобность, если пона
нужны в разное время: `kid.pal` — каждому уровню, заставочные — только нужны в разное время: `kid.pal` — каждому уровню, заставочные — только
заставке. заставке.
### <a id="mus-won"></a>MUS-WON. Финальная тема — кольцевой стриминг с диска ### <a id="hof-entry"></a>HOF-ENTRY. Ввод имени в таблицу рекордов
У оригинала после титров победы игрок вписывает своё имя в Hall of Fame
(`input_str` в `end_sequence`, seg001:614), и победная тема играет всё это
время — она и рассчитана на то, что человек сидит и печатает.
У нас показ таблицы есть, ввода нет. Из-за этого тема сейчас
дослушивается в конце `pop_ending_show`; когда ввод появится, ожидание
надо перенести ЗА таблицу — как в оригинале.
### <a id="mus-won"></a>MUS-WON. Финальная тема — кольцевой стриминг — ЗАКРЫТА 2026-08-26
> **Сделано.** Кольцо из шести страниц (96 КБ = 9 с), насос заворачивается
> по `pop_mus_ring`, подкачка в `pop_music_service` по дистанции
> «проиграно / прочитано». Разбор и грабли — `../docs/sound_plan.md` §8.
> Проверено в MAME на сборке `LEVEL=14`.
<details><summary>исходная постановка</summary>
Трек 56 (`won`, 115 с) — это 1,2 МБ, 78 страниц EMM. Ни в `POP_MUS_PAGES` Трек 56 (`won`, 115 с) — это 1,2 МБ, 78 страниц EMM. Ни в `POP_MUS_PAGES`
(20), ни в разумный бюджет памяти он не влезает, поэтому единственный (20), ни в разумный бюджет памяти он не влезает, поэтому единственный
@@ -1296,6 +1325,8 @@ N навык M» — отдельная надобность, если пона
**Заодно.** Тот же механизм снимает ограничение `POP_MUS_PAGES` со всех **Заодно.** Тот же механизм снимает ограничение `POP_MUS_PAGES` со всех
треков: сейчас каждый должен помещаться в память целиком. треков: сейчас каждый должен помещаться в память целиком.
</details>
### <a id="arc-atlases"></a>ARC-ATLASES. Атласы — в архивы PBA1 — ЗАКРЫТА 2026-08-26 ### <a id="arc-atlases"></a>ARC-ATLASES. Атласы — в архивы PBA1 — ЗАКРЫТА 2026-08-26
> **Сделано.** Восемь архивов: kid (58 атласов), pv (78), shadow (32), bg > **Сделано.** Восемь архивов: kid (58 атласов), pv (78), shadow (32), bg
+8
View File
@@ -19,6 +19,7 @@
#include "pop_timer.h" #include "pop_timer.h"
#include "pop_pal.h" #include "pop_pal.h"
#include "pop_ui.h" #include "pop_ui.h"
#include "pop_music.h" /* подкачка потоковой темы победы */
#include "pop_hof.h" #include "pop_hof.h"
#define HOF_COUNT 5 #define HOF_COUNT 5
@@ -260,10 +261,15 @@ static void hof_draw(uint8_t edit_row)
pop_screen_present(page); pop_screen_present(page);
} }
/* Ожидания на экране рекордов держат ПОТОКОВУЮ музыку: победная тема
* (1,2 МБ) играет кольцом страниц, и дочитывать её надо всё время, пока
* игрок смотрит таблицу — иначе насос догонит загрузчик и пойдёт по
* кругу старыми данными. */
static void hof_wait_release(void) static void hof_wait_release(void)
{ {
while (kbd_raw_any_down()) { while (kbd_raw_any_down()) {
kbd_raw_sync(); kbd_raw_sync();
pop_music_service();
gfx_wait_vsync(); gfx_wait_vsync();
} }
} }
@@ -340,6 +346,7 @@ void pop_hof_show(void) __banked
hof_wait_release(); hof_wait_release();
while (!kbd_raw_any_down()) { while (!kbd_raw_any_down()) {
kbd_raw_sync(); kbd_raw_sync();
pop_music_service();
gfx_wait_vsync(); gfx_wait_vsync();
} }
hof_wait_release(); hof_wait_release();
@@ -363,6 +370,7 @@ void pop_hof_show_title(void) __banked
hof_wait_release(); hof_wait_release();
while (!kbd_raw_any_down()) { while (!kbd_raw_any_down()) {
kbd_raw_sync(); kbd_raw_sync();
pop_music_service();
gfx_wait_vsync(); gfx_wait_vsync();
} }
hof_wait_release(); hof_wait_release();
+35 -2
View File
@@ -1323,6 +1323,10 @@ static int cut_begin(uint8_t hg_state)
intro_commit_palette(); intro_commit_palette();
if (intro_pv_draw_base() != 0) return -1; if (intro_pv_draw_base() != 0) return -1;
if (cut_load() != 0) return -1; if (cut_load() != 0) return -1;
/* Атласы сцены прочитаны — можно открывать вывод. Без этого сцена шла
* молча: pop_ending_show глушит CBL первым действием, а включить его
* обратно было некому, и «arrived to princess» (26) не звучал вовсе. */
(void)pop_sfx_start();
return 0; return 0;
} }
@@ -1500,6 +1504,7 @@ static uint8_t intro_run(const pop_cs_cmd_t *script)
} else if (ev == POP_CS_EVENT_FADE_IN) pop_pal_fade_in(INTRO_FADE); } else if (ev == POP_CS_EVENT_FADE_IN) pop_pal_fade_in(INTRO_FADE);
else if (ev == POP_CS_EVENT_FADE_OUT) pop_pal_fade_out(INTRO_FADE); else if (ev == POP_CS_EVENT_FADE_OUT) pop_pal_fade_out(INTRO_FADE);
else if (ev == POP_CS_EVENT_FLASH) intro_flash(arg); else if (ev == POP_CS_EVENT_FLASH) intro_flash(arg);
pop_music_service(); /* подкачка потокового трека (финал) */
gfx_wait_vsync(); gfx_wait_vsync();
} }
@@ -1730,7 +1735,35 @@ void pop_ending_show(void) __banked
/* Встреча с принцессой — живая сцена (end_sequence_anim). Дальше по /* Встреча с принцессой — живая сцена (end_sequence_anim). Дальше по
* скрипту идёт только текст HAIL, поэтому статический сценарий ниже * скрипту идёт только текст HAIL, поэтому статический сценарий ниже
* начинается с него; при нехватке ресурсов отыгрывается целиком. */ * начинается с него; при нехватке ресурсов отыгрывается целиком. */
if (cut_ending() == 0) intro_run(ending_hail_script); if (cut_ending() == 0) {
else intro_run(ending_script); /* ПОБЕДНАЯ ТЕМА (56) — потоком с диска: 115 секунд и 1,2 МБ в память
* не влезают никак. Оригинал пускает её здесь же, сразу после
* встречи с принцессой и до экрана HAIL (seg001:583). Дальше
* страницы дочитывает pop_music_service из циклов сцен. */
(void)pop_sfx_start();
(void)pop_music_ring_start(POP_MUS_WON);
intro_run(ending_hail_script);
/* ДОСЛУШАТЬ. Оригинал не обрывает победную тему переходом в меню:
* `while (check_sound_playing() && !key_test_quit())` (seg001:637),
* и лишь потом гасит экран. Клавишей прервать можно.
*
* У ОРИГИНАЛА между титрами и этим ожиданием стоит таблица рекордов
* с вводом имени, и тема играет сквозь неё. Наша таблица пока без
* ввода, поэтому ждём здесь; когда ввод появится — ожидание
* переедет за него (задача HOF-ENTRY). */
{
intro_skip_t tail;
intro_skip_begin(&tail);
while (pop_music_busy() && !intro_skip_requested(&tail)) {
pop_music_service(); /* поток дочитывается по ходу */
pop_wait_edge();
}
}
} else {
intro_run(ending_script);
}
pop_music_free();
pop_sfx_pause();
intro_restore_game_palette(); intro_restore_game_palette();
} }
+114
View File
@@ -62,6 +62,34 @@ static void slot_release(uint8_t n)
slot_blocks[n] = 0; 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 void pop_music_stop(void) __banked
{ {
pop_mus_left = 0; pop_mus_left = 0;
@@ -69,6 +97,7 @@ void pop_music_stop(void) __banked
void pop_music_free(void) __banked void pop_music_free(void) __banked
{ {
if (ring_on) pop_music_ring_stop();
if (ld_busy) { close(ld_fd); ld_busy = 0; } if (ld_busy) { close(ld_fd); ld_busy = 0; }
pop_mus_left = 0; pop_mus_left = 0;
slot_release(0); slot_release(0);
@@ -96,6 +125,7 @@ int8_t pop_music_load_begin(uint8_t id) __banked
char name[12]; char name[12];
uint8_t pages, n; uint8_t pages, n;
if (ring_on) pop_music_ring_stop(); /* потоковый трек и слоты не дружат */
if (ld_busy) load_abort(); if (ld_busy) load_abort();
if (id >= POP_MUS_COUNT) return -1; if (id >= POP_MUS_COUNT) return -1;
pages = pop_mus_tbl[id].pages; pages = pop_mus_tbl[id].pages;
@@ -174,6 +204,7 @@ int8_t pop_music_stream(uint8_t id) __banked
{ {
uint8_t i, n; 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_begin(id) != 0) return -1;
if (pop_music_load_step() < 0) return -1; /* первая страница */ if (pop_music_load_step() < 0) return -1; /* первая страница */
n = ld_slot; n = ld_slot;
@@ -197,6 +228,10 @@ void pop_music_play(void) __banked
uint8_t n, i; uint8_t n, i;
if (!mus_ready) return; if (!mus_ready) return;
/* СНЯТЬ КОЛЬЦО. Если до этого играл потоковый трек (финал), насос всё
* ещё заворачивал бы страницы по кругу шести обычный трек звучал бы
* своей первой третью без конца (поймано на титрах после победы). */
if (ring_on) pop_music_ring_stop();
n = (uint8_t)(mus_ready - 1); n = (uint8_t)(mus_ready - 1);
/* Насос обязан замолчать ДО подмены таблицы страниц: иначе он успеет /* Насос обязан замолчать ДО подмены таблицы страниц: иначе он успеет
* взять порцию по новому индексу из старого курсора. */ * взять порцию по новому индексу из старого курсора. */
@@ -215,6 +250,84 @@ void pop_music_play(void) __banked
pop_mus_left = slot_blocks[n]; 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 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) { if (!pop_mus_want || pop_app_state == POP_APP_DEMO) {
/* Выключили на ходу: оборвать поток и замолчать. Эффекты при этом /* Выключили на ходу: оборвать поток и замолчать. Эффекты при этом
* продолжают звучать у них свой тумблер. */ * продолжают звучать у них свой тумблер. */
+11
View File
@@ -34,6 +34,7 @@
#define POP_MUS_STORY_2 50 /* принцесса ждёт */ #define POP_MUS_STORY_2 50 /* принцесса ждёт */
#define POP_MUS_STORY_3 53 /* входит Джафар */ #define POP_MUS_STORY_3 53 /* входит Джафар */
#define POP_MUS_STORY_4 52 /* Джафар уходит */ #define POP_MUS_STORY_4 52 /* Джафар уходит */
#define POP_MUS_WON 56 /* победа: 115 с, только потоком */
/* Курсор проигрывания — читает НАСОС (pop_sfx.c, резидент), пишет /* Курсор проигрывания — читает НАСОС (pop_sfx.c, резидент), пишет
* загрузчик. Отдельный от эффектов: они звучат поверх музыки, а не * загрузчик. Отдельный от эффектов: они звучат поверх музыки, а не
@@ -45,6 +46,8 @@ extern volatile uint16_t pop_mus_left; /* порций до конца, 0
/* Номер играющего трека (нумерация оригинала). Нужен менеджеру звука: /* Номер играющего трека (нумерация оригинала). Нужен менеджеру звука:
* музыка участвует в таблицах приоритета наравне с эффектами. */ * музыка участвует в таблицах приоритета наравне с эффектами. */
extern uint8_t pop_mus_id; extern uint8_t pop_mus_id;
/* Размер кольца страниц для потокового трека; 0 — обычный режим. */
extern uint8_t pop_mus_ring;
/* ДВА СЛОТА. Заставочные сцены идут цепочкой треков (титры -> история -> /* ДВА СЛОТА. Заставочные сцены идут цепочкой треков (титры -> история ->
* PV из трёх реплик), и загрузка каждого это полсекунды-секунда диска. * PV из трёх реплик), и загрузка каждого это полсекунды-секунда диска.
@@ -88,6 +91,14 @@ void pop_music_stop(void) __banked;
* стрима. Всё чтение с диска живёт здесь. */ * стрима. Всё чтение с диска живёт здесь. */
void pop_music_service(void) __banked; void pop_music_service(void) __banked;
/* ПОТОКОВЫЙ ТРЕК ИЗ КОЛЬЦА СТРАНИЦ — для того, что в память не влезает.
* Финал `won` весит 1,2 МБ (78 страниц); играется из шести по кругу, а
* дочитывает его pop_music_service, пока трек звучит.
* 0 играет, -1 нет файла/памяти. Останавливает pop_music_ring_stop
* (её же зовёт pop_music_free). */
int8_t pop_music_ring_start(uint8_t id) __banked;
void pop_music_ring_stop(void) __banked;
/* Трек ещё звучит? 0 — доиграл или не запускался. */ /* Трек ещё звучит? 0 — доиграл или не запускался. */
uint8_t pop_music_busy(void) __banked; uint8_t pop_music_busy(void) __banked;
+3 -2
View File
@@ -12,9 +12,9 @@
#include <stdint.h> #include <stdint.h>
#define POP_MUS_COUNT 56 #define POP_MUS_COUNT 57
#define POP_MUS_BLOCK 128 #define POP_MUS_BLOCK 128
#define POP_MUS_MAX_PAGES 20 #define POP_MUS_MAX_PAGES 78
typedef struct { typedef struct {
uint8_t pages; /* 0 — трека нет */ uint8_t pages; /* 0 — трека нет */
@@ -78,6 +78,7 @@ static const pop_mus_ent_t pop_mus_tbl[POP_MUS_COUNT] = {
{ 11, 1403 }, /* 53 story_3_Jaffar_enters */ { 11, 1403 }, /* 53 story_3_Jaffar_enters */
{ 17, 2064 }, /* 54 intro_theme */ { 17, 2064 }, /* 54 intro_theme */
{ 11, 1350 }, /* 55 story_1_absence */ { 11, 1350 }, /* 55 story_1_absence */
{ 78, 9865 }, /* 56 won */
}; };
#endif /* POP_MUSIC_TBL_H */ #endif /* POP_MUSIC_TBL_H */
+16 -2
View File
@@ -55,6 +55,10 @@ uint8_t pop_mus_id;
/* Тик насоса: +1 на каждый отданный CBL блок, то есть 85,4 Гц. */ /* Тик насоса: +1 на каждый отданный CBL блок, то есть 85,4 Гц. */
volatile uint8_t pop_snd_tick; volatile uint8_t pop_snd_tick;
/* Размер КОЛЬЦА страниц для потоковой музыки, 0 — обычный трек целиком в
* памяти. Читает насос, ставит загрузчик (pop_music.c). */
uint8_t pop_mus_ring;
/* Заявка на музыкальный трек: номер в нумерации оригинала, 0 — нет заявки /* Заявка на музыкальный трек: номер в нумерации оригинала, 0 — нет заявки
* (нулевого трека не бывает). Ставит pop_sfx_play, снимает главный цикл. */ * (нулевого трека не бывает). Ставит pop_sfx_play, снимает главный цикл. */
uint8_t pop_mus_req; uint8_t pop_mus_req;
@@ -141,8 +145,18 @@ int pop_sfx_fill(uint16_t n)
pop_mus_ptr += n; pop_mus_ptr += n;
if (pop_mus_ptr >= 0x4000) { if (pop_mus_ptr >= 0x4000) {
pop_mus_ptr = 0; pop_mus_ptr = 0;
if ((uint8_t)(pop_mus_pg + 1) < POP_MUS_PAGES) pop_mus_pg++; /* КОЛЬЦО. Финальная тема — 1,2 МБ, в память целиком не влезает
else pop_mus_left = 0; * никак, поэтому она играется из нескольких страниц по кругу, а
* загрузчик дочитывает в те, что мы уже прошли (pop_music.c).
* Конец трека определяет left, а не номер страницы. */
if (pop_mus_ring) {
if ((uint8_t)(pop_mus_pg + 1) < pop_mus_ring) pop_mus_pg++;
else pop_mus_pg = 0;
} else if ((uint8_t)(pop_mus_pg + 1) < POP_MUS_PAGES) {
pop_mus_pg++;
} else {
pop_mus_left = 0;
}
} }
return 1; return 1;
} }