SprPoP: автономное приложение, выделенное из roomtest
Порт PoP переехал в applications/SprPoP — приложение, которое собирается само: код, оригинальные данные, конверторы ресурсов и сборка внутри одной папки. Наружу знает единственный путь — корень тулчейна (SPRINTER_ROOT, по умолчанию ../..). applications/PoP/roomtest ЗАМОРОЖЕНА и остаётся архивом закрытых задач, багов и исполненных планов. Скопировано из applications/PoP/roomtest@4b74478. Перенос проверен побайтово: собранный sprpop.exe совпал с roomtest.exe того же коммита, все 39 дисковых ресурсов и все 16 генерируемых заголовков — тоже, host- тесты зелёные (15/15). Раскладка: src/ рукописный C (roomtest.c -> sprpop.c) gen/ генерируемые заголовки, в репозитории assets/orig/ оригинальные данные игры, вне репозитория (копирайт) assets/packed/ то, что ложится на диск, в раскладке диска tools/ конверторы; все пути — в одном tools/paths.py build/ выход: exe, каталоги ресурсов, hdd/, промежуточные atl/ Сборка ресурсов: assets/packed и gen — версионируемые ВХОДЫ, а не то, что пересчитывается каждым make. Автоматика построена на ОТСУТСТВИИ файла, а не на таймстемпах: git не хранит времена, и в свежем клоне сравнение по времени превращалось бы в лотерею. Недостающий ресурс или заголовок чинится сам, рекурсивным вызовом в ветку генерации. Музыка собирается из любого из четырёх наборов записей (make music-mp3, music-mt32, ...); набор входит в имя stamp'а, поэтому смена набора сама делает музыку устаревшей. Длины реплик больше не захардкожены: упаковщик печатает их в gen/pop_music_ticks.h, и шкала сцены выражена через них — иначе mt32 (реплики на 6% длиннее) молча ломал катсцену. Тулчейн: в app.mk два обратносовместимых крючка (SRC_DIR/BUILD_DIR), HDD_IMG стал ?=; команда сборки roomtest не изменилась. Корневой make host-tests переключён на SprPoP. Подгонка тайминга катсцены с принцессой (PV_MAGIC_LEAD): сцена render-bound и идёт ~49 тиков/с вместо 60, из-за чего кода реплики приходила раньше молнии. Это обход, а не лечение; разбор с замерами — docs/BUGS_OPEN.md, записи SND-PACE-DEAD, PV-RENDER-BOUND, MUS-LEFT-TEAR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* pop_music.h — музыкальный трек через тот же CBL, что и эффекты.
|
||||
*
|
||||
* ПОЧЕМУ PCM, А НЕ AY. Оцифровки музыки в игре нет вовсе: digisnd*.dat
|
||||
* содержит только эффекты, музыка лежит MIDI под Adlib. Зато есть готовые
|
||||
* записи DOS-версии, и путь «запись -> сырой поток CBL» не требует ни
|
||||
* синтезатора на хосте, ни аранжировки под три канала AY (разбор вариантов
|
||||
* — ../docs/sound_plan.md §1). Плата известна: CBL один, поэтому пока
|
||||
* играет музыка, эффектов нет. Оригинал ведёт себя так же — play_sound
|
||||
* там глушит предыдущий звук.
|
||||
*
|
||||
* ГДЕ УМЕСТНО. Заставки и титры: эффектов там не бывает, картинка
|
||||
* статична, и загрузка сотни-другой килобайт с диска никому не мешает.
|
||||
* Музыка ПО ХОДУ игры (джинглы смерти, зелья) — отдельный вопрос, её
|
||||
* придётся либо гасить эффектами, либо уводить на AY.
|
||||
*
|
||||
* ФОРМАТ на диске — один файл MUS\m<id>.bin на трек, читается порциями по
|
||||
* 16 КБ из одного открытого fd (bank_read_page). Каталог с длинами
|
||||
* генерирует toolchain/pop_pack_music.py в pop_music_tbl.h.
|
||||
*/
|
||||
#ifndef POP_MUSIC_H
|
||||
#define POP_MUSIC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Сколько страниц EMM держим под трек. Двадцать = 320 КБ, это все
|
||||
* заставочные треки, кроме финального «won» (115 с, 1,2 МБ): тот придётся
|
||||
* либо резать, либо стримить, и это отдельная задача. */
|
||||
#define POP_MUS_PAGES 20
|
||||
|
||||
/* Номера треков — нумерация оригинала (SDLPoP data/music/names.txt). */
|
||||
#define POP_MUS_INTRO 54 /* тема заставки, звучит на титрах */
|
||||
#define POP_MUS_STORY_1 55 /* «In the absence of the Sultan...» */
|
||||
#define POP_MUS_STORY_2 50 /* принцесса ждёт */
|
||||
#define POP_MUS_STORY_3 53 /* входит Джафар */
|
||||
#define POP_MUS_STORY_4 52 /* Джафар уходит */
|
||||
#define POP_MUS_WON 56 /* победа: 115 с, только потоком */
|
||||
|
||||
/* Курсор проигрывания — читает НАСОС (pop_sfx.c, резидент), пишет
|
||||
* загрузчик. Отдельный от эффектов: они звучат поверх музыки, а не
|
||||
* вместо неё, и у каждого свой источник. */
|
||||
extern uint8_t pop_mus_page[POP_MUS_PAGES]; /* физические страницы EMM */
|
||||
extern volatile uint8_t pop_mus_pg; /* индекс текущей страницы */
|
||||
extern volatile uint16_t pop_mus_ptr; /* адрес в окне W0 */
|
||||
extern volatile uint16_t pop_mus_left; /* порций до конца, 0 — молчим */
|
||||
/* Номер играющего трека (нумерация оригинала). Нужен менеджеру звука:
|
||||
* музыка участвует в таблицах приоритета наравне с эффектами. */
|
||||
extern uint8_t pop_mus_id;
|
||||
/* Размер кольца страниц для потокового трека; 0 — обычный режим. */
|
||||
extern uint8_t pop_mus_ring;
|
||||
|
||||
/* ДВА СЛОТА. Заставочные сцены идут цепочкой треков (титры -> история ->
|
||||
* PV из трёх реплик), и загрузка каждого — это полсекунды-секунда диска.
|
||||
* Прятать её в паузу между репликами негде: пауз в оригинале нет, там
|
||||
* следующая реплика начинается сразу. Поэтому слотов два: пока звучит
|
||||
* один, следующий читается во второй, и переключение стоит копирования
|
||||
* двадцати байт таблицы страниц.
|
||||
*
|
||||
* Загрузить трек в свободный слот (НЕ запускает; играющий трек не трогает).
|
||||
* 0 — готово, -1 — нет трека/памяти/файла. */
|
||||
int8_t pop_music_load(uint8_t id) __banked;
|
||||
|
||||
/* ТА ЖЕ ЗАГРУЗКА ПО ОДНОЙ СТРАНИЦЕ ЗА ВЫЗОВ — для тех, кому нельзя
|
||||
* замереть на секунду: страница стоит 33 мс, то есть четверть кадра
|
||||
* заставочной сцены, и подкачка следующей реплики не видна.
|
||||
* begin — открыть файл и занять страницы (0 / -1);
|
||||
* step — прочитать очередную: 0 — ещё есть, 1 — трек готов к play,
|
||||
* -1 — ошибка (слот освобождён);
|
||||
* loading — идёт ли чтение (звать step имеет смысл, пока 1).
|
||||
* Оборвать начатое чтение можно pop_music_free. */
|
||||
int8_t pop_music_load_begin(uint8_t id) __banked;
|
||||
int8_t pop_music_load_step(void) __banked;
|
||||
uint8_t pop_music_loading(void) __banked;
|
||||
|
||||
/* Отпустить ОБА слота. Останавливает проигрывание, если оно шло. */
|
||||
void pop_music_free(void) __banked;
|
||||
|
||||
/* Пустить трек СРАЗУ, дочитывая его по ходу звучания: первая страница
|
||||
* читается здесь (33 мс), остальные — вызывающим, по одной за кадр, пока
|
||||
* pop_music_loading(). Для игровых джинглов, где паузы на диск быть не
|
||||
* может. 0 — играет, -1 — трека нет либо не хватило памяти. */
|
||||
int8_t pop_music_stream(uint8_t id) __banked;
|
||||
|
||||
/* Пустить последний загруженный трек с начала; слот предыдущего при этом
|
||||
* освобождается. Без парного load ничего не делает. */
|
||||
void pop_music_play(void) __banked;
|
||||
void pop_music_stop(void) __banked;
|
||||
|
||||
/* ОБСЛУЖИВАНИЕ МУЗЫКИ — один вызов на кадр из любого цикла (игра, интро,
|
||||
* катсцена): разбирает заявку pop_mus_req и доливает страницы начатого
|
||||
* стрима. Всё чтение с диска живёт здесь. */
|
||||
void pop_music_service(void) __banked;
|
||||
|
||||
/* ПОТОКОВЫЙ ТРЕК ИЗ КОЛЬЦА СТРАНИЦ — для того, что в память не влезает.
|
||||
* Финал `won` весит 1,2 МБ (78 страниц); играется из шести по кругу, а
|
||||
* дочитывает его pop_music_service, пока трек звучит.
|
||||
* 0 — играет, -1 — нет файла/памяти. Останавливает pop_music_ring_stop
|
||||
* (её же зовёт pop_music_free). */
|
||||
int8_t pop_music_ring_start(uint8_t id) __banked;
|
||||
void pop_music_ring_stop(void) __banked;
|
||||
|
||||
/* Трек ещё звучит? 0 — доиграл или не запускался. */
|
||||
uint8_t pop_music_busy(void) __banked;
|
||||
|
||||
#endif /* POP_MUSIC_H */
|
||||
Reference in New Issue
Block a user