Замеры по ассетам: 31 эффект уже 8-битным PCM на 11 000 Гц (у CBL есть 10 937,5 — расхождение 0,6 %, формат сэмпла совпадает байт в байт, то есть конверсии нет вовсе), 103 941 Б = 6,3 EMM-страницы. Вся музыка есть нотами PC-спикера — 7 КБ на 57 звуков, и нота там задана в ГЕРЦАХ напрямую (проверено по speaker_callback), а не делителем PIT, как кажется по числам. MIDI разбирать не нужно. Отдельный таймер не нужен: секвенсор музыки двигает CBL-callback раз в 11,7 мс, а короче 12 мс во всей музыке 2 ноты из 1469.
11 KiB
Звук в порте 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. Что проверить артефактом ДО кодинга
- Порты AY на Sprinter — записать в 0xFFFD/0xBFFD и убедиться, что MAME отдаёт звук (порты идут через DCP-таблицу, «стандартные ZX» — это ожидание, а не факт).
- CBL в режиме huge. Шапка
cbl.hговорит «код/данные в W2 (tiny/big)», но это скорее всего устаревшая оговорка: IM2-трамплин давно переделан на all-modes, и наш кадровый путь в huge работает. Проверитьcbl_openиз roomtest. - Совместное владение портом 0xFE. Бит 5 (луч) у нас держится через
_cbl_port_ref«немым» кодом частоты; когда откроется НАСТОЯЩИЙ CBL, владение переходит к нему. Убедиться, что пейсинг переживаетcbl_open/cbl_close. - Цена fill() в кадре. 128 байт через
cbl_push_otirраз в 11,7 мс — замерить тем же способом, что и остальное (брейкпоинт + totalcycles).
7. Про MIDI — почему не он
Музыка в MIDISND — настоящие MThd/MTrk чанки, 27 КБ. Чтобы играть их на AY, нужен разбор MIDI, раскладка каналов на три голоса и таблица инструментов — это отдельный проект, и звучать он будет НЕ так, как оригинал на PC. А набор PC-спикера — это ровно то, что слышал игрок на IBM PC 1989 года: один квадратный голос. Он у нас есть целиком, весит 7 КБ и ложится на AY напрямую.
Если позже захочется богаче — материал уже будет разобран, и можно разложить те же мелодии на три канала AY (бас/мелодия/арпеджио), не трогая ни данные, ни секвенсор.