/* * 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 #include #include #include #include #include /* 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; /* Курсор проигрывания. 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; }