/* * _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; }