Звук PoP: приоритеты и перебиваемость вместо «всегда перебивать»
Пользователь услышал расхождение с SDLPoP: у нас решётка обрывалась приземлением Кида, в оригинале доигрывает до конца, а приземления не слышно. Оказалось, упущен целый механизм. play_sound (seg000:12C5) НЕ играет, а только номинирует кандидата на кадр — из нескольких выживает важнейший (меньше prio = важнее, при равенстве последний). play_next_sound (seg000:1304) раз в кадр решает, запускать ли: можно, только если ничего не играет ЛИБО текущий помечен перебиваемым и новый не менее важен. Иначе номинант выбрасывается — очереди в оригинале нет. Отсюда всё, что слышно: gate_closing_fast неперебиваем и доигрывает целиком; челюсти (prio 0x10) всегда важнее решётки (0x32), поэтому решётка звучит только в паузах между укусами. Таблицы из SDLPoP с учётом fix_sound_priorities (в его config.h он определён безусловно). Створка двери уровня — единственная запись, правимая на ходу, вынесена в отдельный байт. Добавлен пропущенный stop_sounds на завершении открытия двери (seg007:455). Проверено записью MAME: старт уровня 1 был 135+210 мс (решётка, обрезанная на 80 мс), стал один всплеск 455 мс с корреляцией огибающей +0,889 со звуком 6. Заведён BUG-SND-FIRSTRUN: искажение первого эффекта при первом запуске после загрузки системы — вероятно, лечится _cbl_prime, но проверить можно только на железе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -87,13 +87,98 @@ int pop_sfx_fill(uint16_t n)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- ОЧЕРЕДЬ И ПРИОРИТЕТЫ (порт seg000:12C5 + seg000:1304) ------------ *
|
||||
*
|
||||
* Оригинал НЕ перебивает играющий звук чем попало, и это слышно. Модель
|
||||
* ровно такая:
|
||||
*
|
||||
* play_sound(id) — только НОМИНИРУЕТ кандидата на кадр; из нескольких
|
||||
* за кадр остаётся ВАЖНЕЙШИЙ (меньше prio = важнее,
|
||||
* при равенстве побеждает последний — сравнение <=);
|
||||
* play_next_sound() — раз в кадр решает, запускать ли: можно, если ничего
|
||||
* не играет, ЛИБО текущий звук помечен «перебиваемым»
|
||||
* И новый не менее важен. Иначе номинант ВЫБРАСЫВАЕТСЯ
|
||||
* (next_sound = -1 безусловно), а не встаёт в очередь.
|
||||
*
|
||||
* Отсюда то, что слышно в игре: `gate_closing_fast` (6) неперебиваем и
|
||||
* доигрывает до звонкого конца, а приземление Кида в этот момент пропадает
|
||||
* совсем; челюсти (47, prio 0x10) всегда важнее решётки (4, prio 0x32), и
|
||||
* решётка звучит только в паузах между укусами.
|
||||
*
|
||||
* Таблицы — из SDLPoP (sound_prio_table seg000:1528, sound_interruptible
|
||||
* data.h:433) с правками fix_sound_priorities(): в config.h SDLPoP
|
||||
* FIX_SOUND_PRIORITIES определён безусловно, то есть сравниваем мы себя
|
||||
* именно с исправленным вариантом (10 -> 0x0D, 48 -> 0x15, 49 перебиваем).
|
||||
*/
|
||||
|
||||
/* Приоритет: МЕНЬШЕ значит ВАЖНЕЕ. Музыкальные id тут тоже есть — они
|
||||
* никогда не номинируются (нет оцифровки), но индексация должна совпадать
|
||||
* с оригиналом, иначе таблицу не сверить глазами. */
|
||||
static const uint8_t snd_prio[POP_SND_COUNT] = {
|
||||
0x14, 0x1E, 0x23, 0x66, 0x32, 0x37, 0x30, 0x30, 0x4B, 0x50, /* 0.. 9 */
|
||||
0x0D, 0x12, 0x0C, 0x0B, 0x69, 0x6E, 0x73, 0x78, 0x7D, 0x82, /* 10..19 */
|
||||
0x91, 0x96, 0x9B, 0xA0, 0x01, 0x01, 0x01, 0x01, 0x01, 0x13, /* 20..29 */
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, /* 30..39 */
|
||||
0x01, 0x01, 0x01, 0x01, 0x87, 0x8C, 0x0F, 0x10, 0x15, 0x16, /* 40..49 */
|
||||
0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01 /* 50..56 */
|
||||
};
|
||||
|
||||
/* «Играющий звук можно перебить» — битовая карта (бит id&7 байта id>>3):
|
||||
* 57 флагов уложены в 8 байт, потому что в резиденте W1/W2 каждая сотня
|
||||
* байт на счету, а читаем мы их раз в кадр. */
|
||||
static const uint8_t snd_intr[8] = {
|
||||
/* 0.. 7 */ (uint8_t)(0<<0 | 1<<1 | 1<<2 | 1<<3 | 1<<4 | 1<<5 | 0<<6 | 1<<7),
|
||||
/* 8..15 */ (uint8_t)(1<<0 | 1<<1 | 1<<2 | 1<<3 | 1<<4 | 1<<5 | 0<<6 | 0<<7),
|
||||
/*16..23 */ (uint8_t)(1<<0 | 1<<1 | 0<<2 | 1<<3 | 1<<4 | 1<<5 | 1<<6 | 1<<7),
|
||||
/*24..31 */ (uint8_t)(0<<0 | 0<<1 | 0<<2 | 0<<3 | 0<<4 | 1<<5 | 0<<6 | 0<<7),
|
||||
/*32..39 */ 0,
|
||||
/*40..47 */ (uint8_t)(0<<0 | 0<<1 | 0<<2 | 0<<3 | 0<<4 | 0<<5 | 0<<6 | 1<<7),
|
||||
/*48..55 */ 0,
|
||||
/*56.. */ 0
|
||||
};
|
||||
|
||||
/* Створка двери уровня (15) — единственная запись, которую оригинал правит
|
||||
* НА ХОДУ (seg007:442/464): при закрытии её перебивать можно, при открытии
|
||||
* нельзя (иначе съезд створки обрывается и «конца» не слышно). Держим
|
||||
* отдельным байтом, чтобы таблица осталась в ПЗУ. */
|
||||
uint8_t pop_sfx_slide_intr;
|
||||
|
||||
/* Номинант кадра, ЗАПИСАННЫЙ КАК id+1: ноль значит «никого». Так сделано
|
||||
* ради crt0 — он зануляет _DATA, а «нет номинанта» у оригинала это -1;
|
||||
* со смещением на единицу начальное состояние получается даром. */
|
||||
static uint8_t snd_next1;
|
||||
static uint8_t snd_curr; /* что играет — для prio/intr */
|
||||
|
||||
static uint8_t snd_interruptible(uint8_t id)
|
||||
{
|
||||
if (id == 15) return pop_sfx_slide_intr;
|
||||
return (uint8_t)(snd_intr[id >> 3] & (uint8_t)(1 << (id & 7)));
|
||||
}
|
||||
|
||||
/* ---- запуск / останов ------------------------------------------------ */
|
||||
|
||||
void pop_sfx_play(uint8_t id)
|
||||
{
|
||||
uint8_t pg;
|
||||
uint16_t off, len;
|
||||
if (!pop_snd_ok || id >= POP_SND_COUNT) return;
|
||||
/* Нет оцифровки (музыка) — не номинируем вовсе: порт проверки
|
||||
* `if (NULL == sound_pointers[id]) return;`. Иначе музыкальный id
|
||||
* выигрывал бы конкурс и глушил живой эффект. */
|
||||
if (pop_snd_tbl[id].len == 0) return;
|
||||
if (!snd_next1 || snd_prio[id] <= snd_prio[snd_next1 - 1])
|
||||
snd_next1 = (uint8_t)(id + 1);
|
||||
}
|
||||
|
||||
void pop_sfx_tick(void)
|
||||
{
|
||||
uint8_t id, pg;
|
||||
uint16_t off, len;
|
||||
if (!snd_next1) return;
|
||||
id = (uint8_t)(snd_next1 - 1);
|
||||
snd_next1 = 0; /* номинант живёт ровно кадр */
|
||||
if (sfx_left != 0 && /* что-то играет... */
|
||||
(!snd_interruptible(snd_curr) || snd_prio[id] > snd_prio[snd_curr]))
|
||||
return; /* ...и уступать не обязано */
|
||||
snd_curr = id;
|
||||
pg = pop_snd_tbl[id].page;
|
||||
off = pop_snd_tbl[id].off;
|
||||
len = pop_snd_tbl[id].len;
|
||||
@@ -103,7 +188,7 @@ void pop_sfx_play(uint8_t id)
|
||||
IRQ_DISABLE();
|
||||
sfx_pg = pg;
|
||||
sfx_ptr = off;
|
||||
sfx_left = len; /* len == 0 — просто глушим */
|
||||
sfx_left = len;
|
||||
IRQ_ENABLE();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user