Музыка по ходу игры: смерть, зелья, меч, конец уровня, катсцены
Звуки 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:
@@ -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-атласе. Файл короче страницы:
|
||||
|
||||
@@ -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 и читатель готовы и работают на звуке:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 доиграл — уровень пройден.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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), который лишь НОМИНИРУЕТ кандидата на
|
||||
|
||||
@@ -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). Если
|
||||
* картинка была перевёрнута, отрисовка комнаты ниже уже пойдёт обычной
|
||||
|
||||
Reference in New Issue
Block a user