Files
Sprinter-SDCC/tests/cbltest/cbltest.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

160 lines
7.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.
/*
* cbltest — полный перебор CBL-звука (Phase 2b, docs/im2_isr_design.md).
*
* Матрица: 2 насоса (OTIR/ACCEL) × 8 форматов (mono/stereo-L/stereo-R/
* stereo-LR, каждый в 8- и 16-бит) × 4 частоты (7.8/10.9/15.6/21.9 кГц)
* = 64 комбинации, каждая ~1 с пилообразного тона (период 64 сэмпла/
* кадра). Для каждой печатается requests/underruns — 0 недоливов
* ожидается всегда. В MAME на слух должно быть отчётливо слышно:
* смену тембра насоса (не должно быть разницы OTIR/ACCEL), канал
* (левый/правый/оба) и высоту тона (растёт с частотой).
*
* OTIR+16-бит (16 из 64 комбинаций) ПРОПУСКАЮТСЯ — cbl_open() отдаёт
* EINVAL: OTIR-порт данных не умеет собирать 16-бит сэмпл из пары
* байт (только акселератор это делает; см. cbl_open.c, сверено с
* исходником MAME sprinter.cpp).
*
* Новая модель без кольца libc (2026-07-07): fill_tone() зовётся ИЗ
* ISR, сама пропихивает n байт из уже готового периодического чанка
* через cbl_push_otir/accel (по current_pump) — данные никогда не
* кончаются, так что CBL_UNDERRUN_APP (без буфера тишины) достаточно.
* Main-цикл ничего не поллит — просто ждёт ~1 с через halt().
*/
#include <stdio.h>
#include <time.h>
#include <conio.h>
#include <errno.h>
#include <cbl.h>
static uint8_t chunk[256];
static uint8_t current_pump; /* CBL_PUMP_OTIR/ACCEL для активной комбинации */
/* Заполнить chunk одним периодом (64 сэмпла/кадра) пилы — раскладка
* зависит от bits (8/16) и stereo (моно / чередование L,R). */
static void gen_chunk(uint8_t bits, uint8_t stereo, uint8_t want_l, uint8_t want_r)
{
if (!stereo) {
if (bits == 8) {
for (uint16_t i = 0; i < sizeof(chunk); i++) {
chunk[i] = (uint8_t)(i * 4); /* период 64 (обёртка uint8_t) */
}
} else {
int16_t *p = (int16_t *)chunk;
for (uint16_t i = 0; i < sizeof(chunk) / 2; i++) {
uint16_t idx = i & 63;
p[i] = (int16_t)(uint16_t)(idx * 1024u);
}
}
} else if (bits == 8) {
for (uint16_t k = 0; k < sizeof(chunk) / 2; k++) {
uint8_t frame = (uint8_t)(k & 63);
uint8_t tone = (uint8_t)(frame * 4);
chunk[2 * k] = want_l ? tone : 0x80;
chunk[2 * k + 1] = want_r ? tone : 0x80;
}
} else {
int16_t *p = (int16_t *)chunk;
for (uint16_t k = 0; k < sizeof(chunk) / 4; k++) {
uint16_t frame = k & 63;
int16_t tone = (int16_t)(uint16_t)(frame * 1024u);
p[2 * k] = want_l ? tone : 0;
p[2 * k + 1] = want_r ? tone : 0;
}
}
}
/* Зовётся ИЗ ISR (см. cbl.h: обязано быть быстрым). chunk — готовый
* period-64 тон, никогда не "кончается", поэтому всегда успех. */
static int fill_tone(uint16_t n)
{
if (current_pump == CBL_PUMP_ACCEL) cbl_push_accel(chunk, n);
else cbl_push_otir(chunk, n);
return 1;
}
static void play_combo(uint16_t idx, uint16_t total,
const char *pump_name, uint8_t pump_mode,
const char *fmt_name, uint8_t fmt, uint8_t bits,
uint8_t stereo, uint8_t want_l, uint8_t want_r,
const char *freq_name, uint8_t freq_code)
{
gen_chunk(bits, stereo, want_l, want_r);
current_pump = pump_mode;
if (cbl_open(freq_code, fmt, pump_mode, CBL_UNDERRUN_APP, fill_tone) != 0) {
printf("[%2u/%u] %s %s %s: cbl_open failed errno=%d\n",
idx, total, pump_name, fmt_name, freq_name, errno);
return;
}
printf("[%2u/%u] %s %s %s ...\n", idx, total, pump_name, fmt_name, freq_name);
datetime_t t, t2;
getdatetime(&t);
getdatetime(&t2);
while (t.second == t2.second) {
__asm__("halt"); /* ISR сам кормит CBL — просто ждём */
getdatetime(&t2);
}
uint16_t reqs = cbl_requests();
uint16_t undr = cbl_underruns();
cbl_close();
if (undr) printf(" !! underruns=%u (reqs=%u)\n", undr, reqs);
}
int main(void)
{
static const uint8_t pumps[2] = { CBL_PUMP_OTIR, CBL_PUMP_ACCEL };
static const char *const pump_names[2] = { "otir ", "accel" };
static const uint8_t freq_codes[4] = {
CBL_FREQ_7K8, CBL_FREQ_10K9, CBL_FREQ_15K6, CBL_FREQ_21K9
};
static const char *const freq_names[4] = { "7.8k ", "10.9k", "15.6k", "21.9k" };
/* формат: биты, fmt-код, stereo?, L?, R?, имя */
static const uint8_t fmt_bits[8] = { 8, 8, 8, 8, 16, 16, 16, 16 };
static const uint8_t fmt_code[8] = {
CBL_FMT_MONO8, CBL_FMT_STEREO8, CBL_FMT_STEREO8, CBL_FMT_STEREO8,
CBL_FMT_MONO16, CBL_FMT_STEREO16, CBL_FMT_STEREO16, CBL_FMT_STEREO16
};
static const uint8_t fmt_stereo[8] = { 0, 1, 1, 1, 0, 1, 1, 1 };
static const uint8_t fmt_l[8] = { 1, 1, 0, 1, 1, 1, 0, 1 };
static const uint8_t fmt_r[8] = { 1, 0, 1, 1, 1, 0, 1, 1 };
static const char *const fmt_names[8] = {
"mono8 ", "st8-L ", "st8-R ", "st8-LR ",
"mono16 ", "st16-L ", "st16-R ", "st16-LR "
};
uint16_t total = 2u * 8u * 4u;
uint16_t idx = 0;
for (uint8_t p = 0; p < 2; p++) {
for (uint8_t f = 0; f < 8; f++) {
for (uint8_t q = 0; q < 4; q++) {
idx++;
/* OTIR принципиально не может собрать 16-бит сэмпл
* (см. cbl_open.c) — cbl_open вернёт EINVAL; не тратим
* время на заведомо запрещённую комбинацию. */
if (pumps[p] == CBL_PUMP_OTIR && fmt_bits[f] == 16) {
printf("[%2u/%u] %s %s %s : skip (OTIR can't do 16-bit)\n",
idx, total, pump_names[p], fmt_names[f], freq_names[q]);
continue;
}
play_combo(idx, total,
pump_names[p], pumps[p],
fmt_names[f], fmt_code[f], fmt_bits[f],
fmt_stereo[f], fmt_l[f], fmt_r[f],
freq_names[q], freq_codes[q]);
}
}
}
puts("press any key (keyboard must work)...");
getch();
puts("cbltest done.");
return 0;
}