24ced6058f
Эффекты берём из 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, поэтому ни один блок не пересекает границу страницы — проигрывателю не нужна логика стыка.
336 lines
23 KiB
Markdown
336 lines
23 KiB
Markdown
# Звук в порте 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 (бас/мелодия/арпеджио), не трогая
|
||
ни данные, ни секвенсор.
|