/* * 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 #include #include #include #include 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; }