SprPoP: музыка без перелинковки — длины и длительности уехали на диск

Часть I плана music_runtime_index_plan.md (MI0..MI5).  gen/pop_music_tbl.h
и gen/pop_music_ticks.h УДАЛЕНЫ: длины треков и длительности реплик
читаются из MUS/mus.idx (формат PMI1, tools/pop_idx.py, тесты в
make test-tools).  Один и тот же sprpop.exe работает с любым из четырёх
наборов записей — sha256 бинарника при смене MUSIC_FMT не меняется.

ГДЕ ЖИВЁТ ИНДЕКС.  228 байт таблицы в W2 не положить (свободной кучи там
порядка двух сотен), поэтому индекс лежит в одной странице EMM, а в
резиденте от него два байта.  Данные в странице — со смещения 0x100:
gfx_w0_page_prepare пишет в неё стабы прерываний (0x38 и 0x66), и с нуля
они попали бы прямо в записи id 10 и 21.  Со смещением работает штатная
защита, а не запрет прерываний (тот же приём, что CFG_BASE в
pop_config.c).  Число страниц в индексе не хранится — считается из blocks,
чтобы не разъехалось.

ПАУЗА КОНЦА УРОВНЯ — СОСТОЯНИЕМ, А НЕ СЧЁТЧИКОМ.  pop_endmus_left и
POP_MUS_TICKS_32/41 удалены; главный цикл ждёт pop_music_active() —
«заявка лежит, идёт загрузка или трек звучит».  Одного busy мало: между
заявкой и первой нотой 190-230 мс (замер в sound_plan §9).  Прежний
счётчик закрывал эту щель ценой зависимости EXE от набора и жёсткого
делителя /4, который врал в режимах FAST/FASTEST (там логический кадр 3
кадра луча, а не 4).  Побочно исправилось расхождение с SDLPoP: при
выключенном звуке заявка не кладётся, и уровень меняется сразу, как в
оригинале (seg006:651 + seg003:387) — раньше игра держала пройденный
уровень лишние 12 секунд в тишине.

PV-СЦЕНА — на четырёх якорях (8 байт статики), которые считаются из
индекса при входе в сцену; прежние выражения шкалы не изменились.  План
предлагал протащить структуру времён через пять функций — для сцены,
которая идёт раз за запуск, это того не стоит.

ПАМЯТЬ.  За обе фазы резидент не вырос, а освободился: _CODE 23865 ->
23544, куча 239 -> 256 Б.  Банк 9 похудел на 118 Б (ушла pop_mus_tbl из
rodata), банк 11 — на длительности реплик.

ПРОВЕРЕНО В MAME: exe побайтово одинаков для flac и mt32; все 22 трека в
индексах различаются, и контрольные значения совпали с предсказанными
планом (m41 732->685, m50 831->867, m53 985->1044, m56 9865->10462
блоков, 78->82 страницы); на mt32 PV-сцена проходит целиком по его
длительностям; без mus.idx музыки нет, эффекты работают, игра проходима.

