libc: IM2 Phase 2b — звук CBL/COVOX через callback fill(), без кольца libc
CBL уже имеет аппаратный буфер 256 Б (2×128, двойная буферизация на стороне железа) — держать поверх него ещё одно кольцо в libc было бы лишней копией. cbl_open(freq, fmt, pump_mode, underrun_mode, fill) регистрирует callback, вызываемый из ISR за очередным блоком; он сам пропихивает данные приложения (откуда угодно) через cbl_push_otir()/ cbl_push_accel() — без промежуточного буфера. - два насоса: OTIR (порт 0x4F) и ACCEL (акселератор, спец-страница EMM 0xFD@0xC000); OTIR+16-бит запрещён (EINVAL) — по исходнику MAME порт данных физически не может собрать 16-бит сэмпл из пары байт; - форматы CBL_FMT_MONO8/16/STEREO8/16, частоты 7.8..109к; - CBL_UNDERRUN_APP (по умолчанию, недолив не наша забота) / CBL_UNDERRUN_SILENCE (буфер тишины malloc'ится только в этом режиме); - tests/cbltest: матрица 64 комбинации (2 насоса × 8 форматов × 4 частоты); tests/cblwav: banked-стрим речи с дискеты (физстраницы кэшированы заранее — mem_get_page нельзя звать из fill()/ISR); tests/cblstream: единственный случай с собственным кольцом уровня приложения (диск нельзя читать из fill()). Verified в MAME 2026-07-07 — все три теста работают. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* cbl.h — COVOX-Blaster: потоковый вывод звука через прерывания.
|
||||
*
|
||||
* CBL — COVOX с буферным ОЗУ (256 Б, аппаратно разбито на ДВЕ половины
|
||||
* по 128 Б — двойная буферизация целиком на стороне железа, см.
|
||||
* официальную документацию "5.3 COVOX-Blaster"): бит 7 порта 0xFE
|
||||
* отражает, какая половина СЕЙЧАС играет; когда она флипает, вектор
|
||||
* 0xFF поднимает прерывание — пора долить НЕАКТИВНУЮ половину.
|
||||
*
|
||||
* Библиотека НЕ держит собственного кольца — оно было бы лишним
|
||||
* вторым буфером поверх аппаратного. Вместо этого при cbl_open()
|
||||
* регистрируется callback fill(): ISR вызывает его напрямую, а
|
||||
* callback САМ пропихивает n байт из данных приложения (откуда бы они
|
||||
* ни жили — статический массив, банковая EMM-страница, файл) прямо в
|
||||
* CBL через cbl_push_otir()/cbl_push_accel() — без промежуточной
|
||||
* копии в libc.
|
||||
*
|
||||
* fill(n) ОБЯЗАН быть быстрым (то же ограничение, что у irq_install()
|
||||
* — никаких ESTEX/BIOS/gfx-вызовов, только память и один push) и
|
||||
* вернуть ненулевое значение, если реально пропихнул n байт; 0 — если
|
||||
* данных сейчас нет (недолив/underrun).
|
||||
*
|
||||
* Формат данных (bits 6/5 порта 0x4E; см. Forum.txt "Звук через
|
||||
* COVOX-Blaster"):
|
||||
* 8-бит: unsigned, центр 0x80 (моно — один байт/сэмпл;
|
||||
* стерео — чередование L/R байт)
|
||||
* 16-бит: signed, центр 0x0000, little-endian (моно — int16/сэмпл;
|
||||
* стерео — чередование L/R int16)
|
||||
* Блок запроса — 128 байт для 8-бит, 256 байт для 16-бит (не зависит
|
||||
* от моно/стерео — это ПОЛНЫЙ байтовый блок, раскладка каналов внутри
|
||||
* него на размер не влияет); n, приходящий в fill(), равен этому блоку.
|
||||
*
|
||||
* Способ выдачи блока — CBL_PUMP_* (см. cbl_open):
|
||||
* OTIR — cbl_push_otir(), блочный вывод в порт 0x4F; НЕ умеет
|
||||
* 16-бит (см. ниже) — cbl_open(..., 16-бит формат,
|
||||
* CBL_PUMP_OTIR, ...) вернёт EINVAL.
|
||||
* ACCEL — cbl_push_accel(), запись в спец-страницу EMM 0xFD
|
||||
* (маппится в W3 на 0xC000) через акселератор (см.
|
||||
* docs/converted/accel_r.txt); единственный способ получить
|
||||
* настоящий 16-бит сэмпл.
|
||||
*
|
||||
* OTIR + 16-бит запрещён не просто "хуже", а физически не работает:
|
||||
* по исходнику эмулятора (MAME sprinter.cpp) порт данных 0x4F ВСЕГДА
|
||||
* кладёт байт как есть в один слот, не собирая пару байт в 16-бит
|
||||
* значение — это умеет только акселераторный путь (страница 0xFD).
|
||||
*
|
||||
* Поведение при недоливе — CBL_UNDERRUN_* (4-й параметр cbl_open):
|
||||
* CBL_UNDERRUN_APP (по умолчанию, 0) — недолив не наша забота;
|
||||
* в CBL доигрывает то, что уже лежит в его буфере (никакого
|
||||
* буфера тишины не аллоцируется).
|
||||
* CBL_UNDERRUN_SILENCE (1) — насос сам пропихивает тишину при
|
||||
* недоливе; буфер тишины (128/256 Б по формату) аллоцируется
|
||||
* malloc'ом ВНУТРИ cbl_open(), только в этом режиме.
|
||||
* cbl_underruns() считает недоливы в ОБОИХ режимах — это диагностика,
|
||||
* поведение не меняет.
|
||||
*
|
||||
* cbl_close() обязателен (atexit подстрахует) — иначе CBL продолжит
|
||||
* прерывать шелл после выхода; заодно освобождает буфер тишины, если
|
||||
* он был аллоцирован.
|
||||
*
|
||||
* Требования как у <irq.h>: код/данные в W2 (tiny/big).
|
||||
*/
|
||||
|
||||
#ifndef CBL_H
|
||||
#define CBL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Коды частоты дискретизации (bits 3..0 порта 0x4E; из форума
|
||||
* Sprinter Team; коды 0/1 — legacy, не использовать). */
|
||||
#define CBL_FREQ_7K8 0x8 /* 7.8125 кГц */
|
||||
#define CBL_FREQ_10K9 0x9 /* 10.9375 кГц */
|
||||
#define CBL_FREQ_15K6 0xA /* 15.625 кГц */
|
||||
#define CBL_FREQ_21K9 0xB /* 21.875 кГц */
|
||||
#define CBL_FREQ_31K3 0xC /* 31.25 кГц */
|
||||
#define CBL_FREQ_43K8 0xD /* 43.75 кГц */
|
||||
#define CBL_FREQ_54K7 0xE /* 54.6875 кГц */
|
||||
#define CBL_FREQ_109K 0xF /* 109.375 кГц */
|
||||
|
||||
/* Формат: биты 5(16-бит)/6(stereo) порта 0x4E — значения готовы для
|
||||
* прямого OR с freq_code внутри cbl_open(). */
|
||||
#define CBL_FMT_MONO8 0x00 /* 8-бит, моно — блок 128 Б */
|
||||
#define CBL_FMT_MONO16 0x20 /* 16-бит, моно — блок 256 Б */
|
||||
#define CBL_FMT_STEREO8 0x40 /* 8-бит, стерео — блок 128 Б */
|
||||
#define CBL_FMT_STEREO16 0x60 /* 16-бит, стерео — блок 256 Б */
|
||||
|
||||
/* Способ выдачи блока. */
|
||||
#define CBL_PUMP_OTIR 0 /* cbl_push_otir() — базовый, не умеет 16-бит */
|
||||
#define CBL_PUMP_ACCEL 1 /* cbl_push_accel() — единственный путь для 16-бит */
|
||||
|
||||
/* Поведение при недоливе (fill() вернул 0 или не задан). */
|
||||
#define CBL_UNDERRUN_APP 0 /* по умолчанию: не наша забота, буфер тишины не аллоцируется */
|
||||
#define CBL_UNDERRUN_SILENCE 1 /* насос сам шлёт тишину; буфер аллоцируется в cbl_open() */
|
||||
|
||||
/* fill(n): пропихнуть n байт (через cbl_push_otir/accel) прямо из
|
||||
* данных приложения. Вызывается ИЗ ISR — обязан быть быстрым (без
|
||||
* ESTEX/BIOS/gfx/malloc). Вернуть ненулевое при успехе, 0 — нет
|
||||
* данных сейчас (недолив). */
|
||||
typedef int (*cbl_fill_fn)(uint16_t n);
|
||||
|
||||
/* Включить CBL с частотой CBL_FREQ_*, форматом CBL_FMT_*, способом
|
||||
* выдачи CBL_PUMP_* и поведением при недоливе CBL_UNDERRUN_*; fill —
|
||||
* callback, зовущийся из ISR за новым блоком (может быть NULL, тогда
|
||||
* недолив — на каждом запросе). 0 или -1 + errno (EBUSY — уже
|
||||
* открыт; EINVAL — плохой код частоты/формат/pump/underrun_mode, или
|
||||
* не-W2 режим; ENOMEM — не хватило памяти под буфер тишины). */
|
||||
int cbl_open(uint8_t freq_code, uint8_t fmt, uint8_t pump_mode,
|
||||
uint8_t underrun_mode, cbl_fill_fn fill);
|
||||
|
||||
/* Выключить CBL, отпустить IM2-таблицу, освободить буфер тишины (если
|
||||
* был). Идемпотентно. */
|
||||
void cbl_close(void);
|
||||
|
||||
/* Пропихнуть n байт из src прямо в CBL (звать ИЗ fill()). */
|
||||
void cbl_push_otir (const void *src, uint16_t n);
|
||||
void cbl_push_accel(const void *src, uint16_t n);
|
||||
|
||||
/* Счётчики диагностики: блоки-запросы и недоливы (fill() вернул 0
|
||||
* или не задан). */
|
||||
uint16_t cbl_requests(void);
|
||||
uint16_t cbl_underruns(void);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user