Музыка по ходу игры: смерть, зелья, меч, конец уровня, катсцены

Звуки 24..43 в наборе оцифровки пустые — в оригинале это Adlib-музыка, и
в digisnd её нет. На этом и построено подключение: pop_sfx_play для звука
с нулевой длиной кладёт номер в pop_mus_req, а разбирает заявку
pop_music_service() — один вызов на кадр из любого цикла (игра, интро,
катсцена). Всё чтение с диска живёт там.

Ждать полной загрузки джингла нельзя — это фриз посреди игры. Поэтому
pop_music_stream читает первую страницу (33 мс) и сразу пускает трек: она
звучит 1,5 с, а следующая читается те же 33 мс. Остальные доливаются по
одной за кадр.

Расставлено по местам оригинала: смерть (24/28/32), вступление первого
уровня и тень шестого (25), сцены перед уровнями (27/35/40), встреча с
Джафаром (29), зелья (30/33/39), время вышло (36), меч и смерть стража
(37), смерть Джафара (43), конец уровня (41/32), встреча с принцессой
(26). Темы «раз за уровень» сбрасывает pop_music_level_start.

Упакован 21 трек (2,4 МБ). Не озвучен только финал: won — 115 с и 78
страниц EMM, ему нужен кольцевой стриминг (задача MUS-WON на доске).

