Files
Sprinter-SDCC/applications/PoP/roomtest/pop_sfx.c
T
snark13 e0c86a96ef Ctrl+S — вкл/выкл звук; чит «выдать меч» убран
Порт Ctrl+S из SDLPoP (seg000:657).  Выключение реально закрывает CBL, а
не глушит сэмпл: иначе насос продолжал бы отдавать блоки тишины и платить
те же 3,26 % процессорного времени.

Флаг намерения pop_snd_want отдельно от pop_snd_ok: последний гасит
служебная пауза на время загрузки уровня, и правь Ctrl+S только его —
первая же смена уровня вернула бы выключенный звук.

Индикация: пурпурная палочка в борте, когда звука НЕТ (включённый слышно
и так, а молчание неотличимо от «нечему звучать»).

Чит S «выдать меч» был отладочным и больше не нужен — снят, клавиша ушла
под звук.

Замер цены звука — sound_plan.md §9: 8 001 такт на прерывание при периоде
245 759 (3,26 % времени), +2,2 % к работе кадра в сцене 11/15, период
кадра не сдвинулся ни разу.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 15:49:33 +03:00

134 lines
7.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* pop_sfx.c — ГОРЯЧАЯ часть звуковых эффектов: насос в прерывании CBL,
* запуск/останов и таблица звуков. Зачем всё это и в каком виде лежат
* данные — в шапке pop_sfx.h и ../docs/sound_plan.md.
*
* ПОЧЕМУ МОДУЛЬ РЕЗИДЕНТНЫЙ (в EXTRA_SRCS, а не в банке). Три причины,
* и каждой хватило бы одной:
* - callback насоса зовётся ИЗ ПРЕРЫВАНИЯ; трамплин перед вызовом
* ставит базовую страницу W1, значит хендлер обязан быть в базе;
* - pop_sfx_play дёргается из горячих мест (play_seq на каждый шаг
* Кида, физика, боёвка) — трамплин там был бы чистой потерей;
* - pop_page_read маппит W3 на время чтения, а банковый код сам живёт
* в W3 и вырезал бы себя из-под ног (та же причина, по которой
* резидентен pop_level_read_file, см. _pop_level.h).
* Холодная половина (аллокация страниц, cbl_open) — в pop_sfx_cold.c.
*/
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <sprinter.h>
#include <cbl.h>
#include <irq.h> /* IRQ_DISABLE/IRQ_ENABLE вокруг курсора */
#include "pop_sfx.h"
#include "_pop_sfx.h"
/* Физические страницы набора; заполняет холодная половина. */
uint8_t pop_snd_page[POP_SND_PAGES];
uint8_t pop_snd_ok;
/* ХОЧЕТ ЛИ ЗВУКА ПОЛЬЗОВАТЕЛЬ (Ctrl+S) — отдельно от pop_snd_ok.
*
* Разделять обязательно: pop_snd_ok значит «CBL сейчас открыт», и его
* гасит служебная пауза на время загрузки уровня. Если бы Ctrl+S правил
* только его, то первая же смена уровня позвала бы pop_sfx_start() и
* вернула звук, который пользователь выключил. Поэтому start уважает
* этот флаг, а pause его не трогает.
*
* Живёт в резиденте, а не в банке: метку в борте рисует отладочный ярлык
* каждый кадр, и тащить ради одного байта трамплин незачем. */
uint8_t pop_snd_want;
/* Курсор проигрывания. volatile: пишет главный цикл, читает прерывание.
* Всё в 8/16 бит — 32-битной арифметики на Z80 избегаем (см. memory
* avoid_32bit_arith_z80); поэтому и в таблице смещение уже разобрано на
* страницу и адрес в окне. */
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) */
/* ---- насос ---------------------------------------------------------- *
* Зовётся из ISR CBL раз в ~11,7 мс за очередными 128 байтами.
*
* ОКНО W0 БЕРЁМ ВЗАЙМЫ. Фон в этот момент может держать в W0 страницу
* атласа (pop_blit_b делает map ... blit ... unmap), поэтому сохраняем
* порт, подставляем свою страницу и возвращаем как было. Для фона
* подмена невидима: прерывание для него атомарно. Тот же приём, что у
* трамплина с W1 (_irq_tramp.c).
*
* Заплатки ISR-стаба в 0x38/0x66 нашим страницам НЕ нужны (в отличие от
* страницы уровня, pop_level_read_file): пока страница замаплена, мы
* внутри прерывания и IFF сброшен — прерваться некому.
*
* ТИШИНУ ЛЬЁМ СВОЮ. Когда эффект кончился, отдавать в CBL всё равно надо:
* иначе железо крутит по кругу хвост своего буфера и это слышно как
* жужжание. Готовый блок 0x80 лежит первым в наборе (упаковщик), поэтому
* libc-шный CBL_UNDERRUN_SILENCE с его malloc нам не нужен — а куча в
* тесном резиденте W2 отказала бы и уронила весь звук. */
int pop_sfx_fill(uint16_t n)
{
uint8_t saved, pg;
uint16_t ptr;
if (sfx_left) { pg = sfx_pg; ptr = sfx_ptr; }
else { pg = POP_SND_SILENCE_PAGE; ptr = POP_SND_SILENCE_OFF; }
saved = _io_page_w0;
_io_page_w0 = pop_snd_page[pg];
cbl_push_otir((const void *)ptr, n);
_io_page_w0 = saved;
if (!sfx_left) return 1; /* играли тишину — курсор не двигаем */
sfx_ptr += n;
if (sfx_ptr >= 0x4000) { /* граница страницы — ровно по блоку */
sfx_ptr = 0;
sfx_pg++;
}
sfx_left -= n;
return 1;
}
/* ---- запуск / останов ------------------------------------------------ */
void pop_sfx_play(uint8_t id)
{
uint8_t pg;
uint16_t off, len;
if (!pop_snd_ok || id >= POP_SND_COUNT) return;
pg = pop_snd_tbl[id].page;
off = pop_snd_tbl[id].off;
len = pop_snd_tbl[id].len;
/* Курсор из трёх полей, а читает его прерывание — обновляем под DI,
* иначе насос может застать наполовину обновлённое состояние и уехать
* в чужие данные. */
IRQ_DISABLE();
sfx_pg = pg;
sfx_ptr = off;
sfx_left = len; /* len == 0 — просто глушим */
IRQ_ENABLE();
}
void pop_sfx_stop(void)
{
IRQ_DISABLE();
sfx_left = 0;
IRQ_ENABLE();
}
/* ---- чтение файла в ФИЗИЧЕСКУЮ страницу ------------------------------ *
* Резидентно по причине из шапки: маппим W3, читаем, возвращаем окно.
* Ровно то же делает atlas_load, поэтому вызов из банка безопасен.
* 0 — прочитано ожидаемое число байт, -1 — нет. */
int pop_page_read(const char *path, uint8_t phys, uint16_t want)
{
uint8_t saved_w3;
int fd, n;
fd = open(path, O_RDONLY);
if (fd < 0) return -1;
saved_w3 = _io_page_w3;
sprinter_page_w3(phys);
n = read(fd, (uint8_t *)0xC000, want);
_io_page_w3 = saved_w3;
close(fd);
return (n == (int)want) ? 0 : -1;
}