libc/cbl: выключение CBL больше не оставляет железо петь одну ноту
cbl_close() не пишет в порт управления ноль: пока gfx_wait_vsync держит ссылку ради бита луча (в PoP её берёт pop_pace_arm на всю программу), bit7 остаётся включённым. А буфер CBL (256 слотов) при включённом bit7 крутится по кругу и не чистится ничем — железо бесконечно поёт хвост последнего сэмпла, тоном и громкостью по последней мелодии. Пойман пользователем на железе: Ctrl+S во время музыки и пропуск заставки давали ноту до следующего cbl_open. В MAME не воспроизводится: "немым" кодом частоты был reserved-код 2, у которого divs[2]==0 и таймер не заводится вовсе. На железе reserved-коды не определены, ЦАП тактируется — тишина держалась на свойстве эмулятора, а не железа. Теперь тишину даёт СОДЕРЖИМОЕ БУФЕРА: - _cbl_port_sync() после каждой записи в порт управления зовёт _cbl_prime(0x80). Это закрывает и паузу звука, и включение bit7 ради луча на холодном старте (в буфере лежал мусор от прошлой программы), и полное выключение — при bit7=0 те же 256 записей уходят прямо в ЦАП обычного COVOX и центруют его, снимая щелчок; - _CBL_VSYNC_FREQ переведён с reserved-кода 2 на документированный 8 (7,8125 кГц): поведение определено и на железе, и в MAME, прерывания по-прежнему выключены (bit4=0), а бит 7 порта 0xFE трамплин смотрит только при живом хуке насоса; - cbl_close() зовёт sync внутри той же DI-скобки, где снимает хук, иначе насос долил бы буфер уже после заливки. Цена: +2 Б программам со звуком, +11 Б графическим (тянется _cbl_prime следом за gfx_wait_vsync), 256 OUT'ов (~0,3 мс) на редкое событие — эталоны cblstream/cbltest/cblwav/gfx_dbuf обновлены. Рост atlas в size-check к этой правке отношения не имеет (замерен тот же и без неё). Проверено: кодоген _cbl_port.asm; SprPoP пересобран и прогнан в MAME (титры → пропуск заставки → уровень 1, пейсинг по лучу жив). Сам баг проверяется только на железе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
This commit is contained in:
@@ -15,9 +15,9 @@ bios_text 4461
|
|||||||
blitperf 5860
|
blitperf 5860
|
||||||
blitw 4939
|
blitw 4939
|
||||||
cat 913
|
cat 913
|
||||||
cblstream 5860
|
cblstream 5862
|
||||||
cbltest 6006
|
cbltest 6008
|
||||||
cblwav 6099
|
cblwav 6101
|
||||||
conio 4605
|
conio 4605
|
||||||
conio2 3929
|
conio2 3929
|
||||||
convbench 3508
|
convbench 3508
|
||||||
@@ -28,7 +28,7 @@ fdmax 6023
|
|||||||
filetest 10547
|
filetest 10547
|
||||||
fpsdiv 4870
|
fpsdiv 4870
|
||||||
gets 509
|
gets 509
|
||||||
gfx_dbuf 5131
|
gfx_dbuf 5142
|
||||||
gfx_demo 4114
|
gfx_demo 4114
|
||||||
gfxbanks 6044
|
gfxbanks 6044
|
||||||
hello 4167
|
hello 4167
|
||||||
|
|||||||
|
+31
-9
@@ -26,12 +26,29 @@
|
|||||||
*
|
*
|
||||||
* _cbl_port_ref/_cbl_port_unref (_cbl_port.c) — общий счётчик желающих
|
* _cbl_port_ref/_cbl_port_unref (_cbl_port.c) — общий счётчик желающих
|
||||||
* держать bit7 порта 0x004E включённым НЕЗАВИСИМО от полноценной CBL-
|
* держать bit7 порта 0x004E включённым НЕЗАВИСИМО от полноценной CBL-
|
||||||
* сессии. Нужно gfx_wait_vsync() (см. libc/gfx/gfx_wait_vsync.c): бит
|
* сессии. Нужно gfx_wait_vsync() (см. libbgi/common/gfx_wait_vsync.c):
|
||||||
* 5 порта 0xFE отражает позицию луча (см. MAME sprinter.cpp kbd_fe_r),
|
* бит 5 порта 0xFE отражает позицию луча (см. MAME sprinter.cpp
|
||||||
* но доступен ТОЛЬКО пока включён cbl_mode(). Если реальный CBL уже
|
* kbd_fe_r), но доступен ТОЛЬКО пока включён cbl_mode(). Если реальный
|
||||||
* открыт — портом владеет он, ref/unref его не трогают; иначе включают
|
* CBL уже открыт — портом владеет он, ref/unref его не трогают; иначе
|
||||||
* bit7 "немым" кодом частоты _CBL_VSYNC_FREQ (таймер CBL не заводится
|
* включают bit7 "немым" кодом частоты _CBL_VSYNC_FREQ и заливают
|
||||||
* — divs[2]=0 в MAME — ни звука, ни прерываний, только синхро-бит).
|
* аппаратный буфер тишиной — БЕЗ ЗАЛИВКИ ЖЕЛЕЗО ПОЁТ (см. ниже).
|
||||||
|
*
|
||||||
|
* ЖЕЛЕЗО ПОЁТ ТО, ЧТО ЛЕЖИТ В БУФЕРЕ. Буфер CBL — 256 слотов, которые
|
||||||
|
* при включённом bit7 крутятся ПО КРУГУ и которые никто не чистит;
|
||||||
|
* фирменный пример Sprinter Team (docs/converted/Forum.txt, "Самый
|
||||||
|
* простой пример") на этом и построен: залил 256 отсчётов — "должен
|
||||||
|
* пищать одну ноту". Значит любое состояние "bit7 включён, а насос не
|
||||||
|
* работает" — это бесконечная нота из хвоста последнего сэмпла.
|
||||||
|
*
|
||||||
|
* В MAME этого не видно: там код частоты 2 не заводил таймер вовсе
|
||||||
|
* (divs[2]=0, sprinter.cpp:822), поэтому "немой" режим был тих даром.
|
||||||
|
* На железе код 2 — reserved (Forum.txt:704), поведение не определено, и
|
||||||
|
* ЦАП тактируется: пользователь поймал бесконечную ноту после Ctrl+S и
|
||||||
|
* после пропуска заставки (2026-09-02). Поэтому теперь: (1) частота —
|
||||||
|
* документированная, самая медленная; (2) буфер ВСЕГДА оставляем залитым
|
||||||
|
* тишиной — и на входе в "немой" режим, и при полном выключении (там те
|
||||||
|
* же записи центруют ЦАП обычного COVOX, bit7=0 → порт данных идёт прямо
|
||||||
|
* в ЦАП). Цена — 256 OUT'ов (~0,3 мс) на редкое событие.
|
||||||
*/
|
*/
|
||||||
#ifndef _CBL_INTERNAL_H
|
#ifndef _CBL_INTERNAL_H
|
||||||
#define _CBL_INTERNAL_H
|
#define _CBL_INTERNAL_H
|
||||||
@@ -42,7 +59,10 @@
|
|||||||
#define _CBL_CTRL_PORT 0x004E
|
#define _CBL_CTRL_PORT 0x004E
|
||||||
#define _CBL_DATA_PORT 0x4F
|
#define _CBL_DATA_PORT 0x4F
|
||||||
#define _CBL_ACCEL_PAGE 0xFD /* спец-страница EMM: запись на W3@0xC000 идёт в CBL */
|
#define _CBL_ACCEL_PAGE 0xFD /* спец-страница EMM: запись на W3@0xC000 идёт в CBL */
|
||||||
#define _CBL_VSYNC_FREQ 0x02 /* "немой" код частоты — divs[2]=0 в MAME, таймер не идёт */
|
#define _CBL_VSYNC_FREQ 0x08 /* "немой" режим: самый медленный ДОКУМЕНТИРОВАННЫЙ
|
||||||
|
* код (7,8125 кГц). Тишина обеспечена не
|
||||||
|
* остановкой таймера (это свойство MAME, не
|
||||||
|
* железа), а залитым тишиной буфером. */
|
||||||
|
|
||||||
extern uint16_t _cbl_block; /* размер блока: 128 или 256 (по формату) */
|
extern uint16_t _cbl_block; /* размер блока: 128 или 256 (по формату) */
|
||||||
extern uint16_t _cbl_reqs; /* блоков-запросов обслужено */
|
extern uint16_t _cbl_reqs; /* блоков-запросов обслужено */
|
||||||
@@ -62,8 +82,10 @@ int _cbl_open_raw(uint8_t freq_code, uint8_t fmt, uint8_t pump_mode,
|
|||||||
uint8_t underrun_mode, cbl_fill_fn fill);
|
uint8_t underrun_mode, cbl_fill_fn fill);
|
||||||
|
|
||||||
/* Залить весь аппаратный буфер (256 слотов) байтом тишины сразу после
|
/* Залить весь аппаратный буфер (256 слотов) байтом тишины сразу после
|
||||||
* включения — иначе первые 23 мс играет то, что лежало в буфере раньше.
|
* ЛЮБОЙ записи в порт управления: при включении — иначе первые 23 мс
|
||||||
* Подробный разбор — в шапке _cbl_prime.c. */
|
* играет то, что лежало в буфере раньше; при выключении — иначе железо
|
||||||
|
* крутит хвост последнего сэмпла бесконечной нотой. Подробный разбор —
|
||||||
|
* в шапке _cbl_prime.c. */
|
||||||
void _cbl_prime(uint8_t silence);
|
void _cbl_prime(uint8_t silence);
|
||||||
|
|
||||||
/* ISR-насосы: вызвать fill(), при недоливе — тишина по режиму (один
|
/* ISR-насосы: вызвать fill(), при недоливе — тишина по режиму (один
|
||||||
|
|||||||
+20
-3
@@ -7,9 +7,23 @@
|
|||||||
*
|
*
|
||||||
* Если реальный CBL уже открыт (_irq_cbl_hook != 0) — портом владеет
|
* Если реальный CBL уже открыт (_irq_cbl_hook != 0) — портом владеет
|
||||||
* он, ref/unref его не трогают вообще (это уже нужное состояние).
|
* он, ref/unref его не трогают вообще (это уже нужное состояние).
|
||||||
* Иначе включаем/выключаем bit7 "немым" кодом частоты _CBL_VSYNC_FREQ
|
* Иначе включаем/выключаем bit7 "немым" кодом частоты _CBL_VSYNC_FREQ.
|
||||||
* — таймер CBL при этом коде не заводится (divs[2]=0 в MAME), значит
|
*
|
||||||
* ни звука, ни прерываний, только доступ к синхро-биту.
|
* ТИШИНУ ДАЁТ ЗАЛИТЫЙ БУФЕР, А НЕ ОСТАНОВЛЕННЫЙ ТАЙМЕР. Раньше здесь
|
||||||
|
* стоял reserved-код частоты 2 — ровно потому, что в MAME при нём не
|
||||||
|
* заводится таймер (divs[2]=0). На железе поведение reserved-кодов не
|
||||||
|
* определено, ЦАП тактируется, и включённый bit7 поверх старого буфера
|
||||||
|
* даёт бесконечную ноту из хвоста последнего сэмпла (пойман Ctrl+S во
|
||||||
|
* время музыки, 2026-09-02; разбор — в шапке _cbl.h). Поэтому после
|
||||||
|
* КАЖДОЙ записи в порт управления заливаем все 256 слотов тишиной:
|
||||||
|
* - bit7 включён — крутится тишина (беззвучно на любом железе);
|
||||||
|
* - bit7 выключен — те же записи уходят в ЦАП обычного COVOX и
|
||||||
|
* центруют его на 0x80, снимая щелчок/постоянное смещение.
|
||||||
|
* Оба состояния редкие (старт пейсинга, пауза звука, выход), 256 OUT'ов
|
||||||
|
* — около 0,3 мс.
|
||||||
|
*
|
||||||
|
* Цена для ГРАФИЧЕСКОЙ программы без звука: _cbl_prime (~10 Б кода)
|
||||||
|
* приезжает следом за gfx_wait_vsync. Дешевле, чем гудящее железо.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "_cbl.h"
|
#include "_cbl.h"
|
||||||
@@ -19,6 +33,9 @@ void _cbl_port_sync(void)
|
|||||||
{
|
{
|
||||||
if (_irq_cbl_hook) return; /* реальный CBL сам владеет портом */
|
if (_irq_cbl_hook) return; /* реальный CBL сам владеет портом */
|
||||||
_cbl_ctrl(_cbl_port_refs ? (uint8_t)(0x80 | _CBL_VSYNC_FREQ) : 0);
|
_cbl_ctrl(_cbl_port_refs ? (uint8_t)(0x80 | _CBL_VSYNC_FREQ) : 0);
|
||||||
|
/* Формат "немого" режима всегда 8-битный (bit5 не выставлен), поэтому
|
||||||
|
* тишина здесь — 0x80 независимо от того, каким был закрытый сеанс. */
|
||||||
|
_cbl_prime(0x80);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _cbl_port_ref(void)
|
void _cbl_port_ref(void)
|
||||||
|
|||||||
+15
-1
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* _cbl_prime — залить аппаратный буфер CBL тишиной СРАЗУ после включения.
|
* _cbl_prime — залить аппаратный буфер CBL тишиной СРАЗУ после записи в
|
||||||
|
* порт управления: и при включении, и при выключении.
|
||||||
*
|
*
|
||||||
* ЗАЧЕМ. Буфер CBL (256 слотов, две половины по 128) железо НЕ чистит ни
|
* ЗАЧЕМ. Буфер CBL (256 слотов, две половины по 128) железо НЕ чистит ни
|
||||||
* при сбросе, ни при записи в порт управления — там остаётся то, что лежало
|
* при сбросе, ни при записи в порт управления — там остаётся то, что лежало
|
||||||
@@ -31,6 +32,19 @@
|
|||||||
* Слотов ровно 256 НЕЗАВИСИМО от формата (в 16-бит слот держит целый
|
* Слотов ровно 256 НЕЗАВИСИМО от формата (в 16-бит слот держит целый
|
||||||
* сэмпл), поэтому и записей всегда 256 — байт тишины разный: 0x80 для
|
* сэмпл), поэтому и записей всегда 256 — байт тишины разный: 0x80 для
|
||||||
* 8-бит беззнакового, 0x00 для 16-бит.
|
* 8-бит беззнакового, 0x00 для 16-бит.
|
||||||
|
*
|
||||||
|
* ВТОРАЯ РОЛЬ — ГАШЕНИЕ (добавлена 2026-09-02, _cbl_port_sync). Запись
|
||||||
|
* в порт управления НЕ чистит буфер и в обратную сторону: пока bit7
|
||||||
|
* включён (а его держит gfx_wait_vsync ради бита луча), железо крутит
|
||||||
|
* оставшиеся 256 байт по кругу — это ровно "одна нота" из фирменного
|
||||||
|
* примера Forum.txt, только незваная. Поэтому после каждой записи в
|
||||||
|
* порт управления зовём эту же функцию:
|
||||||
|
* - bit7 остался включён — по кругу крутится тишина;
|
||||||
|
* - bit7 снят — те же 256 записей идут прямо в ЦАП обычного COVOX
|
||||||
|
* (MAME sprinter.cpp, `case 0x88` без cbl_mode()) и оставляют его в
|
||||||
|
* нуле 0x80, а не на последнем сэмпле.
|
||||||
|
* Гонки с насосом нет и здесь: при гашении хук уже снят, при включении
|
||||||
|
* первое прерывание будет только на 128-м слоте.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "_cbl.h"
|
#include "_cbl.h"
|
||||||
|
|||||||
+11
-2
@@ -10,7 +10,16 @@
|
|||||||
* Порт 0x004E не гасится напрямую — вместо этого _cbl_port_sync()
|
* Порт 0x004E не гасится напрямую — вместо этого _cbl_port_sync()
|
||||||
* пересчитывает нужное значение: если gfx_wait_vsync() ещё держит
|
* пересчитывает нужное значение: если gfx_wait_vsync() ещё держит
|
||||||
* ссылку (_cbl_port_refs > 0), останется "немой" режим ради бита
|
* ссылку (_cbl_port_refs > 0), останется "немой" режим ради бита
|
||||||
* синхронизации; иначе порт гасится полностью.
|
* синхронизации; иначе порт гасится полностью. Он же заливает
|
||||||
|
* аппаратный буфер тишиной — БЕЗ ЭТОГО ЗАКРЫТИЕ CBL НЕ ЗАКРЫВАЕТ ЗВУК:
|
||||||
|
* пока bit7 держит vsync-ссылка, железо крутит по кругу оставшиеся в
|
||||||
|
* буфере 256 байт последнего сэмпла и поёт ноту до следующего
|
||||||
|
* cbl_open() (пойман Ctrl+S во время музыки, 2026-09-02; полный разбор
|
||||||
|
* — в шапке _cbl.h). В MAME это не воспроизводится.
|
||||||
|
*
|
||||||
|
* Порядок обязателен: сначала снять хук (иначе насос долил бы буфер уже
|
||||||
|
* после заливки), и только потом _cbl_port_sync с заливкой — вся
|
||||||
|
* скобка под DI, чтобы между снятием хука и тишиной ничего не влезло.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "_cbl.h"
|
#include "_cbl.h"
|
||||||
@@ -21,7 +30,7 @@ void cbl_close(void)
|
|||||||
if (!_irq_cbl_hook) return;
|
if (!_irq_cbl_hook) return;
|
||||||
IRQ_DISABLE();
|
IRQ_DISABLE();
|
||||||
_irq_cbl_hook = 0;
|
_irq_cbl_hook = 0;
|
||||||
|
_cbl_port_sync(); /* режим порта + заливка буфера тишиной */
|
||||||
IRQ_ENABLE();
|
IRQ_ENABLE();
|
||||||
_cbl_port_sync();
|
|
||||||
_irq_table_unref();
|
_irq_table_unref();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user