Звуковые эффекты PoP: оцифровка оригинала через CBL

Набор digisnd1..3.dat приведён упаковщиком к 10 937,5 Гц (частота
железа), склеен в 8 EMM-страниц с выравниванием каждого звука на 128 —
размер блока запроса CBL, поэтому ни один блок не пересекает границу
страницы и проигрыватель не знает слова «стык».

Насос (pop_sfx.c) резидентный: его зовут из прерывания CBL, из горячих
мест физики и из play_seq.  Тишину льём свою (первый блок набора), а не
через CBL_UNDERRUN_SILENCE с его malloc — куча в резиденте W2 тесная.
Открытие CBL разведено с загрузкой (pop_sfx_start отдельно от
pop_sfx_init): пока ESTEX читает файлы, насос не успевает долить блок и
железо крутит хвост буфера — на слух скрежет.

Разведены все места play_sound() SDLPoP, у которых есть оцифровка
(id 0..23, 44..49): посадки, падение, удары о стену, зацеп, тряска и
провал плит, ворота, дверь уровня, пики, чомпер, кнопки, боёвка, меч,
зеркало, скелет, зелье.  Таблица соответствий — docs/sound_plan.md §8.
Музыкальные id остаются с нулевой длиной до фазы музыки.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 15:16:43 +03:00
parent 24ced6058f
commit 8ea4c32e51
25 changed files with 561 additions and 137 deletions
+73
View File
@@ -24,6 +24,7 @@
#include "pop_tune.h"
#include "pop_ctrl.h" /* pop_ctrl_init/close — ввод поднимает и закрывает boot */
#include "pop_shadow.h" /* запечённый набор Тени */
#include "pop_sfx.h" /* звуковые эффекты через CBL */
#include "pop_vflip.h" /* сброс зеркальных страниц на смене уровня */
#include "pop_cheat.h" /* pop_cheats — режим разработки включает pop_boot */
#include "pop_pace.h" /* pop_speed_mode — режим скорости в отладочной метке */
@@ -308,6 +309,74 @@ uint8_t pop_dbg_roomnav(uint8_t dir) __banked
return 1;
}
/* ---- ЧИТЫ (seg000: switch под if (cheats_enabled)) -------------------- *
* Переехали из главного цикла в банк 8: дёргаются раз в кадр, то есть
* один трамплин на кадр, а места в резиденте W1/W2 не осталось (звук,
* атлас Тени и пейсинг съели остаток; см. TASKS_OPEN, MEM).
*
* Флаги «фронта» стали статиками модуля — раньше они были локалями main.
* Смысл прежний: одно нажатие = одно срабатывание.
*/
static uint8_t cheat_prev, immo_prev, sword_prev, nlvl_prev;
static uint8_t upsd_prev, xdec_prev, xinc_prev;
void pop_cheat_tick(void) __banked
{
/* Читы (seg000: switch под if (cheats_enabled)). Все — по
* ФРОНТУ нажатия, иначе срабатывали бы каждый кадр.
* K — убить стража;
* I — бессмертие Кида (наш отладочный, в оригинале его нет:
* иначе боёвку проверять получается ровно три раза);
* S — выдать меч, не бегая за ним в комнату 15. Штатно меч
* берётся с пола (pop_have_sword ставит подбор);
* Shift+L — следующий уровень (seg000:698, ветка next_level =
* current_level + 1). Единственный вменяемый способ
* тестировать уровни 2..15. NB: L без Shift — отладочный
* «осторожный шаг вправо» (pop_ctrl KBD_DBG_STEPR); с
* Shift шаг тоже пройдёт, но уровень тут же сменится, так
* что конфликта на практике нет;
* [ / ] — подвинуть Кида на ПИКСЕЛЬ влево/вправо (seg000:1828,
* `--Char.x` / `++Char.x`). Нужен потому, что мост MAME
* теряет нажатия при быстрой отправке, и подогнать Кида в
* нужную позу скриптом нельзя (на это упёрлись BUG-LOOSE-2
* и BUG-GATE-PASS-1). Пишем прямо в Kid.x: геометрию
* персонажа в этом порте задаёт не диспетчер (грабли
* L1-EXIT). Колонку не трогаем — её пересчитает физика
* следующего кадра. */
uint8_t k = kbd_raw_down(KBD_CHEAT_KILL);
uint8_t i = kbd_raw_down(KBD_CHEAT_IMMO);
uint8_t s = kbd_raw_down(KBD_CHEAT_SWORD);
uint8_t nl = (uint8_t)(kbd_raw_down(KBD_CHEAT_NEXTLVL) &&
(kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT)));
uint8_t up = kbd_raw_down(KBD_CHEAT_UPSIDE);
uint8_t xd = kbd_raw_down(KBD_CHEAT_XDEC);
uint8_t xi = kbd_raw_down(KBD_CHEAT_XINC);
if (pop_cheats && k && !cheat_prev) pop_guard_kill();
/* Бессмертие по кругу: 0 выкл -> 1 «только бой» -> 2 «бой и
* мелкий урон» -> 0. Что означают уровни — pop_state.h. */
if (pop_cheats && i && !immo_prev)
pop_immortal = (uint8_t)(pop_immortal >= 2 ? 0 : pop_immortal + 1);
if (pop_cheats && s && !sword_prev) pop_have_sword = 1;
if (pop_cheats && nl && !nlvl_prev && pop_current_level < POP_LAST_LEVEL)
pop_next_level = (uint8_t)(pop_current_level + 1);
/* U — переворот экрана (seg000:0793 — чит и в оригинале).
* Тот же путь, что у зелья типа 4: флаг + пометка «перевернулись». */
if (pop_cheats && up && !upsd_prev) { /* тот же путь, что у зелья */
pop_upside_want = (uint8_t)!pop_upside_want;
pop_upside_dirty = 1;
}
if (pop_cheats && xd && !xdec_prev) Kid.x = (uint8_t)(Kid.x - 1);
if (pop_cheats && xi && !xinc_prev) Kid.x = (uint8_t)(Kid.x + 1);
cheat_prev = k;
immo_prev = i;
sword_prev = s;
nlvl_prev = nl;
upsd_prev = up;
xdec_prev = xd;
xinc_prev = xi;
}
/* ---- ОТЛАДКА: состояние прогона в верхнем борте ---------------------- *
*
* Четыре блока ПАЛОЧКАМИ, каждый своим цветом (outtextxy не годится: текст
@@ -766,6 +835,7 @@ int pop_boot(void) __banked
pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
pop_shadow_load(); /* запечённый набор Тени — один раз на запуск */
pop_sfx_init(); /* звук: 8 страниц оцифровки (вывод пока молчит) */
if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */
puts("kid atlas load failed");
return -1;
@@ -830,6 +900,7 @@ int pop_level_switch(void) __banked
* дальше, а урон — нет (do_startpos дольёт до потолка). */
hitp_beg_lev = hitp_max;
pop_checkpoint = 0; /* уровень пройден (seg003:413) */
pop_sfx_pause(); /* загрузка уровня — ESTEX надолго, звук глушим */
if (pop_level_load_num(pop_next_level) != 0) {
pop_next_level = pop_current_level; /* файла нет — остаёмся */
} else {
@@ -861,6 +932,7 @@ int pop_level_switch(void) __banked
* и делать это в момент глотка нельзя. Звать можно откуда
* угодно — функция идемпотентна. */
if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all();
pop_sfx_start(); /* ...и обратно, когда всё прочитано */
}
return 0;
}
@@ -872,6 +944,7 @@ void pop_shutdown(void) __banked
closegraph();
pop_bg_free();
pop_kid_free();
pop_sfx_close();
pop_shadow_free();
pop_guard_free();
pop_kid_data_free();