Files
Sprinter-SDCC/applications/PoP/docs/sound_plan.md
T
snark13 4688364091 Звук: решения пользователя и единая частота 10 937,5 Гц
Эффекты — WAV через CBL; музыка первым заходом путь A (ноты на AY);
заставки потом WAV; музыка по ходу игры — открыто (WAV с гашением
эффектов либо путь B).

Единую частоту берём не 11 000, а ровно частоту CBL 10 937,5: тогда тон
точен (иначе −9,9 цента), а пересчитывать три файла всё равно надо.
112 922 -> 124 531 Б, 6,9 -> 7,6 EMM-страниц; рост целиком от
leveldoor_sliding (источник 2 750 Гц).  Взамен CBL открывается один раз и
частота не меняется никогда.
2026-08-20 12:10:04 +03:00

18 KiB
Raw Blame History

Звук в порте PoP — разбор и план

Дата: 2026-08-20. Статус: разбор, кода нет.

Задача пользователя: добавить звук. Приоритет — эффекты; музыку, если найдётся способ. Эффекты — обязательно WAV, а не PC-спикер (уточнение 2026-08-20; см. §1а — оказалось, что они и так все в WAV). Ниже — что реально лежит в ассетах, что умеет железо, и почему получившийся план вышел проще, чем ожидалось.

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 звучат ОДНОВРЕМЕННО, и смешивать их программно не надо.

1а. Уточнение после разбора ВСЕХ наборов MS-DOS версии (2026-08-20)

Пользователь попросил, чтобы эффекты были не PC-спикером, а WAV, и заодно посмотреть mt32snd[1-2].dat. Разобрал все восемь .dat из MSDOS/. Ответ короткий: эффекты И ТАК все до одного есть в WAV, а вот у музыки WAV нет ни в одном наборе.

набор формат какие звуки сколько
digisnd1..3 WAV, 8 бит PCM эффекты 0..23, 44..49, 51 31
mt32snd1..2 MIDI для Roland MT-32 ТЕ ЖЕ эффекты 0..23, 44..51 31
midisnd1..2 MIDI (AdLib/GM) музыка 24..43, 50, 52..56 22
ibm_snd1..2 ноты PC-спикера всё подряд, 0..56 57

Здесь пряталась ловушка: mt32snd по имени похож на «музыку получше», а на деле это набор ЭФФЕКТОВ для владельцев MT-32 — те же id, что у digisnd. Музыки в нём нет вовсе.

Частоты WAV: 28 звуков на 11 000 Гц, по одному на 8 200, 14 000 и 2 750. Итого 112 922 сэмпла = 11,4 с, ~110 КБ ≈ 6,7 EMM-страниц.

Только PC-спикером, без альтернатив, остаются четыре id: 31, 34, 42 (пустые) и 38 blink — четыре ноты. То есть на весь звук игры спикер нужен ровно для одного писка.

Музыка: WAV нет, есть три пути

путь данные что получится цена
A. Ноты PC-спикера на AY 7 КБ один квадратный голос — ровно то, что слышали на IBM PC 1989 секвенсор на полсотни строк
B. MIDI -> AY, три голоса 27 КБ исходника богаче: бас + мелодия + арпеджио разбор MIDI + раскладка по каналам
C. MIDI -> WAV на хосте, стрим через CBL см. ниже настоящее звучание, любое нужен синтезатор на хосте + место

Про объём для пути C (замерено по длительностям треков):

группа треков длительность WAV 11 кГц
звучат ПО ХОДУ игры (гимн уровня, смерть, зелья, перо, победа) 12 74,7 с 803 КБ = 50 EMM-страниц
заставки и титры 10 248,1 с 2 665 КБ = 167 страниц

Игровая половина в EMM влезает (при ~215 свободных страницах), а заставочная — нет, её пришлось бы стримить с диска. Но заставки идут тогда, когда игра ничего не рисует, так что стрим там как раз уместен.

Предложение: начинать с A (7 КБ, работает сразу, ноль рисков), а C держать как отдельную фазу — она ортогональна: проигрыватель WAV для музыки это тот же cbl_push, что и для эффектов, только длиннее буфер. B имеет смысл только если C окажется неподъёмным по месту.

1б. РЕШЕНИЯ (пользователь, 2026-08-20)

  1. Эффекты — WAV через CBL. Как в §1: играются как есть.
  2. Музыка, первый заход — путь A (ноты спикера на AY).
  3. Заставки и титры — потом WAV. Конфликта с эффектами там нет: одновременно они не звучат.
  4. Музыка ПО ХОДУ игры (она может совпасть с эффектом) — открыто, два варианта: либо тоже WAV с ГАШЕНИЕМ эффектов на время музыки (музыка важнее — проверить на слух), либо путь B (MIDI -> три голоса AY).
  5. Все эффекты привести к одной частоте.
  6. Синтезатор для MIDI -> WAV — решать ближе к делу; годятся и онлайн- конвертеры, хоть вручную, если fluidsynth/timidity не поставится.

Про единую частоту (замер)

Приводим не к 11 000, а ровно к 10 937,5 Гц — частоте CBL (CBL_FREQ_10K9). Тогда тон точен, а не «на 0,6 % ниже»: проигрывание 11 000 Гц данных на 10 937,5 даёт сдвиг 9,9 цента, что на коротком эффекте не слышно, но бесплатно избавиться от него всё равно приятно — пересчитывать три файла всё равно придётся.

id звук было станет дельта
15 leveldoor_sliding 2 750 Гц, 4 436 сэмплов 17 643 +13 207 Б
23 footstep 8 200 Гц, 996 1 329 +333 Б
51 princess_door_opening 14 000 Гц, 6 188 4 834 1 354 Б
остальные 28 (11 000 Гц) ×0,9943 588 Б

Итог: 112 922 -> 124 531 Б, 6,9 -> 7,6 EMM-страниц. Рост целиком от leveldoor_sliding: источник у него 2 750 Гц, вчетверо реже целевой, и апсэмплинг не улучшит звучание — только уравняет формат. Платим 0,8 страницы за то, что CBL открывается ОДИН раз и частоту менять не надо никогда — ни между эффектами, ни при переходе на музыку-WAV.

(Альтернатива для него — хранить как есть и повторять каждый сэмпл четырежды в рантайме: 2 750 × 4 = 11 000 ровно. Это код в fill() ради 13 КБ; не стоит того, но если место когда-нибудь прижмёт — вариант есть.)

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 (путь A), тик из CBL-callback небольшая
З6 заставки и титры — WAV-музыка потоком (эффекты в это время не звучат) после З1-З4
З7 музыка по ходу игры: WAV с гашением эффектов ЛИБО путь B — решать по итогам З6 открыто
З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 (бас/мелодия/арпеджио), не трогая ни данные, ни секвенсор.