Музыка по ходу игры: смерть, зелья, меч, конец уровня, катсцены
Звуки 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:
@@ -845,3 +845,46 @@ EMM: `pop_music_load*` всегда пишет в НЕ играющий, `pop_mu
|
||||
курсор `pop_mus_id` меняется ровно на своих кадрах, интро доходит до
|
||||
демо-режима. Слуховая проверка (нет ли хрипа от диска при играющей
|
||||
музыке) — за пользователем.
|
||||
|
||||
## 7. МУЗЫКА ПО ХОДУ ИГРЫ (2026-08-26)
|
||||
|
||||
Звуки 24..43 в наборе оцифровки ПУСТЫЕ — в оригинале это Adlib-музыка, и
|
||||
в digisnd её нет вовсе. На этом и построено подключение: `pop_sfx_play`
|
||||
для звука с нулевой длиной не пытается его играть, а кладёт НОМЕР в
|
||||
`pop_mus_req` (один байт). Заявку разбирает `pop_music_service()` — один
|
||||
вызов на кадр из любого цикла (игрового, интро, катсцены); всё чтение с
|
||||
диска живёт там.
|
||||
|
||||
**Стриминг вместо загрузки.** Ждать полной загрузки джингла нельзя — это
|
||||
фриз на треть секунды посреди игры. `pop_music_stream` читает ПЕРВУЮ
|
||||
страницу (33 мс) и сразу пускает трек: она звучит 1,5 с, а следующая
|
||||
читается те же 33 мс — запас сорокакратный. Остальные доливаются по
|
||||
одной за кадр, пока `pop_music_loading()`. Номера страниц известны сразу
|
||||
после `mem_alloc_pages`, поэтому таблица для насоса заполняется целиком —
|
||||
данные появятся раньше, чем насос до них дойдёт.
|
||||
|
||||
**Что и где играет** (номера и места — из SDLPoP):
|
||||
|
||||
| трек | событие | место у нас |
|
||||
|------|---------|-------------|
|
||||
| 24 / 28 / 32 | смерть: обычная / в бою / от руки тени | `ctrl_kid_death` |
|
||||
| 25 | вступление 1-го уровня (Кид сидит), тень 6-го | `control_crouched`, `guards.c` |
|
||||
| 27 / 35 / 40 | сцены перед 2/4/6/12, 8/9, «времени мало» | `pop_pre_cutscene_show` |
|
||||
| 29 | встреча с Джафаром | `pop_meet_jaffar` |
|
||||
| 30 / 33 | большая склянка / малая | `pop_proc_get_object` |
|
||||
| 36 | время вышло | `pop_time_expired_show` |
|
||||
| 37 / 43 | меч найден, страж убит / смерть Джафара | `pop_proc_get_object`, `on_guard_killed` |
|
||||
| 39 | перо (медленное падение) | `pop_proc_get_object` |
|
||||
| 41 / 32 | конец уровня / конец 4-го (тень) | опкод SND_LEVEL в `play_seq` |
|
||||
| 26 | встреча с принцессой | `cut_ending` |
|
||||
|
||||
Темы, которые звучат один раз за заход на уровень (вступление 1-го, тень
|
||||
6-го), сбрасывает `pop_music_level_start()` из `pop_start_level`. Оригинал
|
||||
для этого портит переменную двери (`leveldoor_open = 0x4D`) — у нас на это
|
||||
есть свои два байта.
|
||||
|
||||
**Чего пока нет.** Финальный `won` (56): 115 с, 1,2 МБ, 78 страниц EMM —
|
||||
в `POP_MUS_PAGES` (20) он не помещается даже теоретически. Ему нужен
|
||||
КОЛЬЦЕВОЙ стриминг: держать в памяти несколько страниц и дочитывать в те,
|
||||
что насос уже прошёл. Это отдельная задача — курсор насоса придётся
|
||||
научить заворачиваться, а загрузчик — держать дистанцию от него.
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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