e0c86a96ef
Порт 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>
134 lines
7.0 KiB
C
134 lines
7.0 KiB
C
/*
|
||
* 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;
|
||
}
|