Музыка: PCM-трек через CBL, первый экран истории озвучен
Путь C из sound_plan.md вместо ранее выбранного A (ноты спикера на AY). Причина смены: появились готовые записи DOS-версии, и строка «нужен синтезатор на хосте», из-за которой C считался дорогим, отпала — осталось `ffmpeg -ac 1 -ar 10937 -f u8`. Оцифровки музыки в самой игре нет вовсе: digisnd*.dat содержит только эффекты, музыка лежит MIDI под Adlib, так что конвертировать ресурс игры всё равно было не из чего. * toolchain/pop_pack_music.py — запись flac -> сырой u8 на 10 937,5 Гц (частота CBL, чтобы её не переключать никогда), нарезка на куски MUS\m<id>_<nn>.bin по 16 КБ: bank_load_file читает файл только целиком и только в одну страницу. Каталог -> pop_music_tbl.h; длина трека хранится ПОРЦИЯМИ по 128 байт — 169 КБ в uint16 не влезает, 1350 блоков влезает (32-бит арифметики на Z80 избегаем). * pop_music.c (банк 9) — загрузка трека в EMM и курсор; pop_sfx_fill (резидент) получил третий источник: эффект важнее музыки, музыка важнее тишины. Эффект музыку не сбрасывает — её курсор стоит, пока тот доигрывает, и она продолжается с места. * story_1_absence звучит на первом экране истории, где была тишина. Грузим ПОСЛЕ сборки картинки: чтение идёт через ESTEX, и при открытом CBL насос не успел бы долить блок. Проверено записью звука из MAME: корреляция огибающих с эталоном 0,836 при сдвиге 1,8 с, RMS 22,6 против 18,3 (разница — 8-битное квантование). Эффекты двери в PV-сцене после трека звучат по-прежнему. Бюджет: резидент +83 Б кода и +25 Б данных, запас W2 151 Б (кучи в приложении нет, malloc не слинкован); банк 9 занят на 88 %. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* pop_music.c — загрузка и запуск музыкального трека, БАНК 9.
|
||||
*
|
||||
* Здесь только холодная часть: выделить страницы EMM, вычитать в них куски
|
||||
* m<id>_<nn>.bin и выставить курсор. Сам вывод делает насос CBL в
|
||||
* резиденте (pop_sfx.c): когда эффект не звучит, он берёт байты отсюда, а
|
||||
* не из блока тишины. Контракт и мотивация — в pop_music.h.
|
||||
*
|
||||
* Имя файла собираем вручную, без sprintf: printf-семейство в резидент не
|
||||
* тянем, а тут и формат-то из двух чисел.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <sprinter_mem.h>
|
||||
#include "pop_music.h"
|
||||
#include "pop_music_tbl.h"
|
||||
#include "pop_sfx.h"
|
||||
|
||||
uint8_t pop_mus_page[POP_MUS_PAGES];
|
||||
static uint8_t mus_blk; /* блок EMM под трек */
|
||||
static uint8_t mus_pages; /* сколько страниц реально занято */
|
||||
static uint16_t mus_blocks; /* длина трека в порциях по 128 Б */
|
||||
|
||||
/* "MUS\mNN_KK.bin" — ровно 8.3, как требует DSS. */
|
||||
static void mus_name(char *buf, uint8_t id, uint8_t chunk)
|
||||
{
|
||||
buf[0] = 'M'; buf[1] = 'U'; buf[2] = 'S'; buf[3] = '\\';
|
||||
buf[4] = 'm';
|
||||
buf[5] = (char)('0' + id / 10);
|
||||
buf[6] = (char)('0' + id % 10);
|
||||
buf[7] = '_';
|
||||
buf[8] = (char)('0' + chunk / 10);
|
||||
buf[9] = (char)('0' + chunk % 10);
|
||||
buf[10] = '.'; buf[11] = 'b'; buf[12] = 'i'; buf[13] = 'n';
|
||||
buf[14] = 0;
|
||||
}
|
||||
|
||||
void pop_music_stop(void) __banked
|
||||
{
|
||||
pop_mus_left = 0;
|
||||
}
|
||||
|
||||
void pop_music_free(void) __banked
|
||||
{
|
||||
pop_mus_left = 0;
|
||||
if (mus_blk) {
|
||||
mem_free_block(mus_blk);
|
||||
mus_blk = 0;
|
||||
}
|
||||
mus_pages = 0;
|
||||
mus_blocks = 0;
|
||||
}
|
||||
|
||||
int8_t pop_music_load(uint8_t id) __banked
|
||||
{
|
||||
char name[15];
|
||||
uint8_t i, pages;
|
||||
|
||||
if (id >= POP_MUS_COUNT) return -1;
|
||||
pages = pop_mus_tbl[id].pages;
|
||||
if (!pages || pages > POP_MUS_PAGES) return -1;
|
||||
|
||||
pop_music_free();
|
||||
mus_blk = mem_alloc_pages(pages);
|
||||
if (!mus_blk) return -1;
|
||||
for (i = 0; i < pages; i++) {
|
||||
pop_mus_page[i] = mem_get_page(mus_blk, i);
|
||||
mus_name(name, id, i);
|
||||
/* Последний кусок короче страницы — читаем сколько есть; ошибку
|
||||
* ловим по отрицательному коду, а не по «не 16384». */
|
||||
if (!pop_mus_page[i] ||
|
||||
bank_load_file(pop_mus_page[i], 0, name, 16384) < 0) {
|
||||
pop_music_free();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
mus_pages = pages;
|
||||
mus_blocks = pop_mus_tbl[id].blocks;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pop_music_play(void) __banked
|
||||
{
|
||||
if (!mus_pages) return;
|
||||
/* Порядок важен: сначала позиция, потом длина. Насос стартует ровно
|
||||
* с той порции, где left стал ненулевым, и промежуточное состояние
|
||||
* «играем, но курсор от прошлого трека» не возникает. */
|
||||
pop_mus_pg = 0;
|
||||
pop_mus_ptr = 0;
|
||||
pop_mus_left = mus_blocks;
|
||||
}
|
||||
|
||||
uint8_t pop_music_busy(void) __banked
|
||||
{
|
||||
return (uint8_t)(pop_mus_left != 0);
|
||||
}
|
||||
Reference in New Issue
Block a user