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>
141 lines
5.0 KiB
C
141 lines
5.0 KiB
C
/*
|
||
* cblstream — потоковое чтение с диска ОДНОВРЕМЕННО с воспроизведением
|
||
* через CBL. Расчитан на быстрый носитель (HDD) — на floppy
|
||
* захлёбывается (см. tests/cblwav, где поэтому весь клип грузится в
|
||
* RAM заранее). speech30.pcm — ~39.6 c речи, моно 8-бит unsigned,
|
||
* 21.875 кГц.
|
||
*
|
||
* ЕДИНСТВЕННЫЙ случай из трёх cbl-тестов, где приложению НУЖНО своё
|
||
* кольцо (2026-07-07): read() — ESTEX-вызов, а fill()-callback CBL
|
||
* зовётся ИЗ ISR, где ESTEX/BIOS под запретом (см. cbl.h). Поэтому
|
||
* диск читается ТОЛЬКО в main (в собственное кольцо ring[]), а
|
||
* fill_stream() лишь копирует уже готовые байты из ring в стейджинг и
|
||
* пропихивает cbl_push_otir() — сам с диском не работает.
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <conio.h>
|
||
#include <errno.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
#include <cbl.h>
|
||
#include <irq.h>
|
||
|
||
#define RING_SIZE 2048u
|
||
#define READ_CHUNK 1024u
|
||
|
||
static uint8_t ring[RING_SIZE];
|
||
static uint16_t r_rd, r_wr, r_count; /* r_count: main под IRQ_DISABLE, fill() уже в ISR */
|
||
static uint8_t staging[128]; /* блок 8-бит моно */
|
||
static uint16_t ring_in = 0;
|
||
static uint16_t ring_border = 0;
|
||
|
||
static uint16_t ring_free(void)
|
||
{
|
||
IRQ_DISABLE();
|
||
uint16_t c = r_count;
|
||
IRQ_ENABLE();
|
||
return (uint16_t)(RING_SIZE - c);
|
||
}
|
||
|
||
/* Вызывать ТОЛЬКО из main. */
|
||
static void ring_push(const void *src, uint16_t n)
|
||
{
|
||
const uint8_t *s = src;
|
||
while (n) {
|
||
uint16_t tail = (uint16_t)(RING_SIZE - r_wr);
|
||
uint16_t chunk = n < tail ? n : tail;
|
||
memcpy(ring + r_wr, s, chunk);
|
||
r_wr = (uint16_t)((r_wr + chunk) % RING_SIZE);
|
||
IRQ_DISABLE();
|
||
r_count += chunk;
|
||
IRQ_ENABLE();
|
||
s += chunk;
|
||
n = (uint16_t)(n - chunk);
|
||
}
|
||
}
|
||
|
||
/* Зовётся ИЗ ISR — только копия из уже готового ring, никакого ESTEX. */
|
||
static int fill_stream(uint16_t n)
|
||
{
|
||
if (r_count < n) return 0;
|
||
|
||
uint16_t tail = (uint16_t)(RING_SIZE - r_rd);
|
||
if (tail >= n) {
|
||
// memcpy(staging, ring + r_rd, n);
|
||
cbl_push_otir(ring + r_rd, n);
|
||
ring_in++;
|
||
} else {
|
||
memcpy(staging, ring + r_rd, tail);
|
||
memcpy(staging + tail, ring, (uint16_t)(n - tail));
|
||
cbl_push_otir(staging, n);
|
||
ring_border++;
|
||
}
|
||
r_rd = (uint16_t)((r_rd + n) % RING_SIZE);
|
||
r_count = (uint16_t)(r_count - n); /* уже внутри ISR — IRQ и так замаскированы */
|
||
|
||
return 1;
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
int fd = open("speech30.pcm", O_RDONLY);
|
||
if (fd < 0) {
|
||
printf("open(speech30.pcm) failed: errno=%d\n", errno);
|
||
return 1;
|
||
}
|
||
|
||
static uint8_t readbuf[READ_CHUNK];
|
||
uint32_t played = 0;
|
||
uint8_t eof = 0;
|
||
|
||
/* прогреть кольцо реальными данными ДО cbl_open()/puts() — иначе
|
||
* пауза тишины на старте (тот же приём, что в cbltest.c/cblwav.c) */
|
||
while (!eof && ring_free() >= sizeof(readbuf)) {
|
||
int got = read(fd, readbuf, sizeof(readbuf));
|
||
if (got <= 0) { eof = 1; break; }
|
||
ring_push(readbuf, (uint16_t)got);
|
||
played += (uint32_t)got;
|
||
}
|
||
|
||
if (cbl_open(CBL_FREQ_21K9, CBL_FMT_MONO8, CBL_PUMP_OTIR,
|
||
CBL_UNDERRUN_APP, fill_stream) != 0) {
|
||
printf("cbl_open failed: errno=%d\n", errno);
|
||
close(fd);
|
||
return 1;
|
||
}
|
||
|
||
puts("streaming speech30.pcm from disk (21.875 kHz, mono 8-bit)...");
|
||
|
||
while (!eof) {
|
||
if (ring_free() >= sizeof(readbuf)) {
|
||
int got = read(fd, readbuf, sizeof(readbuf));
|
||
if (got <= 0) { eof = 1; break; }
|
||
ring_push(readbuf, (uint16_t)got);
|
||
played += (uint32_t)got;
|
||
} else {
|
||
__asm__("halt"); /* ждём, пока ISR подъест кольцо */
|
||
}
|
||
}
|
||
close(fd);
|
||
printf("streamed %lu bytes from disk\n", played);
|
||
|
||
/* Ждём "почти полного" опустошения, а не точного RING_SIZE: если
|
||
* общая длина потока не кратна 128, необслуженный хвост (<128 Б,
|
||
* тот же квирк, что и в старом кольце libc — см. cbl.h) навечно
|
||
* оставит r_count>0, и точное ожидание никогда бы не завершилось. */
|
||
while (ring_free() < RING_SIZE - 127u) __asm__("halt");
|
||
|
||
printf("requests: %u, underruns: %u (expect 0 during play)\n",
|
||
cbl_requests(), cbl_underruns());
|
||
printf("ring: in: %u, border: %u\n",
|
||
ring_in, ring_border);
|
||
cbl_close();
|
||
|
||
puts("press any key (keyboard must work)...");
|
||
getch();
|
||
puts("cblstream done.");
|
||
return 0;
|
||
}
|