Files
Sprinter-SDCC/tests/cblstream/cblstream.c
T
snark13 5086c47f0f 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>
2026-07-07 21:21:54 +03:00

141 lines
5.0 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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;
}