Files
Sprinter-SDCC/applications/PoP/roomtest/pop_sfx.h
T
snark13 7e6b38cca6 Музыка по ходу игры: смерть, зелья, меч, конец уровня, катсцены
Звуки 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>
2026-08-25 21:52:37 +03:00

93 lines
6.3 KiB
C

/*
* pop_sfx.h — звуковые ЭФФЕКТЫ PoP через COVOX-Blaster.
*
* Данные — оригинальная оцифровка игры (`digisnd1..3.dat`), приведённая
* упаковщиком к 10 937,5 Гц. Конвертировать в рантайме нечего: у PoP
* сэмплы 8 бит БЕЗЗНАКОВЫЕ МОНО, и это ровно формат CBL (см. <cbl.h> и
* ../docs/sound_plan.md).
*
* ОДИН ГОЛОС. Оригинал тоже одноголосый: `play_sound` глушит
* предыдущий звук (`stop_sounds`, seg009.c), поэтому новый эффект просто
* перебивает играющий, и никакого микшера не нужно.
*
* ПОЧЕМУ ЭТО НЕ ЛОМАЕТ КАДР. Насос живёт в прерывании CBL и отдаёт 128
* байт раз в ~11,7 мс. Кадровые прерывания при этом теряются (ветка CBL
* в трамплине делает приватный RETI), но нам всё равно: темп кадра
* считается ПО ЛУЧУ (pop_pace.h), а не по прерываниям. Если бы пейсинг
* остался на них — звук сломал бы игру.
*/
#ifndef POP_SFX_H
#define POP_SFX_H
#include <stdint.h>
/* Поднять набор: занять 8 EMM-страниц и вычитать в них данные. ВЫВОД НЕ
* ВКЛЮЧАЕТ — см. pop_sfx_start. 0 — OK, -1 — не получилось (тогда всё
* дальнейшее молча ничего не делает, игра идёт без звука). */
int pop_sfx_init(void) __banked;
/* Включить вывод (открыть CBL). Звать ПОСЛЕ всей загрузки: пока идёт
* чтение файлов, ESTEX уходит в диск надолго, насос не успевает долить
* блок, и железо крутит хвост своего буфера — на слух сплошной скрежет.
* Идемпотентно. */
int pop_sfx_start(void) __banked;
/* Заглушить вывод на время загрузки (закрывает CBL). Парная к start. */
void pop_sfx_pause(void) __banked;
/* Закрыть CBL и отпустить страницы; звать на выходе. */
void pop_sfx_close(void) __banked;
/* Вкл/выкл звук по воле пользователя (Ctrl+S, порт SDLPoP seg000:657).
* Правит pop_snd_want и приводит вывод в соответствие. */
void pop_sfx_toggle(void) __banked;
/* ХОЧЕТ ЛИ ЗВУКА ПОЛЬЗОВАТЕЛЬ. Не то же самое, что «CBL открыт»: вывод
* ещё гасит служебная пауза на время загрузки уровня. Читать для
* индикации; писать — только через pop_sfx_toggle. 0 и в случае, когда
* набор вообще не поднялся (нет файлов, нет страниц) — то есть флаг
* честно means «звука не будет». */
extern uint8_t pop_snd_want;
/* ЧАСЫ ДЛЯ СЦЕН: +1 на каждый блок, отданный CBL, то есть ровно 85,4 Гц
* (10 937,5 / 128), пока звук открыт. Идут от расхода буфера железом, а
* не от нашего кода, поэтому не врут ни на долгих блитах, ни на чтении с
* диска — в отличие от счёта ожиданий vsync (тот считает НАШИ ожидания) и
* от кадровых прерываний (те теряются в di-окнах акселератора, pop_pace.h).
* Переполняется каждые 3 с — разности брать в uint8_t и накапливать.
* Когда CBL закрыт (звук выключён), НЕ ИДУТ: у пейсинга обязан быть
* запасной путь по кадрам луча. */
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), который лишь НОМИНИРУЕТ кандидата на
* текущий кадр. Из нескольких заявок за кадр выживает важнейшая, а решение
* «запускать или нет» принимает pop_sfx_tick. Звуки без оцифровки (музыка)
* игнорируются.
*
* НЕ банковая и НЕ __banked: зовётся из горячих мест (play_seq в
* pop_kid.c, физика, боёвка), и трамплин на каждый шаг Кида не нужен. */
void pop_sfx_play(uint8_t id);
/* Раз в кадр: запустить номинанта, если можно (порт play_next_sound,
* seg000:1304). Оригинал зовёт его в конце отрисовки кадра — зовём там же.
* Не запустили — номинант ВЫБРАСЫВАЕТСЯ, очереди у оригинала нет. */
void pop_sfx_tick(void);
/* Перебиваемость съезжающей створки двери уровня (звук 15) — единственная
* запись таблицы, которую оригинал правит на ходу (seg007:442/464):
* 1 при закрытии двери, 0 при открытии. */
extern uint8_t pop_sfx_slide_intr;
/* Оборвать текущий эффект (порт stop_sounds). */
void pop_sfx_stop(void);
#endif