/*
 * 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
