# Звук в порте PoP — разбор и план Дата: 2026-08-20. Статус: **разбор, кода нет.** Задача пользователя: добавить звук. Приоритет — эффекты; музыку, если найдётся способ. Ниже — что реально лежит в ассетах, что умеет железо, и почему получившийся план вышел проще, чем ожидалось. ## 1. Главный вывод **Ни MIDI разбирать, ни ноты сочинять не придётся, и ресэмплировать тоже.** - Эффекты уже лежат **8-битным беззнаковым PCM на 11 000 Гц**, а у CBL есть режим **10 937,5 Гц** — расхождение 0,6 %, на слух неразличимо. Формат сэмпла совпадает с нашим CBL байт в байт (`cbl.h`: 8 бит, беззнаковый, центр 0x80). То есть данные играются **как есть**, без конверсии. - Музыка есть в виде **списков нот PC-спикера — 7 КБ на всю игру**, а нота там задана прямо в ГЕРЦАХ. Пересчёт в делитель AY — одно деление. - AY и COVOX на Sp2000 сведены в **один ЦАП TDA1543** (док Ивана Мака, §5), значит музыка на AY и эффекты через CBL звучат ОДНОВРЕМЕННО, и смешивать их программно не надо. ## 2. Что лежит в ассетах (замерено, а не по памяти) Звук в PoP адресуется как ресурс `10000 + N`, N = 0..56 — 57 звуков (`load_sound`, `seg009.c:2289`). Наборов три, и они ПАРАЛЛЕЛЬНЫЕ: один и тот же звук есть в нескольких видах. | набор | что это | объём | покрытие | |---|---|---:|---| | `DIGISND1..3.DAT` | оцифровка, 8 бит PCM | 103 941 Б | **31 звук** (эффекты) | | `MIDISND1..2.DAT` | MIDI-музыка | 27 776 Б | музыка | | `IBM_SND1..2` (распакованы) | ноты PC-спикера | **7 КБ** | **все 57** | ### 2.1 Оцифровка (эффекты) Разбор контейнера: индекс по 8 байт на запись (id, offset, size), **первый байт ресурса — контрольная сумма**, тело за ней (спецификация `POP-DAT-FormatSpecifications`, §3.1.2 — на этом я сначала споткнулся и читал мусор). Тело — `digi_type`: `word rate, word count, word unk, byte size`, дальше сэмплы. - 31 звук, все 8-битные; - частоты: **28 звуков на 11 000 Гц**, по одному на 8 200 и 14 000; - 103 701 сэмпл = **9,4 секунды**, **103 941 Б ≈ 6,3 EMM-страницы**. ### 2.2 Ноты PC-спикера (и эффекты, и музыка) Формат: `byte type(=0), word tempo`, дальше тройки `word frequency, byte length`; `frequency <= 1` — пауза, `0x12` — конец. Ключевое, что пришлось смотреть в `play_speaker_sound`/`speaker_callback` (`seg009.c`): - **`frequency` — это ГЕРЦЫ напрямую** (`generate_square_wave(stream, (float)note->frequency, ...)`), а не делитель PIT, как кажется по маленьким числам; - длительность ноты = `length / tempo` СЕКУНД. Замеры по всем 57 звукам: **2212 нот**, частоты 16..65507 Гц, длительности 1,74..2571 мс. Из них 23 звука — те, у которых оцифровки НЕТ, то есть вся музыка: заставки, гимны уровней, смерть, победа, титры (id 24..43, 50..56). **1469 нот**, и вот их длительности: | длительность ноты | нот | доля | |---|---:|---:| | < 3 мс | 0 | 0 % | | 3..12 мс | 2 | 0,1 % | | 12..25 мс | 140 | 9,5 % | | > 25 мс | 1327 | 90,3 % | Это число решает вопрос про таймер — см. §4. ## 3. Что умеет железо (док Ивана Мака §5 + MAME) - **AY-3-8910/8912** в ПЛМ, «программируется по стандартным описаниям» — то есть ZX-порты; в MAME он заведён как `AY8910(config, "ay8912", X_SP/24)`, то есть **тактовая 1,75 МГц**. Период канала = 109375 / частота(Гц), 12 бит (макс 4095) → снизу берутся частоты от ~27 Гц. Точные Z80-адреса портов идут через таблицу DCP, а не напрямую — **проверить артефактом до кодинга** (ожидаем ZX-стандарт 0xFFFD/0xBFFD). - **CBL** — COVOX с буфером 256 Б, две половины по 128; бит 7 порта 0xFE показывает играющую половину, порт управления 0x4E. Прерывание — когда половина сменилась. - **Бипер** (бит 5 порта 0xFE) — туда же в ЦАП. Нам не нужен. - Всё это **сведено в один ЦАП**, поэтому AY и CBL звучат вместе. У нас уже есть готовая обвязка CBL (`libc/include/cbl.h`): callback `fill(n)`, выдача блока через `cbl_push_otir()` (порт 0x4F) или через акселератор, коды частот, счётчик недоливов. Своего кольца библиотека не держит — данные пропихиваются прямо из наших EMM-страниц. ## 4. Предлагаемая архитектура ``` эффекты (31 шт, 8 бит 11 кГц) музыка (23 шт, ноты) │ │ EMM-страницы (6,3) 7 КБ нот в банке │ │ cbl_push_otir из fill() запись 3 регистров AY │ │ CBL (10,9 кГц) ──────┐ ┌────────── AY (1,75 МГц) ▼ ▼ TDA1543 (аппаратное смешивание) ``` **Один источник прерываний — CBL.** Его callback приходит каждые 128 сэмплов = **11,7 мс** при 10,9 кГц, и он же двигает секвенсор музыки. Отдельный таймер (CTC) НЕ нужен: по таблице из §2.2 короче 12 мс всего 2 ноты из 1469 — они растянутся на один тик, чего не слышно. Почему это важно: `irq_ctc_install` сейчас требует кода в W2 (tiny/big), а roomtest — huge, и попадёт ли туда CTC-трамплин, зависит от раскладки. Обойтись без него — значит не открывать этот фронт вовсе. **Пейсинг кадра при этом не страдает.** Наш темп считается ПО ЛУЧУ (`pop_pace.h`), а не по кадровым прерываниям, поэтому то, что CBL-ветка трамплина делает приватный RETI и съедает кадровые прерывания, нам безразлично. Если бы пейсинг остался на прерываниях — звук бы его сломал. ## 5. Объём работ | фаза | что | оценка | |---|---|---| | **З1** | распаковщик `pop_pack_sound.py`: DAT → `.snd`-страницы EMM (эффекты) + `.not` (ноты музыки) | формат уже разобран | | **З2** | `pop_sfx.c`: `cbl_open` + `fill()`, таблица «звук → страница/смещение/длина», `pop_sfx_play(id)` | ядро | | **З3** | развесить вызовы: 66 мест `play_sound` в оригинале; у нас часть уже помечена TODO (`pop_ctrl.c:408`, `guards.c:992`, `pop_map.c:3035/3107`, …). **Плюс бесплатный кусок**: опкод `SEQ_SOUND` в seqtbl уже разбирается нашим `play_seq` (`pop_kid.c:326`) — шаги, приземления и прочее поедут сами | механическая | | **З4** | `pop_music.c`: секвенсор нот на AY, тик из CBL-callback | небольшая | | **З5** | приоритеты и вытеснение: у оригинала `play_sound` глушит предыдущий (`stop_sounds`), музыка и эффект — разные каналы | правила из seg009 | ## 6. Что проверить артефактом ДО кодинга 1. **Порты AY на Sprinter** — записать в 0xFFFD/0xBFFD и убедиться, что MAME отдаёт звук (порты идут через DCP-таблицу, «стандартные ZX» — это ожидание, а не факт). 2. **CBL в режиме huge.** Шапка `cbl.h` говорит «код/данные в W2 (tiny/big)», но это скорее всего устаревшая оговорка: IM2-трамплин давно переделан на all-modes, и наш кадровый путь в huge работает. Проверить `cbl_open` из roomtest. 3. **Совместное владение портом 0xFE.** Бит 5 (луч) у нас держится через `_cbl_port_ref` «немым» кодом частоты; когда откроется НАСТОЯЩИЙ CBL, владение переходит к нему. Убедиться, что пейсинг переживает `cbl_open`/`cbl_close`. 4. **Цена fill() в кадре.** 128 байт через `cbl_push_otir` раз в 11,7 мс — замерить тем же способом, что и остальное (брейкпоинт + totalcycles). ## 7. Про MIDI — почему не он Музыка в MIDISND — настоящие MThd/MTrk чанки, 27 КБ. Чтобы играть их на AY, нужен разбор MIDI, раскладка каналов на три голоса и таблица инструментов — это отдельный проект, и звучать он будет НЕ так, как оригинал на PC. А набор PC-спикера — это ровно то, что слышал игрок на IBM PC 1989 года: один квадратный голос. Он у нас есть целиком, весит 7 КБ и ложится на AY напрямую. Если позже захочется богаче — материал уже будет разобран, и можно разложить те же мелодии на три канала AY (бас/мелодия/арпеджио), не трогая ни данные, ни секвенсор.