f159aa47e1
Две разные болячки, обе разобраны записью звука MAME в WAV. libc: буфер CBL железо не чистит, а запись в порт управления сразу пускает воспроизведение с нулевого слота — первые 256 сэмплов (23,4 мс) уходит то, что лежало раньше. Своими данными звук идёт лишь с третьей половины: прерывание приходит на 128-м слоте и ставит указатель на противоположную половину. _cbl_prime заливает буфер тишиной сразу после включения (раньше нельзя — запись проходит только при поднятом bit7). В MAME это немо (эмулируемый буфер стартует нулями при двухдополнительном ЦАП), на железе это ровно тот мусор, что ловился на тестовых примерах CBL. PoP: на выходе по ESC звучало ровно 11 мс шума на полной громкости — один пропущенный блок (128 сэмплов). Причина: pop_shutdown освобождал атласы и графику через ESTEX при открытом звуке, насос не успевал долить. Звук гасим первым действием. Проверено записью — всплеска больше нет. Заодно записан разбор стартового всплеска (sound_plan.md §10): это не мусор, а gate_closing_fast из левой комнаты, обрываемый soft_land. Обрыв одноголосьем — поведение оригинала (play_digi_sound начинается с stop_digi, seg009.c:2402). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
55 lines
3.8 KiB
C
55 lines
3.8 KiB
C
/*
|
||
* _cbl_prime — залить аппаратный буфер CBL тишиной СРАЗУ после включения.
|
||
*
|
||
* ЗАЧЕМ. Буфер CBL (256 слотов, две половины по 128) железо НЕ чистит ни
|
||
* при сбросе, ни при записи в порт управления — там остаётся то, что лежало
|
||
* раньше: хвост прошлой сессии, а на холодном старте вообще неинициализи-
|
||
* рованное содержимое. Между тем запись в порт управления взводит счётчик
|
||
* воспроизведения в 0 и тут же пускает таймер, поэтому первое, что уходит в
|
||
* ЦАП, — эти самые 256 слотов. На слух это кусок мусора в момент включения
|
||
* звука (поймано пользователем на старте PoP, 2026-08-20).
|
||
*
|
||
* Разбор по MAME (src/mame/sinclair/sprinter.cpp, единственная доступная нам
|
||
* модель железа):
|
||
* - case 0x89 (порт управления): `m_cbl_cnt = 0; m_cbl_wa = 0;` и завод
|
||
* таймера — буфер при этом не трогается;
|
||
* - cbl_tick: играет `m_cbl_data[m_cbl_cnt++]`, а прерывание «долей
|
||
* половину» поднимает только на `!(m_cbl_cnt & 0x7f)`, ставя указатель
|
||
* записи на ПРОТИВОПОЛОЖНУЮ половину (`m_cbl_wa = m_cbl_cnt ^ 0x80`).
|
||
* Отсюда ровно: первое прерывание приходит, когда сыграна половина 0..127,
|
||
* и приложение заполняет её же, пока играет половина 128..255. Своими
|
||
* данными звук пойдёт только с третьей половины, то есть НЕЗАПОЛНЕННЫМИ
|
||
* уходят все 256 слотов — 23,4 мс на 10 937,5 Гц.
|
||
*
|
||
* ПОЧЕМУ ИМЕННО ТАК ЛЕЧИТСЯ. Заранее, до включения, залить нельзя: запись
|
||
* в порт данных попадает в буфер только при уже поднятом bit7 (`case 0x88:
|
||
* if (cbl_mode())`). Значит заливаем сразу ПОСЛЕ включения — за 256 OUT'ов
|
||
* (~6 400 тактов, 0,3 мс) таймер успевает продвинуться на два-три слота, и
|
||
* наружу проскакивает пара сэмплов вместо 23 мс. Гонки с насосом нет:
|
||
* первое прерывание будет только на 128-м слоте, а зовут нас под DI.
|
||
*
|
||
* Слотов ровно 256 НЕЗАВИСИМО от формата (в 16-бит слот держит целый
|
||
* сэмпл), поэтому и записей всегда 256 — байт тишины разный: 0x80 для
|
||
* 8-бит беззнакового, 0x00 для 16-бит.
|
||
*/
|
||
|
||
#include "_cbl.h"
|
||
|
||
void _cbl_prime(uint8_t silence) __naked
|
||
{
|
||
(void)silence;
|
||
__asm
|
||
;; __sdcccall(1): uint8_t-аргумент уже в A.
|
||
;; C = порт данных, B = счётчик (B=0 значит 256 итераций). Пишем
|
||
;; через `out (c),a`, а не `out (n),a`: у OTIR в этот порт старший
|
||
;; байт адреса тоже задаёт B и меняется по ходу — повторяем ровно
|
||
;; ту же картину на шине, чтобы не зависеть от декодирования.
|
||
ld c, #_CBL_DATA_PORT
|
||
ld b, #0
|
||
00001$:
|
||
out (c), a
|
||
djnz 00001$
|
||
ret
|
||
__endasm;
|
||
}
|