/* * pop_sfx.h — звуковые ЭФФЕКТЫ PoP через COVOX-Blaster. * * Данные — оригинальная оцифровка игры (`digisnd1..3.dat`), приведённая * упаковщиком к 10 937,5 Гц. Конвертировать в рантайме нечего: у PoP * сэмплы 8 бит БЕЗЗНАКОВЫЕ МОНО, и это ровно формат CBL (см. и * ../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 /* Поднять набор: занять 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