5086c47f0f
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>
124 lines
8.1 KiB
C
124 lines
8.1 KiB
C
/*
|
|
* 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
|