/*
 * 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 <stdio.h>
#include <string.h>
#include <conio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <cbl.h>
#include <sprinter_mem.h>

#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;
}
