/* * cblwav — проигрывание РЕАЛЬНОГО звука через CBL (Phase 2b смоук). * * speech.pcm: моно 8-бит unsigned (центр 0x80), 21.875 кГц — сгенерирован * macOS `say` + `afconvert -d UI8@21875` (см. docs/im2_isr_design.md). * * speech.pcm лежит на ДИСКЕТЕ (mc.img на -flop1) — этот привод слишком * медленный для потокового чтения в реальном времени. Поэтому клип * целиком грузится в банковую EMM-память ЗАРАНЕЕ (без таймингов), * крупными блоками через сырой read(), а во время проигрывания диск * больше не участвует. * * Новая модель без кольца libc (2026-07-07): fill_speech() зовётся * ИЗ ISR за очередным блоком; она делает bank_read() из УЖЕ ЗАГРУЖЕННОЙ * страницы прямо в маленький стейджинг-буфер и пропихивает его через * cbl_push_otir() — без промежуточного кольца. * * ВАЖНО: fill() не должен звать mem_get_page() (BIOS rst 8) — BIOS сам * управляет EI/DI вокруг rst 8, а его `ei` при возврате может * преждевременно снять маску прерываний, пока мы ещё внутри ISR (см. * cbl.h: "fill() ОБЯЗАН быть быстрым... никаких ESTEX/BIOS-вызовов"). * Поэтому номера физических страниц считаются ОДИН РАЗ на этапе * загрузки (в main-контексте) и кэшируются в phys_pages[] — fill() * только индексирует массив. bank_read() — чистый порт-ввод/вывод, * без BIOS/ESTEX, вызывать из ISR безопасно. * * Хвост не кратный размеру блока (78 КБ % 128 = 43) — fill() * докладывает остаток тишиной (0x80) прямо в стейджинге на последнем * неполном блоке, никакого отдельного паддинг-прохода не нужно. */ #include #include #include #include #include #include #include #include #define PAGE_SIZE 16384u /* размер страницы EMM */ #define LOAD_CHUNK 4096u /* блок разовой загрузки с диска; делит PAGE_SIZE нацело */ #define SPEECH_SIZE 77995ul /* точный размер tests/cblwav/speech.pcm */ #define MAX_PAGES 8 static uint8_t loadbuf[LOAD_CHUNK]; static uint8_t staging[256]; /* под самый большой блок (16-бит = 256) */ static uint8_t phys_pages[MAX_PAGES]; /* кэш физ. страниц — БЕЗ BIOS-вызовов из fill() */ static uint8_t page_idx; static uint16_t page_off; static uint32_t played; static uint32_t loaded; static volatile uint8_t done; static int fill_speech(uint16_t n) { if (played >= loaded) { done = 1; return 0; } uint16_t want = n; if (want > loaded - played) want = (uint16_t)(loaded - played); bank_read(phys_pages[page_idx], page_off, staging, want); if (want < n) memset(staging + want, 0x80, n - want); /* хвост короче блока */ cbl_push_otir(staging, n); page_off += want; played += want; if (page_off >= PAGE_SIZE) { page_off -= PAGE_SIZE; page_idx++; } return 1; } int main(void) { int fd = open("speech.pcm", O_RDONLY); if (fd < 0) { printf("open(speech.pcm) failed: errno=%d\n", errno); return 1; } uint8_t npages = (uint8_t)((SPEECH_SIZE + PAGE_SIZE - 1) / PAGE_SIZE); uint8_t blk = mem_alloc_pages(npages); if (!blk) { printf("mem_alloc_pages(%u) failed: errno=%d\n", npages, errno); close(fd); return 1; } puts("loading speech.pcm into RAM (slow: floppy)..."); page_idx = 0; page_off = 0; phys_pages[0] = mem_get_page(blk, 0); while (loaded < SPEECH_SIZE) { int got = read(fd, loadbuf, sizeof(loadbuf)); if (got <= 0) break; bank_write(phys_pages[page_idx], page_off, loadbuf, (uint16_t)got); page_off += (uint16_t)got; loaded += (uint32_t)got; if (page_off >= PAGE_SIZE) { page_off -= PAGE_SIZE; page_idx++; phys_pages[page_idx] = mem_get_page(blk, page_idx); /* редко: смена страницы */ } } close(fd); printf("loaded %lu bytes into %u page(s)\n", loaded, npages); page_idx = 0; page_off = 0; played = 0; done = 0; if (cbl_open(CBL_FREQ_21K9, CBL_FMT_MONO8, CBL_PUMP_OTIR, CBL_UNDERRUN_APP, fill_speech) != 0) { printf("cbl_open failed: errno=%d\n", errno); mem_free_block(blk); return 1; } puts("playing speech.pcm (21.875 kHz, mono 8-bit)..."); while (!done) __asm__("halt"); /* ISR сам кормит CBL — просто ждём */ printf("requests: %u, underruns: %u (expect ~1: tail block)\n", cbl_requests(), cbl_underruns()); cbl_close(); mem_free_block(blk); puts("press any key (keyboard must work)..."); getch(); puts("cblwav done."); return 0; }