Files
Sprinter-SDCC/applications/PoP/docs/sound_plan.md
T
snark13 24ced6058f Упаковщик звуковых эффектов + сверка наборов MSDOS и SDLPoP
Эффекты берём из MSDOS/digisnd*.dat: 28 из 31 звука совпадают побайтно с
набором SDLPoP, но три различаются не в пользу последнего — у него
sword_vs_sword короче, sword_moving другой, а spiked вообще пустой
(7 сэмплов против 5 069).

MIDI наоборот: у MSDOS формат 0 (всё слито в одну дорожку), у SDLPoP
формат 1 с 8-9 дорожками — готовое разделение голосов, если дойдём до
пути B (MIDI -> три канала AY).

Упаковщик: 31 эффект -> 10 937,5 Гц, 131 072 Б = 8 EMM-страниц.  Начало
каждого звука выровнено на 128 (блок запроса CBL), а страница кратна 128,
поэтому ни один блок не пересекает границу страницы — проигрывателю не
нужна логика стыка.
2026-08-20 12:42:19 +03:00

336 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Звук в порте 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, 8 бит, МОНО, единая частота.** Проверено по
`convert_digi_sound` (`seg009.c:2358`): один байт на кадр, то есть
моно, и байт беззнаковый (`(b | b<<8) - 32768`), центр 0x80 — ровно
формат нашего CBL. Стерео в данных нет вовсе: каналы у оригинала
размножаются уже на выходе (`digi_audiospec->channels`).
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 КБ; не стоит того, но если место когда-нибудь прижмёт — вариант есть.)
## 1в. Бюджет памяти EMM (живой замер 2026-08-20)
Замерено `mem_info` из работающей программы (уровень 1), а не посчитано на
бумаге: инструментовка ставилась временно и откатана.
| | страниц | КБ |
|---|---:|---:|
| всего в машине | 256 | 4 096 |
| система (DSS) + сам exe: база + 8 банков кода | **43** | 688 |
| наши ассеты | **81** | 1 296 |
| **занято** | **124** | 1 984 |
| **свободно** | **132** | **2 112 (2,06 МБ)** |
Разбивка 81 страницы ассетов (сходится точно):
| набор | страниц |
|---|---:|
| **Тень** (`sk*` 28 + `sf*` 4) | **32** |
| Кид (`kid0..27`) | 28 |
| фон тайлсета (env 10 + wall 1 + fore 1) | 12 |
| страж | 5 |
| зелья (chtab_1), меч, `kid_data.bin`, страница уровня | по 1 |
Самый крупный потребитель теперь — **набор Тени, 32 страницы**, больше
самого Кида. Если место когда-нибудь прижмёт, там есть очевидный резерв
(кадры смерти и позы, в которых Тень не бывает), но при 132 свободных
страницах трогать незачем.
### Что из этого следует для звука
| статья | страниц | останется свободно |
|---|---:|---:|
| эффекты WAV, все 31, 10 937,5 Гц | **8** | 124 |
| музыка ПО ХОДУ игры в WAV (путь C, 12 треков) | 50 | 74 |
| заставки и титры в WAV (10 треков, 248 с) | 167 | **не влезает** |
То есть эффекты — капля, игровая музыка в WAV тоже поместится, а
заставочную придётся стримить с диска в любом случае (что и планировалось:
во время заставок игра ничего не рисует).
Оговорка: 132 свободных страницы — это на уровне 1. На уровне 9 добавятся
зеркальные наборы (`pop_vflip_load_all`: 28 Кид + 5 страж + меч = 34
страницы), останется ~98. Проверять запас надо ИМЕННО ТАМ.
## 1г. MSDOS против SDLPoP: чем отличаются наборы (сверено 2026-08-20)
У нас лежат ДВЕ копии звука — оригинальные `.dat` в `MSDOS/` (версия
1.3/1.4) и распакованные ассеты `SDLPoP/data/` (версия 1.0/1.1). Разница
есть, и она влияет на выбор источника.
### Оцифровка: берём MSDOS
Заголовок разный (`digi_new_type` против `digi_type`), но **28 звуков из
31 совпадают побайтно**. Различаются три, и все не в пользу SDLPoP:
| id | звук | MSDOS | SDLPoP |
|---|---|---:|---:|
| 10 | `sword_vs_sword` | 5 020 сэмплов | 3 504 |
| 11 | `sword_moving` | 1 172 | 1 172, но **другие байты** |
| 48 | `spiked` | 5 069 | **7** — то есть звука нет |
`spiked` в наборе SDLPoP фактически пустой. Поэтому упаковщик читает
`MSDOS/digisnd*.dat`, а не распакованные ассеты — в отличие от графики,
где источник наоборот SDLPoP.
### MIDI: если дойдём до музыки — брать SDLPoP
Здесь всё наоборот. Содержимое музыкально то же (деление 480, те же
каналы 0..7 плюс ударные), но:
| | MSDOS | SDLPoP |
|---|---|---|
| формат MIDI | **0** — всё слито в ОДНУ дорожку | **1** — 8-9 дорожек |
| размер (звук 24) | 327 Б | 448 Б |
| размер (звук 56) | 13 587 Б | 12 773 Б |
Формат 1 с отдельной дорожкой на инструмент — это готовое разделение
голосов. Для пути B (MIDI -> три канала AY) оно решает половину задачи:
дорожки можно выбирать напрямую (бас / мелодия / гармония), а не
разбирать слитый поток и догадываться, что чем было.
**Итог: эффекты из MSDOS, музыка (когда дойдёт) из SDLPoP.**
## 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 (бас/мелодия/арпеджио), не трогая
ни данные, ни секвенсор.