/* * pop_music.h — музыкальный трек через тот же CBL, что и эффекты. * * ПОЧЕМУ PCM, А НЕ AY. Оцифровки музыки в игре нет вовсе: digisnd*.dat * содержит только эффекты, музыка лежит MIDI под Adlib. Зато есть готовые * записи DOS-версии, и путь «запись -> сырой поток CBL» не требует ни * синтезатора на хосте, ни аранжировки под три канала AY (разбор вариантов * — ../docs/sound_plan.md §1). Плата известна: CBL один, поэтому пока * играет музыка, эффектов нет. Оригинал ведёт себя так же — play_sound * там глушит предыдущий звук. * * ГДЕ УМЕСТНО. Заставки и титры: эффектов там не бывает, картинка * статична, и загрузка сотни-другой килобайт с диска никому не мешает. * Музыка ПО ХОДУ игры (джинглы смерти, зелья) — отдельный вопрос, её * придётся либо гасить эффектами, либо уводить на AY. * * ФОРМАТ на диске — один файл MUS\m.bin на трек, читается порциями по * 16 КБ из одного открытого fd (bank_read_page). Каталог с длинами * генерирует toolchain/pop_pack_music.py в pop_music_tbl.h. */ #ifndef POP_MUSIC_H #define POP_MUSIC_H #include /* Сколько страниц 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 */