Проверено в MAME: вступительная тема первого уровня отыгрывается на
старте (курсор дошёл до конца, id 25). Host-тесты: 15 наборов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 21:52:37 +03:00
parent 65797af44c
commit 7e6b38cca6
33 changed files with 300 additions and 35 deletions
+7 -5
View File
@@ -207,12 +207,14 @@ SND_ARC := $(SND_DIR)/snd.arc
SND_DATA := $(SND_ARC)
# МУЗЫКА: записи DOS-версии, приведённые к формату CBL
# (toolchain/pop_pack_music.py). Сейчас озвучена вся заставка:
# 54 — тема титров, 55 — «In the absence», 50/53/52 — три реплики сцены
# с принцессой и Джафаром. Игровые джинглы (смерть, зелья) пойдут
# отдельной задачей: там музыка спорит с эффектами за единственный CBL.
# (toolchain/pop_pack_music.py). Номера — оригинала, чтобы игровой код
# просил трек тем же числом, каким SDLPoP зовёт play_sound.
# заставка 54 55 50 53 52
# катсцены 26 27 35 36 40
# игра 24 25 28 29 30 32 33 37 39 41 43
# Нет только 56 (won, 115 с / 1,2 МБ) — финалу нужен стриминг с диска.
MUS_DIR := $(CURDIR)/../poc/res/music
MUS_IDS := 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
MUS_DATA := $(foreach i,$(MUS_IDS),$(MUS_DIR)/m$(i).bin)
# UI: два исходных шрифта SDLPoP в одном EMM-атласе. Файл короче страницы:
+16
View File
@@ -1206,6 +1206,22 @@ N навык M» — отдельная надобность, если пона
Фазы З1..З5 и список «проверить артефактом до кодинга» — в доке.
### <a id="mus-won"></a>MUS-WON. Финальная тема — кольцевой стриминг с диска
Трек 56 (`won`, 115 с) — это 1,2 МБ, 78 страниц EMM. Ни в `POP_MUS_PAGES`
(20), ни в разумный бюджет памяти он не влезает, поэтому единственный
оставшийся неозвученным кусок игры — экран победы.
**Что нужно.** Кольцевой буфер: держать в EMM несколько страниц (8 = 128
КБ хватит с запасом — это 12 с звучания), а загрузчик дочитывает в те, что
насос уже прошёл. Курсор насоса (`pop_mus_pg`) при этом обязан
заворачиваться, а загрузчик — держать дистанцию от него и не обгонять на
круг. Чтение страницы 33 мс против 1,5 с её звучания — запаса вдоволь,
вся сложность в аккуратности индексов, а не в скорости.
**Заодно.** Тот же механизм снимает ограничение `POP_MUS_PAGES` со всех
треков: сейчас каждый должен помещаться в память целиком.
### <a id="arc-atlases"></a>ARC-ATLASES. Атласы — в архивы PBA1, как звук и музыка
**Что сделано.** Формат PBA1 и читатель готовы и работают на звуке:
+28 -6
View File
@@ -46,6 +46,18 @@
/* tile_is_floor (seg004): не-пол — пустота, ковёр, верх двери, стена и
* «половинки» 26..29. Дубликат крошечного switch'а из pop_map: тянуть его
* через границу банка дороже, чем повторить. */
/* Темы, которые звучат ОДИН раз за заход на уровень: тень шестого уровня
* (seg002:0090) и вступление первого. Оригинал отмечает «уже играли»
* через leveldoor_open = 0x4D, то есть портит переменную двери; у нас на
* это есть свои байты, а сбрасывает их старт уровня. */
static uint8_t shadow6_music;
void pop_music_level_start(void) __banked
{
shadow6_music = 0;
pop_ctrl_level_start();
}
static uint8_t tile_is_floor(uint8_t t)
{
switch (t) {
@@ -343,12 +355,16 @@ uint8_t pop_check_shadow(uint8_t room) __banked
return 1;
}
/* Уровень 6 (seg002:0090): условий на содержимое комнаты нет вовсе —
* тень встаёт при КАЖДОМ входе в комнату 1. Оригинал здесь ещё играет
* sound_25_presentation один раз, отмечая это `leveldoor_open = 0x4D`
* (переменная двери используется как флаг); звука у нас нет, а чужую
* переменную портить незачем — TODO вместе со звуком. */
* тень встаёт при КАЖДОМ входе в комнату 1. Оригинал играет здесь
* sound_25_presentation, помечая «уже играли» через leveldoor_open =
* 0x4D (переменную двери он использует как флаг). Чужую переменную не
* трогаем: у нас для этого свой однобайтовый флаг. */
if (pop_current_level == SHADOW_STEP_LEVEL) {
if (room != SHADOW_STEP_ROOM) return 0;
if (!shadow6_music) {
shadow6_music = 1;
pop_sfx_play(25);
}
do_init_shad(init_shad_6, room, SEQ_2_STAND);
return 1;
}
@@ -384,7 +400,6 @@ void pop_sword_disappears(void) __banked
* комнаты 3 и попадает прямо к Джафару. Всё событие — фора: пока
* pop_guard_notice_timer идёт, Джафар не встаёт в боевую стойку
* (autocontrol_guard_inactive), то есть у Кида есть ~2 секунды достать меч.
* В оригинале здесь же играет sound_29_meet_Jaffar — звука у нас нет.
*
* `pop_leveldoor_open == 0` в условии не косметика: вернувшись к Джафару
* после его смерти (дверь уже открыта), форы быть не должно.
@@ -396,6 +411,7 @@ void pop_meet_jaffar(void) __banked
if (pop_current_level != JAFFAR_LEVEL) return;
if (pop_leveldoor_open != 0) return;
if (Kid.room != JAFFAR_MEET_ROOM) return;
pop_sfx_play(29); /* тема встречи с Джафаром */
pop_guard_notice_timer = JAFFAR_NOTICE_TIME;
}
@@ -426,7 +442,13 @@ void pop_jaffar_exit(void) __banked
* Демо-уровень (0) сюда тоже не входит: его ветка — часть аттракта. */
static void on_guard_killed(void)
{
if (pop_current_level != JAFFAR_LEVEL) return;
if (pop_current_level != JAFFAR_LEVEL) {
/* seg006:1940 — победный трек за стража; за тень его нет: она не
* «убита», а слилась с Кидом. */
if (Char.charid != CHARID_1_SHADOW) pop_sfx_play(37);
return;
}
pop_sfx_play(43); /* смерть Джафара — свой трек */
pop_flash_color = POP_FLASH_WHITE;
pop_flash_time = JAFFAR_FLASH_TIME;
pop_leveldoor_open = 2;
+23
View File
@@ -22,6 +22,7 @@
#include "pop_kid.h"
#include "pop_sfx.h" /* звуковые эффекты (../docs/sound_plan.md) */
#include "pop_map.h"
#include "pop_level.h" /* pop_current_level — вступительная тема 1-го */
#include "pop_guard.h" /* charid, состояние меча, seq стража */
/* ---- CONTROL_* и seq/frame id (подмножество types.h) --------------- */
@@ -254,9 +255,24 @@ static void safe_step(void)
}
}
/* Вступительная тема уровня 1 играется один раз за заход на уровень;
* сбрасывает флаг pop_ctrl_level_start (её зовёт запуск уровня). */
static uint8_t lvl1_music;
void pop_ctrl_level_start(void) __banked { lvl1_music = 0; }
/* ---- control_* обработчики состояний ------------------------------ */
static void control_crouched(void)
{
/* «Presentation» (seg005:02EB): на первом уровне Кид начинает игру
* сидя, и пока он сидит, звучит вступительная тема. Оригинал ждёт
* тишины (`if (!check_sound_playing())`) — у нас достаточно одного
* запуска: заявку разберёт главный цикл, а насос сам придержит музыку,
* пока доигрывает эффект. */
if (pop_current_level == 1 && !lvl1_music) {
lvl1_music = 1;
pop_sfx_play(25);
}
/* Shift в приседе — поднять предмет (seg005 control_crouched). */
if (control_shift2 == CONTROL_HELD && get_item_action()) return;
if (control_y != CONTROL_HELD_DOWN) {
@@ -711,6 +727,13 @@ static void ctrl_kid_death(void)
if (Kid.alive < 0 && hitp_curr == 0) {
Kid.alive = 0;
pop_kid_dead = 1;
/* play_death_music (seg006:15B5): траурный трек зависит от того,
* КАК умер Кид — в бою, от руки тени или просто так. Оригинал ждёт
* шести тиков, чтобы доиграл звук удара; у нас ждать не нужно —
* насос держит курсор музыки, пока звучит эффект. */
if (Guard.charid == CHARID_1_SHADOW) pop_sfx_play(32);
else if (holding_sword) pop_sfx_play(28);
else pop_sfx_play(24);
}
}
+3
View File
@@ -71,4 +71,7 @@ extern int8_t control_forward, control_backward, control_up, control_down,
* своего тика напрямую. */
void pop_control(void) __banked;
/* Снять флаг «вступительная тема уровня 1 уже играла». */
void pop_ctrl_level_start(void) __banked;
#endif
+4
View File
@@ -332,6 +332,10 @@ int pop_guard_load(void) __banked;
* инверсии; зовётся из pop_vflip_load_all (pop_vflip.h). Живёт рядом с
* pop_guard_load: там же gp[] и выбор GUARD/SKEL. */
int pop_guard_vflip_load(void) __banked;
/* Сброс «одноразовых» музыкальных тем уровня (вступление 1-го, тень 6-го).
* Зовёт pop_start_level. */
void pop_music_level_start(void) __banked;
void pop_guard_free(void) __banked;
/* Вход в комнату: поднять стража из данных уровня (порт enter_guard,
+35 -1
View File
@@ -831,6 +831,7 @@ static int intro_pv_animated(void)
torch_right, sand_frame, flash,
render_tick);
pop_sfx_tick(); /* как в оригинале — в конце отрисовки кадра */
pop_music_service(); /* заявки на музыку из сцены (эффект без оцифровки) */
/* Страницу следующей реплики читаем ЗДЕСЬ, до досыпания кадра:
* 33 мс диска укладываются в паузу логического кадра (133 мс) и
* анимации не стоят. */
@@ -1252,6 +1253,8 @@ static uint8_t cut_run(uint16_t frames, uint8_t kid_visible)
gfx_set_visible_page(back);
for (hold = 1; hold < CUT_FRAME_VSYNC; hold++) gfx_wait_vsync();
pop_sfx_tick();
pop_music_service(); /* «встреча с принцессой» — трек, а не эффект */
cut_torch_l = (uint8_t)((cut_torch_l + 1) % 9);
cut_torch_r = (uint8_t)((cut_torch_r + 1) % 9);
cut_sand = (uint8_t)((cut_sand + 1) % 3);
@@ -1519,8 +1522,24 @@ void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked
const pop_cs_cmd_t *script = 0;
pop_sfx_pause();
/* Музыка сцены — как в оригинале: 27 для 2/4/6/12 (seg001:401),
* 35 для 8/9 (seg001:298), 40 для короткой «времени мало» (seg001:388).
* Экран в этот момент чёрный, поэтому читаем трек целиком: пауза на
* диск здесь никому не мешает. */
{
uint8_t track = 27;
if (scene == POP_PRE_8 || scene == POP_PRE_9) track = 35;
else if (scene == POP_PRE_12_SHORT) track = 40;
if (pop_music_load(track) == 0) {
(void)pop_sfx_start();
pop_music_play();
}
}
if (scene == POP_PRE_2_6 || scene == POP_PRE_4 || scene == POP_PRE_12) {
if (pre_princess_animated(scene) >= 0) {
pop_music_free();
pop_sfx_pause();
intro_restore_game_palette();
return;
}
@@ -1530,6 +1549,8 @@ void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked
if (scene == POP_PRE_8 || scene == POP_PRE_9) {
int done = (scene == POP_PRE_8) ? cut_scene_8() : cut_scene_9();
if (done == 0) {
pop_music_free();
pop_sfx_pause();
intro_restore_game_palette();
return;
}
@@ -1539,16 +1560,29 @@ void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked
else if (scene == POP_PRE_8) script = pre_8;
else if (scene == POP_PRE_9) script = pre_9;
else if (scene == POP_PRE_12_SHORT) script = pre_12_short;
if (!script) return;
if (!script) {
pop_music_free();
pop_sfx_pause();
return;
}
intro_run(script);
pop_music_free();
pop_sfx_pause();
intro_restore_game_palette();
}
void pop_time_expired_show(void) __banked
{
pop_sfx_pause();
/* time_expired (seg001:365) начинается с play_sound(sound_36). */
if (pop_music_load(36) == 0) {
(void)pop_sfx_start();
pop_music_play();
}
intro_run(time_expired_script);
pop_music_free();
pop_sfx_pause();
intro_restore_game_palette();
}
+8 -2
View File
@@ -345,8 +345,7 @@ void play_seq(void)
* 2 BUMP звук 8, и шум;
* 3 DRINK звук 18, не шум;
* 4 LEVEL МУЗЫКА конца уровня (32 на 4-м, иначе 41,
* на 13/15 молчок). Оцифровки у неё нет,
* это путь A на AY пока тишина.
* на 13/15 молчок).
* Второй смысл опкода («Кид нашумел») терять нельзя даже
* без звука, см. BUG-GUARD-DEAF-1. */
{
@@ -356,6 +355,13 @@ void play_seq(void)
if (ev == 1) pop_sfx_play(23);
else if (ev == 2) pop_sfx_play(8);
else if (ev == 3) pop_sfx_play(18);
else if (ev == 4) {
/* seg006:646. На четвёртом уровне уход за дверь — это
* встреча с тенью, и трек там свой; на 13-м (Джафар) и
* 15-м оригинал молчит. */
if (pop_current_level == 4) pop_sfx_play(32);
else if (pop_current_level != 13) pop_sfx_play(41);
}
}
break;
case 0xF1: /* END_LEVEL: seq_70 доиграл — уровень пройден.
+7
View File
@@ -1330,6 +1330,7 @@ void pop_proc_get_object(void) __banked
if (Char.charid != CHARID_0_KID) return;
if (pickup_obj_type == -1) {
pop_have_sword = 1; /* в оригинале have_sword = -1 */
pop_sfx_play(37); /* победный трек «меч найден» */
/* Вспышка фона: flash_color = color_14_brightyellow, flash_time = 8
* (seg006 proc_get_object). do_flash = set_bg_attr(0, color)
* оригинал подменяет НУЛЕВУЮ запись палитры, т.е. вспыхивает всё
@@ -1343,12 +1344,16 @@ void pop_proc_get_object(void) __banked
switch (pickup_obj_type) {
case 1: /* здоровье: +1 HP, красная вспышка */
if (hitp_curr != hitp_max) {
pop_sfx_stop(); /* seg006:1871 — stop_sounds */
pop_sfx_play(33);
hitp_delta = 1;
pop_flash_color = POP_FLASH_RED;
pop_flash_time = 2;
}
break;
case 2: /* большая склянка: +1 к ПОТОЛКУ HP */
pop_sfx_stop(); /* seg006:1878 — stop_sounds */
pop_sfx_play(30);
pop_kid_add_life();
pop_flash_color = POP_FLASH_RED;
pop_flash_time = 4; /* seg006:1878 — вспышка длиннее */
@@ -1359,6 +1364,8 @@ void pop_proc_get_object(void) __banked
* и играет sound_39_low_weight по нему же меряет срок; у нас
* срок задан кадрами (POP_FEATHER_FRAMES). */
pop_feather = POP_FEATHER_FRAMES;
pop_sfx_stop();
pop_sfx_play(39); /* «низкий вес» — весь эффект */
pop_flash_color = POP_FLASH_GREEN;
pop_flash_time = 3;
break;
+47
View File
@@ -159,6 +159,38 @@ int8_t pop_music_load(uint8_t id) __banked
return (r < 0) ? -1 : 0;
}
/* ЗАПУСК С ПОДКАЧКОЙ НА ХОДУ — для игровых джинглов (смерть, зелья, меч).
* Ждать полной загрузки нельзя: это фриз на треть секунды посреди игры.
* Зато можно начать сразу после ПЕРВОЙ страницы: она звучит 1,5 с, а
* следующая читается 33 мс запас сорокакратный. Дальше вызывающий
* доливает по странице за кадр (pop_music_load_step, pop_music_loading).
*
* Номера страниц известны сразу после mem_alloc_pages, поэтому таблицу для
* насоса заполняем целиком: данные в этих страницах появятся раньше, чем
* он до них доберётся.
* 0 играет, -1 трека нет или память кончилась. */
int8_t pop_music_stream(uint8_t id) __banked
{
uint8_t i, n;
if (pop_music_load_begin(id) != 0) return -1;
if (pop_music_load_step() < 0) return -1; /* первая страница */
n = ld_slot;
pop_mus_left = 0; /* насос — на тишину */
for (i = 0; i < ld_pages; i++)
pop_mus_page[i] = mem_get_page(slot_blk[n], i);
if (n != mus_cur) slot_release(mus_cur);
mus_cur = n;
slot_pages[n] = ld_pages;
slot_blocks[n] = pop_mus_tbl[id].blocks;
slot_id[n] = id;
pop_mus_id = id;
pop_mus_pg = 0;
pop_mus_ptr = 0;
pop_mus_left = slot_blocks[n];
return 0;
}
void pop_music_play(void) __banked
{
uint8_t n, i;
@@ -182,6 +214,21 @@ void pop_music_play(void) __banked
pop_mus_left = slot_blocks[n];
}
void pop_music_service(void) __banked
{
/* Один вызов на кадр из ЛЮБОГО цикла — игрового, интро, катсцены.
* Заявку кладёт pop_sfx_play (звук без оцифровки = музыка оригинала),
* а диск трогаем только здесь. */
if (pop_mus_req) {
uint8_t id = pop_mus_req;
pop_mus_req = 0;
(void)pop_music_stream(id);
} else if (ld_busy) {
(void)pop_music_load_step();
}
}
uint8_t pop_music_busy(void) __banked
{
return (uint8_t)(pop_mus_left != 0);
+11
View File
@@ -72,11 +72,22 @@ uint8_t pop_music_loading(void) __banked;
/* Отпустить ОБА слота. Останавливает проигрывание, если оно шло. */
void pop_music_free(void) __banked;
/* Пустить трек СРАЗУ, дочитывая его по ходу звучания: первая страница
* читается здесь (33 мс), остальные вызывающим, по одной за кадр, пока
* pop_music_loading(). Для игровых джинглов, где паузы на диск быть не
* может. 0 играет, -1 трека нет либо не хватило памяти. */
int8_t pop_music_stream(uint8_t id) __banked;
/* Пустить последний загруженный трек с начала; слот предыдущего при этом
* освобождается. Без парного load ничего не делает. */
void pop_music_play(void) __banked;
void pop_music_stop(void) __banked;
/* ОБСЛУЖИВАНИЕ МУЗЫКИ — один вызов на кадр из любого цикла (игра, интро,
* катсцена): разбирает заявку pop_mus_req и доливает страницы начатого
* стрима. Всё чтение с диска живёт здесь. */
void pop_music_service(void) __banked;
/* Трек ещё звучит? 0 — доиграл или не запускался. */
uint8_t pop_music_busy(void) __banked;
+16 -16
View File
@@ -46,26 +46,26 @@ static const pop_mus_ent_t pop_mus_tbl[POP_MUS_COUNT] = {
{ 0, 0 }, /* 21 */
{ 0, 0 }, /* 22 */
{ 0, 0 }, /* 23 */
{ 0, 0 }, /* 24 */
{ 0, 0 }, /* 25 */
{ 0, 0 }, /* 26 */
{ 0, 0 }, /* 27 */
{ 0, 0 }, /* 28 */
{ 0, 0 }, /* 29 */
{ 0, 0 }, /* 30 */
{ 3, 305 }, /* 24 regular_death */
{ 2, 196 }, /* 25 find_mirror_and_find_shadow */
{ 9, 1132 }, /* 26 embrace */
{ 8, 915 }, /* 27 cutscene_before_2_4_6_12 */
{ 6, 656 }, /* 28 fight_death */
{ 4, 428 }, /* 29 meet_Jaffar */
{ 3, 299 }, /* 30 life_potion */
{ 0, 0 }, /* 31 */
{ 0, 0 }, /* 32 */
{ 0, 0 }, /* 33 */
{ 5, 624 }, /* 32 end_level_4_and_killed_by_shadow */
{ 2, 205 }, /* 33 potion */
{ 0, 0 }, /* 34 */
{ 0, 0 }, /* 35 */
{ 0, 0 }, /* 36 */
{ 0, 0 }, /* 37 */
{ 7, 888 }, /* 35 cutscene_before_8_9 */
{ 10, 1256 }, /* 36 out_of_time */
{ 3, 280 }, /* 37 guard_death_and_obtaining_the_sword */
{ 0, 0 }, /* 38 */
{ 0, 0 }, /* 39 */
{ 0, 0 }, /* 40 */
{ 0, 0 }, /* 41 */
{ 13, 1591 }, /* 39 floating */
{ 5, 596 }, /* 40 cutscene_not_much_time_left */
{ 9, 1043 }, /* 41 story_5_level_end_and_merge_with_shadow */
{ 0, 0 }, /* 42 */
{ 0, 0 }, /* 43 */
{ 3, 285 }, /* 43 killed_Jaffar */
{ 0, 0 }, /* 44 */
{ 0, 0 }, /* 45 */
{ 0, 0 }, /* 46 */
+15 -4
View File
@@ -55,6 +55,10 @@ uint8_t pop_mus_id;
/* Тик насоса: +1 на каждый отданный CBL блок, то есть 85,4 Гц. */
volatile uint8_t pop_snd_tick;
/* Заявка на музыкальный трек: номер в нумерации оригинала, 0 — нет заявки
* (нулевого трека не бывает). Ставит pop_sfx_play, снимает главный цикл. */
uint8_t pop_mus_req;
static volatile uint8_t sfx_pg; /* индекс в pop_snd_page[] */
static volatile uint16_t sfx_ptr; /* адрес в окне W0: 0x0000..0x3FFF */
static volatile uint16_t sfx_left; /* сколько байт осталось (кратно 128) */
@@ -225,10 +229,17 @@ static uint8_t snd_interruptible(uint8_t id)
void pop_sfx_play(uint8_t id)
{
if (!pop_snd_ok || id >= POP_SND_COUNT) return;
/* Нет оцифровки (музыка) — не номинируем вовсе: порт проверки
* `if (NULL == sound_pointers[id]) return;`. Иначе музыкальный id
* выигрывал бы конкурс и глушил живой эффект. */
if (pop_snd_tbl[id].len == 0) return;
/* НЕТ ОЦИФРОВКИ — ЗНАЧИТ ЭТО МУЗЫКА. В оригинале звуки 24..43 играет
* Adlib по MIDI, и в digisnd их нет вовсе; у нас на их месте записи
* DOS-версии, которые надо читать с диска. В прерывании и в горячем
* коде этого делать нельзя, поэтому здесь только ЗАЯВКА одним байтом
* её разбирает главный цикл (pop_music_stream, банк 9).
* В конкурсе эффектов такой id не участвует: он не глушит живой звук
* (порт проверки `if (NULL == sound_pointers[id]) return;`). */
if (pop_snd_tbl[id].len == 0) {
pop_mus_req = id;
return;
}
if (!snd_next1 || snd_prio[id] <= snd_prio[snd_next1 - 1])
snd_next1 = (uint8_t)(id + 1);
}
+6
View File
@@ -59,6 +59,12 @@ extern uint8_t pop_snd_want;
* запасной путь по кадрам луча. */
extern volatile uint8_t pop_snd_tick;
/* ЗАЯВКА НА МУЗЫКУ. pop_sfx_play(id) для звука без оцифровки (24..43 —
* в оригинале это Adlib-музыка) кладёт сюда номер трека, а разбирает
* заявку главный цикл: чтение с диска ни в прерывании, ни в горячем коде
* недопустимо. 0 заявки нет. Снимать обязан тот, кто обработал. */
extern uint8_t pop_mus_req;
/* Заявить эффект с номером id (0..56 — нумерация оригинала, см.
* sound_plan.md). НЕ запускает его немедленно и НЕ перебивает играющий:
* это порт play_sound (seg000:12C5), который лишь НОМИНИРУЕТ кандидата на
+7 -1
View File
@@ -18,7 +18,8 @@
#include "pop_trob.h"
#include "pop_redraw.h"
#include "pop_geom.h" /* pop_y_to_row, TILE_SIZEX — тайл объекта */
#include "pop_sfx.h" /* pop_sfx_tick — решение о запуске звука за кадр */
#include "pop_sfx.h"
#include "pop_music.h" /* pop_sfx_tick — решение о запуске звука за кадр */
#include "pop_guard.h"
#include "pop_cdraw.h" /* общая отрисовка персонажей (слоты Кид/соперник) */
#include "pop_cheat.h"
@@ -995,6 +996,11 @@ int main(void)
* кадра, seg000:954: все заявки кадра уже поданы, и из них выбирается
* важнейшая. Раньше по кадру нельзя потеряются поздние заявки. */
pop_sfx_tick();
/* МУЗЫКА ПО ХОДУ ИГРЫ. Заявку положил pop_sfx_play (звук без
* оцифровки это Adlib-трек оригинала), а диск трогаем здесь:
* первая страница читается сразу и трек уже звучит, остальные
* доливаются по одной за кадр 33 мс на кадр, пока догоняем. */
pop_music_service();
/* Обе служебные полосы — нижняя статус-строка (сообщения оригинала)
* и верхняя отладочная. Всё change-driven и целиком в банке 9,
@@ -94,6 +94,7 @@ void pop_start_level(void) __banked
int8_t dir;
pop_leveldoor_open = 0; /* seg003:97 — дверь прошлого уровня не в счёт */
pop_music_level_start(); /* одноразовые темы уровня — заново */
pop_feather = 0; /* seg003:189 — перо не переживает старт уровня */
/* upside_down тоже сбрасывается стартом уровня (seg003:38/188). Если
* картинка была перевёрнута, отрисовка комнаты ниже уже пойдёт обычной