НЕ ПРОВЕРЕНО: потоковый m56 на 82 страницах — до финала надо дойти в
игре.  Единственный оставшийся пункт приёмки, отмечен в sound_plan §11.5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
This commit is contained in:
2026-08-31 17:17:59 +03:00
parent 2349481b86
commit df5071a967
17 changed files with 587 additions and 330 deletions
-104
View File
@@ -1,104 +0,0 @@
/*
* pop_music_tbl.h — каталог музыкальных треков, СГЕНЕРИРОВАН.
* Источник: assets/orig/PoP1_DOS_music/flac (записи DOS-версии).
* Генератор: tools/pop_pack_music.py. Руками не править.
*
* Нумерация id — оригинала (SDLPoP data/music/names.txt). pages —
* сколько кусков m<id>_<nn>.bin лежит на диске, blocks — длина трека
* в порциях насоса по 128 байт (в байтах не влезло бы в uint16).
*
* Длительности реплик в тиках вынесены в pop_music_ticks.h: таблица
* ниже — 171 байт ДАННЫХ, и модулю, которому нужна одна константа,
* платить за неё копией таблицы незачем (поймано на pop_intro.c:
* +172 байта в банк 11).
*
* POP_MUS_TICKS_<id> — та же длина в ТИКАХ СЦЕНЫ (60 Гц оригинала).
* Нужна там, где сценарий ждёт окончания реплики: у оригинала это
* `do { frame(1) } while (check_sound_playing())`, у нас — пауза
* длиной в нашу запись. Считается ЗДЕСЬ, а не в C, по двум
* причинам: blocks*40 переполняет uint16 на won (9855 порций), и
* число обязано соответствовать НАБОРУ, которым собрали (flac и
* mt32 — разные исполнения разной длины).
*
* Отношение 40/57 — обратное к тому, которым шкала сцены переводит
* тики в порции насоса (57/40, см. pop_intro.c). Берём именно его,
* а не точное 10937,5/128/60, чтобы длина реплики и ход шкалы
* считались одной и той же арифметикой. Округление — к ближайшему
* (+28 перед делением): отбрасывание вниз теряло по тику на реплику.
*/
#ifndef POP_MUSIC_TBL_H
#define POP_MUSIC_TBL_H
#include <stdint.h>
#define POP_MUS_COUNT 57
#define POP_MUS_BLOCK 128
#define POP_MUS_MAX_PAGES 78
typedef struct {
uint8_t pages; /* 0 — трека нет */
uint16_t blocks; /* длина в порциях по 128 байт */
} pop_mus_ent_t;
static const pop_mus_ent_t pop_mus_tbl[POP_MUS_COUNT] = {
{ 0, 0 }, /* 0 */
{ 0, 0 }, /* 1 */
{ 0, 0 }, /* 2 */
{ 0, 0 }, /* 3 */
{ 0, 0 }, /* 4 */
{ 0, 0 }, /* 5 */
{ 0, 0 }, /* 6 */
{ 0, 0 }, /* 7 */
{ 0, 0 }, /* 8 */
{ 0, 0 }, /* 9 */
{ 0, 0 }, /* 10 */
{ 0, 0 }, /* 11 */
{ 0, 0 }, /* 12 */
{ 0, 0 }, /* 13 */
{ 0, 0 }, /* 14 */
{ 0, 0 }, /* 15 */
{ 0, 0 }, /* 16 */
{ 0, 0 }, /* 17 */
{ 0, 0 }, /* 18 */
{ 0, 0 }, /* 19 */
{ 0, 0 }, /* 20 */
{ 0, 0 }, /* 21 */
{ 0, 0 }, /* 22 */
{ 0, 0 }, /* 23 */
{ 3, 305 }, /* 24 regular_death */
{ 2, 196 }, /* 25 find_mirror_and_find_shadow */
{ 9, 1132 }, /* 26 embrace */
{ 8, 915 }, /* 27 cutscene_before_2_4_6_12 */
{ 6, 656 }, /* 28 fight_death */
{ 4, 428 }, /* 29 meet_Jaffar */
{ 3, 299 }, /* 30 life_potion */
{ 0, 0 }, /* 31 */
{ 5, 624 }, /* 32 end_level_4_and_killed_by_shadow */
{ 2, 205 }, /* 33 potion */
{ 0, 0 }, /* 34 */
{ 7, 888 }, /* 35 cutscene_before_8_9 */
{ 10, 1256 }, /* 36 out_of_time */
{ 3, 280 }, /* 37 guard_death_and_obtaining_the_sword */
{ 0, 0 }, /* 38 */
{ 13, 1591 }, /* 39 floating */
{ 5, 596 }, /* 40 cutscene_not_much_time_left */
{ 9, 1043 }, /* 41 story_5_level_end_and_merge_with_shadow */
{ 0, 0 }, /* 42 */
{ 3, 285 }, /* 43 killed_Jaffar */
{ 0, 0 }, /* 44 */
{ 0, 0 }, /* 45 */
{ 0, 0 }, /* 46 */
{ 0, 0 }, /* 47 */
{ 0, 0 }, /* 48 */
{ 0, 0 }, /* 49 */
{ 10, 1184 }, /* 50 story_2_princess */
{ 0, 0 }, /* 51 */
{ 20, 2443 }, /* 52 story_4_Jaffar_leaves */
{ 11, 1403 }, /* 53 story_3_Jaffar_enters */
{ 17, 2064 }, /* 54 intro_theme */
{ 11, 1350 }, /* 55 story_1_absence */
{ 78, 9865 }, /* 56 won */
};
#endif /* POP_MUSIC_TBL_H */
-44
View File
@@ -1,44 +0,0 @@
/*
* pop_music_ticks.h — длительности треков в ТИКАХ СЦЕНЫ, СГЕНЕРИРОВАН.
* Набор: flac. Генератор: tools/pop_pack_music.py.
* Руками не править.
*
* Нужны там, где сценарий ждёт окончания реплики: у оригинала это
* `do { frame(1) } while (check_sound_playing())`, у нас — пауза длиной
* в НАШУ запись. Число обязано соответствовать набору, которым собрана
* сборка: одна и та же реплика на flac и mt32 разной длины (m53 — 985
* тиков против 1044), и захардкоженная константа молча ломает сцену.
*
* Считается здесь, а не в C: blocks*40 переполняет uint16 на won
* (9855 порций). Отношение 40/57 — обратное к тому, которым шкала
* сцены переводит тики в порции насоса (57/40, pop_intro.c); берём
* именно его, чтобы длина реплики и ход шкалы считались одной
* арифметикой. Округление к ближайшему: вниз теряло по тику.
*/
#ifndef POP_MUSIC_TICKS_H
#define POP_MUSIC_TICKS_H
#define POP_MUS_TICKS_24 214 /* regular_death */
#define POP_MUS_TICKS_25 138 /* find_mirror_and_find_shadow */
#define POP_MUS_TICKS_26 794 /* embrace */
#define POP_MUS_TICKS_27 642 /* cutscene_before_2_4_6_12 */
#define POP_MUS_TICKS_28 460 /* fight_death */
#define POP_MUS_TICKS_29 300 /* meet_Jaffar */
#define POP_MUS_TICKS_30 210 /* life_potion */
#define POP_MUS_TICKS_32 438 /* end_level_4_and_killed_by_shadow */
#define POP_MUS_TICKS_33 144 /* potion */
#define POP_MUS_TICKS_35 623 /* cutscene_before_8_9 */
#define POP_MUS_TICKS_36 881 /* out_of_time */
#define POP_MUS_TICKS_37 196 /* guard_death_and_obtaining_the_sword */
#define POP_MUS_TICKS_39 1116 /* floating */
#define POP_MUS_TICKS_40 418 /* cutscene_not_much_time_left */
#define POP_MUS_TICKS_41 732 /* story_5_level_end_and_merge_with_shadow */
#define POP_MUS_TICKS_43 200 /* killed_Jaffar */
#define POP_MUS_TICKS_50 831 /* story_2_princess */
#define POP_MUS_TICKS_52 1714 /* story_4_Jaffar_leaves */
#define POP_MUS_TICKS_53 985 /* story_3_Jaffar_enters */
#define POP_MUS_TICKS_54 1448 /* intro_theme */
#define POP_MUS_TICKS_55 947 /* story_1_absence */
#define POP_MUS_TICKS_56 6923 /* won */
#endif /* POP_MUSIC_TICKS_H */