/* ======================================================================
 * pmselftest.c  -  PokeyMAX "audio-visual self-test" homage
 *
 * Standalone Atari 8-bit program (build -> XEX) styled after the XL/XE
 * Audio-Visual self-test (M. Colburn, 1200XL).  Uses the original XL/XE self-test audio-visual display list,
 * screen layout, and DVN blitter; replaces only the sound generator.
 *
 *   [ANTIC 7 / GR.2]  big title    "AUDIO-VISUAL" / "TEST"
 *   [ANTIC D / GR.7]  staff band   five lines, clef, moving notes
 *   [ANTIC 7 / GR.2]  device label "SID" etc.
 *   [ANTIC 2 / GR.0]  exit prompt
 *
 * TUNE: first six notes of Mussorgsky's "Promenade" (Pictures at an
 * Exhibition, 1874, public domain), Bb major, opening on the 6th degree:
 *     G4 F4 Bb4 | C5 F5 D5   (first three crotchets, fourth/fifth quavers, final crotchet)
 * drawn left-to-right on the staff as they sound, flat shown on Bb.
 *
 * Walks each device the core reports (POKEY/SID/PSG/COVOX/SAMPLE), panned
 * L/R, with per-device volume balanced so SID does not stand out quiet.
 *
 * ANTIC/GTIA usage follows ilmenit/atari-800-skills conventions.
 * ====================================================================== */

#include <stdint.h>
#include <string.h>
#include "covox_pcm.h"

/* ---- PokeyMAX registers (dev guide v1.30; quad really reads 3) ------- */
#define POKEY1 ((volatile uint8_t *)0xD200)
#define POKEY2 ((volatile uint8_t *)0xD210)
#define POKEY3 ((volatile uint8_t *)0xD220)
#define POKEY4 ((volatile uint8_t *)0xD230)
#define AUDF1 0x00
#define AUDC1 0x01
#define AUDC2 0x03
#define AUDCTL 0x08
#define STIMER 0x09
#define SKCTL 0x0F
#define IRQEN 0x0E
#define STIMER 0x09

#define CONFIG      ((volatile uint8_t *)0xD20C)
#define CFG_ENABLE   0x3F
#define CFG_DISABLE  0x00
#define CFG_CAPS    ((volatile uint8_t *)0xD211)
#define CFG_VERSION ((volatile uint8_t *)0xD214)
#define CFG_PSGMODE ((volatile uint8_t *)0xD215)

#define CAP_POKEY_MASK 0x03
#define CAP_SID   0x04
#define CAP_PSG   0x08
#define CAP_COVOX 0x10
#define CAP_SAMPLE 0x20
#define CAP_QPOKEY 0x80  /* pseudo-cap: require quad POKEY, caps low bits == 3 */

#define SID1 ((volatile uint8_t *)0xD240)
#define SID2 ((volatile uint8_t *)0xD260)
#define SID_FREQLO 0x00
#define SID_FREQHI 0x01
#define SID_PWLO 0x02
#define SID_PWHI 0x03
#define SID_CTRL 0x04
#define SID_AD 0x05
#define SID_SR 0x06
#define SID_FCLO 0x15
#define SID_FCHI 0x16
#define SID_RESFILT 0x17
#define SID_MODVOL 0x18

#define PSG1 ((volatile uint8_t *)0xD2A0)
#define PSG2 ((volatile uint8_t *)0xD2B0)

#define COVOX_CH1 ((volatile uint8_t *)0xD280)
#define COVOX_CH2 ((volatile uint8_t *)0xD281)
#define COVOX_CH3 ((volatile uint8_t *)0xD282)
#define COVOX_CH4 ((volatile uint8_t *)0xD283)

#define SAM_RAMADDRL ((volatile uint8_t *)0xD284)
#define SAM_RAMADDRH ((volatile uint8_t *)0xD285)
#define SAM_RAMDATAI ((volatile uint8_t *)0xD287)
#define SAM_CHANSEL  ((volatile uint8_t *)0xD288)
#define SAM_ADDRL    ((volatile uint8_t *)0xD289)
#define SAM_ADDRH    ((volatile uint8_t *)0xD28A)
#define SAM_LENL     ((volatile uint8_t *)0xD28B)
#define SAM_LENH     ((volatile uint8_t *)0xD28C)
#define SAM_PERL     ((volatile uint8_t *)0xD28D)
#define SAM_PERH     ((volatile uint8_t *)0xD28E)
#define SAM_VOL      ((volatile uint8_t *)0xD28F)
#define SAM_DMA      ((volatile uint8_t *)0xD290)
#define SAM_IRQEN    ((volatile uint8_t *)0xD291)
#define SAM_IRQACT   ((volatile uint8_t *)0xD292)
#define SAM_CFG      ((volatile uint8_t *)0xD293)

/* ---- OS / ANTIC / GTIA ----------------------------------------------- */
#define POKMSK  (*(volatile uint8_t *)0x0010)
#define SSKCTL  (*(volatile uint8_t *)0x0232)
#define RTCLOK2 (*(volatile uint8_t *)0x0014)
#define CH      (*(volatile uint8_t *)0x02FC)
#define HELPFG  (*(volatile uint8_t *)0x02DC)
#define CONSOL  (*(volatile uint8_t *)0xD01F)
#define COLBK   (*(volatile uint8_t *)0xD01A)
#define WSYNC   (*(volatile uint8_t *)0xD40A)
#define DMACTL  (*(volatile uint8_t *)0xD400)
#define SDMCTL  (*(volatile uint8_t *)0x022F)
#define SDLSTL  (*(volatile uint16_t *)0x0230)
#define VDSLST  (*(volatile uint16_t *)0x0200)
#define NMIEN   (*(volatile uint8_t  *)0xD40E)
#define CHBAS   (*(volatile uint8_t  *)0x02F4)
#define COLOR0  (*(volatile uint8_t *)0x02C4)
#define COLOR1  (*(volatile uint8_t *)0x02C5)
#define COLOR2  (*(volatile uint8_t *)0x02C6)
#define COLOR3  (*(volatile uint8_t *)0x02C7)
#define COLOR4  (*(volatile uint8_t *)0x02C8)

#define KEY_NONE 0xFF
#define KEY_ESC  0x1C
#define IRQEN_KEYBOARD 0x40
#define IRQEN_TIMER1   0x01
#define VTIMR1 (*(volatile uint16_t *)0x0210)
/* Generic IRQ RAM vector.  The OS ROM IRQ handler pushes A and immediately
 * does JMP (VIMIRQ) BEFORE polling IRQST, so revectoring this is the
 * fastest user IRQ path - it skips the OS's per-source IRQST dispatch that
 * routing through VTIMR1 incurs.  During the COVOX pass we own it. */
#define VIMIRQ (*(volatile uint16_t *)0x0216)


/* ---- Original XL/XE audio-visual self-test display --------------------
 * Port of DISL5, SMEM5, TAVD, TCDA and DVN from the XL OS Rev. 11
 * source.  The original self-test uses screen memory as if based at $3000:
 *   $3000 text/status row, $3100 bitmap staff area, $3150... staff lines.
 * We keep the same offsets inside this array and only alter the sound side.
 * ---------------------------------------------------------------------- */
#define AV_BASE 0x3000u
#define AV_SCREEN_SIZE 0x0400u
#define AV_ROW_BYTES 16
#define AV_TEXT_W 16      /* DISL5 runs with SDMCTL=$21 narrow playfield: ANTIC 6 rows are 16 bytes */
#define AV_EXIT_W 32      /* ANTIC 2 high-res text is 32 bytes wide in the same narrow playfield */

/* Screen memory, display list and text buffers MUST observe ANTIC's
 * alignment rules or the picture corrupts:
 *   - A display list may not cross a 1 KB boundary (no JMP across it).
 *   - A bitmap mode-line region may not cross a 4 KB boundary (ANTIC's
 *     memory-scan counter only carries the low 12 bits).
 * When the binary was small these BSS arrays happened to fall inside one
 * aligned block; as it grew, av_screen drifted across $7000 (a 1 KB *and*
 * 4 KB boundary) and the staff bitmap wrapped -> the corruption you saw.
 *
 * Fix (no linker-config change): over-allocate backing arrays and point
 * runtime pointers at aligned addresses inside them.  A 1 KB region that
 * starts 1 KB-aligned is, by construction, wholly inside one 4 KB block,
 * so both rules are satisfied.  Because indexing a pointer uses the same
 * name[i] syntax as an array, the rest of the code is unchanged. */
static uint8_t av_screen_backing[AV_SCREEN_SIZE + 0x400];
static uint8_t misc_backing[256 + 80 + (AV_TEXT_W * 2) + AV_EXIT_W];

static uint8_t *av_screen;        /* 1 KB, 1 KB-aligned (bitmap-safe) */
static uint8_t *av_title;
static uint8_t *av_exit;
static uint8_t *dlist;

static uint16_t saved_sdlstl, saved_vdslst;
static uint8_t saved_sdmctl;
static uint8_t saved_chbas, saved_color0, saved_color1, saved_color2, saved_color3, saved_color4;

/* Round up to the next 1 KB / 256 B boundary. */
static uint8_t *align1k(uint8_t *p)
{
    return (uint8_t *)(uint16_t)(((uint16_t)p + 0x3FF) & 0xFC00);
}
static uint8_t *align256(uint8_t *p)
{
    return (uint8_t *)(uint16_t)(((uint16_t)p + 0xFF) & 0xFF00);
}

/* Assign aligned pointers.  Called once, before any drawing. */
static void av_alloc(void)
{
    uint8_t *m;
    av_screen = align1k(av_screen_backing);  /* 1 KB-aligned: bitmap-safe   */
    m = align256(misc_backing);              /* 256-aligned slot            */
    dlist    = m;  m += 80;                  /* 80 bytes, can't cross 1 KB  */
    av_title = m;  m += AV_TEXT_W * 2;
    av_exit  = m;                            /* AV_EXIT_W bytes             */
}

#define AVOFF(a) ((uint16_t)((a) - AV_BASE))

/* Original TAVD: 15 glyph fragments, 6 bytes each.  DVN treats byte 0 as
 * a 16-row vertical run, then bytes 1..5 as the following five rows. */
static const uint8_t TAVD[] = {
    0x01,0x1F,0x3F,0x7F,0x3E,0x1C,        /* 0 */
    0x00,0x41,0x42,0x4C,0x70,0x40,        /* 1 */
    0x00,0x01,0x02,0x04,0x08,0x10,        /* 2 */
    0x00,0x43,0x44,0x48,0x48,0x48,        /* 3 */
    0x00,0x44,0x22,0x10,0x08,0x07,        /* 4 */
    0x00,0x04,0x08,0x05,0x02,0x00,        /* 5 */
    0x00,0x30,0x48,0x88,0x84,0x84,        /* 6 */
    0x00,0x88,0x88,0x90,0xA0,0xC0,        /* 7 */
    0x00,0xF0,0x88,0x84,0x82,0x82,        /* 8 */
    0x00,0x82,0x82,0x84,0x88,0xF0,        /* 9 */
    0x00,0x00,0x00,0x00,0x00,0x80,        /* 10 */
    0x80,0x80,0x80,0x80,0x80,0x80,        /* 11 */
    0x00,0x1C,0x3E,0x7F,0x7E,0x7C,        /* 12 */
    0x40,0x00,0x00,0x00,0x00,0x00,        /* 13 */
    0x00,0x04,0x04,0x06,0x05,0x06         /* 14 */
};

static const uint16_t TCDA[] = {
    0x30C1, 0x3121, 0x3181, 0x31F1, 0x3002,
    0x3062, 0x3122, 0x3182, 0x30C2, 0x31C2
};
#define TCDAL (sizeof(TCDA)/sizeof(TCDA[0]))

static uint8_t scrcode(uint8_t c)
{
    if (c < 0x20) return c + 0x40;
    if (c < 0x60) return c - 0x20;
    return c;
}
static uint8_t strlen8(const char *s)
{
    uint8_t n = 0;
    while (s[n]) n++;
    return n;
}
static void textn_center_mem(uint8_t *dst, uint8_t w, const char *s)
{
    uint8_t i, l = strlen8(s);
    uint8_t x = (l < w) ? (uint8_t)((w - l) >> 1) : 0;
    for (i=0;i<w;i++) dst[i] = 0;
    for (i=0;i<l && (uint8_t)(x+i)<w;i++) dst[x+i] = scrcode((uint8_t)s[i]);
}
static void textn_center_screen(uint16_t addr, uint8_t w, const char *s)
{
    textn_center_mem(&av_screen[AVOFF(addr)], w, s);
}
static void build_exit_prompt(void)
{
    static const char txt[] = "RESET OR HELP TO EXIT";
    uint8_t i, l = sizeof(txt)-1;
    uint8_t x = (AV_EXIT_W - l) >> 1;
    for (i=0;i<AV_EXIT_W;i++) av_exit[i] = 0;
    for (i=0;i<l;i++) {
        uint8_t c = scrcode((uint8_t)txt[i]);
        if (i < 5 || (i >= 9 && i < 13)) c |= 0x80;   /* inverse RESET and HELP */
        av_exit[x+i] = c;
    }
}
static void set_status_for_device(const char *name)
{
    textn_center_screen(0x3000, AV_TEXT_W, name);
}

extern void prompt_dli(void);

static void display_save(void)
{
    saved_sdlstl = SDLSTL;
    saved_vdslst = VDSLST;
    saved_sdmctl = SDMCTL;
    saved_chbas = CHBAS;
    saved_color0 = COLOR0; saved_color1 = COLOR1; saved_color2 = COLOR2;
    saved_color3 = COLOR3; saved_color4 = COLOR4;
}
static void display_release(void)
{
    SDMCTL = 0;
    DMACTL = 0;
    SDLSTL = saved_sdlstl;
    VDSLST = saved_vdslst;
    NMIEN = 0x40;
    SDMCTL = saved_sdmctl;
    DMACTL = saved_sdmctl;
    CHBAS = saved_chbas;
    COLOR0 = saved_color0; COLOR1 = saved_color1; COLOR2 = saved_color2;
    COLOR3 = saved_color3; COLOR4 = saved_color4;
}
static void build_dlist(void)
{
    uint8_t *d = dlist;
    uint16_t a;
    uint8_t r;
    *d++ = 0x70; *d++ = 0x70; *d++ = 0x70; *d++ = 0x70;
    *d++ = 0x46; a=(uint16_t)av_title; *d++=(uint8_t)a; *d++=(uint8_t)(a>>8);
    *d++ = 0x70; *d++ = 0x06;
    *d++ = 0x70; *d++ = 0x70;
    *d++ = 0x4B; a=(uint16_t)(av_screen + 0x100); *d++=(uint8_t)a; *d++=(uint8_t)(a>>8);
    for (r=0; r<42; r++) *d++ = 0x0B;
    *d++ = 0x70;
    *d++ = 0x46; a=(uint16_t)av_screen; *d++=(uint8_t)a; *d++=(uint8_t)(a>>8);
    /* Original DISL3 tail: a DLI blank line changes PF1/PF2 before
     * the high-res prompt, so ANTIC 2 text is white on the red background
     * without disturbing the colour-2 staff/note bitmap above. */
    *d++ = 0xA0; *d++ = 0x40;
    *d++ = 0x42; a=(uint16_t)av_exit; *d++=(uint8_t)a; *d++=(uint8_t)(a>>8);
    a=(uint16_t)dlist; *d++=0x41; *d++=(uint8_t)a; *d++=(uint8_t)(a>>8);
}
static void install_dlist(void)
{
    uint8_t f = RTCLOK2;
    __asm__("sei");
    /* The original self-test SDL routine selects the small/narrow
     * playfield via SDMCTL=$21.  DISL5 and the ST31xx drawing offsets
     * depend on ANTIC fetching 16 bytes per bitmap row.  Leaving the
     * normal playfield width active makes ANTIC stride through memory
     * too quickly, which shows up as torn/dotted staff and garbage text. */
    SDMCTL = 0;
    DMACTL = 0;
    VDSLST = (uint16_t)prompt_dli;
    SDLSTL = (uint16_t)dlist;
    NMIEN = 0xC0;
    SDMCTL = 0x21;
    DMACTL = 0x21;
    __asm__("cli");
    while (RTCLOK2 == f) { }
}
static void set_colours(void)
{
    COLOR4 = 0x32;           /* red background */
    COLOR0 = 0x0F;           /* playfield 0 / text */
    COLOR1 = 0x0F;
    COLOR2 = 0x0F;           /* original staff/note colour */
    COLOR3 = 0x0F;
}
static void av_clear(void)
{
    uint16_t i;
    for (i=0; i<AV_SCREEN_SIZE; i++) av_screen[i] = 0;
}
static void av_init_title(void)
{
    textn_center_mem(av_title, AV_TEXT_W, "POKEYMAX");
    textn_center_mem(av_title + AV_TEXT_W, AV_TEXT_W, "AUDIO TEST");
    build_exit_prompt();
}
static void av_dvn(uint16_t dst_addr, uint8_t data_offset)
{
    uint16_t p = AVOFF(dst_addr);
    uint8_t x = data_offset;
    uint8_t rows = 16;
    uint8_t chunks = 6;
    for (;;) {
        av_screen[p] |= TAVD[x];
        p += AV_ROW_BYTES;
        rows--;
        if (rows != 0) continue;
        rows++;
        x++;
        chunks--;
        if (chunks == 0) break;
    }
}
static void draw_staff_lines(void)
{
    uint8_t x;
    av_clear();
    for (x=0; x<=0x0F; x++) {
        av_screen[AVOFF(0x3150)+x] = 0xFF;
        av_screen[AVOFF(0x31B0)+x] = 0xFF;
        av_screen[AVOFF(0x3210)+x] = 0xFF;
        av_screen[AVOFF(0x3270)+x] = 0xFF;
        av_screen[AVOFF(0x32D0)+x] = 0xFF;
    }
}
static void draw_treble_clef(void)
{
    uint8_t i;
    uint8_t data = 2 * 6;
    for (i=0; i<TCDAL; i++) {
        av_dvn(TCDA[i], data);
        data += 6;
    }
}
static void draw_note_original(uint8_t i)
{
    switch (i) {
    case 0:
        av_dvn(0x3154, 0*6); break;
    case 1:
        av_dvn(0x3186, 0*6); break;
    case 2:
        av_dvn(0x30F8, 12*6); av_dvn(0x30C7, 14*6); av_dvn(0x3248, 13*6); break;
    case 3:
        av_dvn(0x30CA, 12*6); av_dvn(0x321A, 13*6); av_dvn(0x31CA, 1*6); break;
    case 4:
        av_dvn(0x303C, 12*6); av_dvn(0x318C, 13*6); av_dvn(0x313C, 1*6); break;
    default:
        av_dvn(0x309E, 12*6); av_dvn(0x31EE, 13*6); break;
    }
}
/* ---- timing ---------------------------------------------------------- */
static void wait_frames(uint16_t n){ while(n--){ uint8_t s=RTCLOK2; while(RTCLOK2==s){} } }
static uint8_t help_pressed(void){ if (HELPFG) { HELPFG=0; return 1; } return 0; }

/* ---- the phrase ------------------------------------------------------ */
enum { N_G4, N_F4, N_BB4, N_C5, N_D5, N_F5, NUM_NOTES };
static const uint8_t  pokey_audf[NUM_NOTES] = { 0x51,0x5B,0x44,0x3C,0x35,0x2D };
static const uint16_t sid_freq[NUM_NOTES]   = { 0x191F,0x1661,0x1DDF,0x2188,0x25A3,0x2CC2 };
static const uint16_t psg_tp[NUM_NOTES]     = { 318,358,268,238,212,178 };
static const uint16_t sample_period[NUM_NOTES] = { 143,160,120,107,95,80 };

#define CROTCHET 30
#define QUAVER   15
static const uint8_t phrase_note[6] = { N_G4, N_F4, N_BB4, N_C5, N_F5, N_D5 };
static const uint8_t phrase_dur[6]  = { CROTCHET,CROTCHET,CROTCHET, QUAVER,QUAVER,CROTCHET };

/* ---- COVOX raw DAC player -------------------------------------------
 * The ISR path made repeat work, but made the phrase collapse into one note
 * on real hardware.  This returns to the original per-note delay loop, while
 * keeping the stronger SAMPLE/COVOX cleanup that fixed the second-run silence. */
static void covox_midscale(void)
{
    COVOX_CH1[0] = 0x80; COVOX_CH2[0] = 0x80;
    COVOX_CH3[0] = 0x80; COVOX_CH4[0] = 0x80;
}
static void covox_cleanup_sample_engine(void)
{
    SAM_DMA[0]=0; SAM_IRQEN[0]=0; SAM_IRQACT[0]=0; SAM_VOL[0]=0;
    /* Raw COVOX is an 8-bit DAC path.  Do not leave PokeyMAX in the
     * 4-bit sample configuration here; that makes COVOX quiet/grainy. */
    SAM_CFG[0]=0xF0;
    covox_midscale();
}

/* ---- audio ownership ------------------------------------------------- */
static uint8_t saved_pokmsk, saved_sskctl, saved_psgmode;
static uint16_t saved_vtimr1;

static void psg_silence(volatile uint8_t *psg)
{
    psg[0x08]=0; psg[0x09]=0; psg[0x0A]=0; psg[0x07]=0x3F;
}
static void psg_set_max_stereo(void)
{
    uint8_t mode;
    *CONFIG = CFG_ENABLE;
    saved_psgmode = *CFG_PSGMODE;
    mode = saved_psgmode;
    mode &= (uint8_t)~0x0C;
    mode |= 0x0C;                 /* MAX stereo: PSG1 left, PSG2 right */
    *CFG_PSGMODE = mode;
    *CONFIG = CFG_DISABLE;
}
static void psg_restore_mode(void)
{
    *CONFIG = CFG_ENABLE;
    *CFG_PSGMODE = saved_psgmode;
    *CONFIG = CFG_DISABLE;
}

static void silence_all(void)
{
    POKEY1[AUDC1]=0; POKEY1[AUDC2]=0; POKEY2[AUDC1]=0; POKEY2[AUDC2]=0;
    POKEY3[AUDC1]=0; POKEY4[AUDC1]=0;
    SID1[SID_MODVOL]=0; SID2[SID_MODVOL]=0;
    psg_silence(PSG1); psg_silence(PSG2);
    covox_cleanup_sample_engine();
}
static void audio_acquire(void)
{
    saved_pokmsk=POKMSK; saved_sskctl=SSKCTL; saved_vtimr1=VTIMR1;
    psg_set_max_stereo();
    POKEY1[AUDCTL]=0; POKEY2[AUDCTL]=0; POKEY3[AUDCTL]=0; POKEY4[AUDCTL]=0;
    SSKCTL=0x03;
    POKEY1[SKCTL]=0x03; POKEY2[SKCTL]=0x03; POKEY3[SKCTL]=0x03; POKEY4[SKCTL]=0x03;
    silence_all();
}
static void audio_release(void)
{
    silence_all();
    psg_restore_mode();
    SSKCTL=saved_sskctl; POKEY1[SKCTL]=saved_sskctl;
    VTIMR1=saved_vtimr1; POKMSK=saved_pokmsk; POKEY1[IRQEN]=saved_pokmsk;
}

/* ---- per-device players (volume balanced) ---------------------------- */
#define POKEY_VOL 0x06
static void pokey_on(uint8_t n, uint8_t right)
{
    volatile uint8_t *p = right?POKEY2:POKEY1;
    p[AUDCTL]=0; p[AUDF1]=pokey_audf[n]; p[AUDC1]=(uint8_t)(0xA0|POKEY_VOL);
}
static void pokey_off(uint8_t right){ (right?POKEY2:POKEY1)[AUDC1]=0; }

/* Quad POKEY check: play a simple two-note chord on the same stereo side,
 * using the second POKEY pair as the harmony IC.  Left = POKEY1+POKEY3,
 * right = POKEY2+POKEY4. */
static void qpokey_on(uint8_t n, uint8_t right)
{
    volatile uint8_t *root = right ? POKEY2 : POKEY1;
    volatile uint8_t *harm = right ? POKEY4 : POKEY3;
    root[AUDCTL]=0; harm[AUDCTL]=0;
    root[AUDF1]=pokey_audf[n];                 root[AUDC1]=(uint8_t)(0xA0|0x05);
    harm[AUDF1]=pokey_audf[n]>>1; harm[AUDC1]=(uint8_t)(0xA0|0x05);
}
static void qpokey_off(uint8_t right)
{
    (right?POKEY2:POKEY1)[AUDC1]=0;
    (right?POKEY4:POKEY3)[AUDC1]=0;
}
/* SID: avoid the filter by default.  The previous low-pass settings could
 * easily remove most of the fundamental on PokeyMAX/SID variants.  This uses
 * two SID voices on the selected chip instead: voice 1 is a bright PWM-ish
 * pulse, voice 2 is a saw fifth, giving a much more obvious SID-family sound
 * without the risk of filtering the note away. */
static const uint16_t sid_pw[NUM_NOTES]      = { 0x0380,0x0580,0x0780,0x0980,0x0B00,0x0680 };
static const uint8_t  sid_harmony[NUM_NOTES] = { N_D5,  N_C5,  N_F5,  N_G4,  N_C5,  N_F5  };
static void sid_on(uint8_t n, uint8_t right)
{
    volatile uint8_t *s=right?SID2:SID1;
    uint16_t f=sid_freq[n], f2=sid_freq[sid_harmony[n]], pw=sid_pw[n];

    /* Kill gates first so ADSR reliably retriggers. */
    s[SID_CTRL+0]=0x00;
    s[SID_CTRL+7]=0x00;
    s[SID_CTRL+14]=0x00;

    /* Filter off, maximum volume. */
    s[SID_FCLO]=0x00;
    s[SID_FCHI]=0x00;
    s[SID_RESFILT]=0x00;
    s[SID_MODVOL]=0x0F;

    /* Voice 1: bright pulse, varied pulse width per note. */
    s[SID_AD+0]=0x04;                                /* instant attack, medium decay */
    s[SID_SR+0]=0xF5;                                /* full sustain, audible release */
    s[SID_PWLO+0]=(uint8_t)pw; s[SID_PWHI+0]=(uint8_t)(pw>>8);
    s[SID_FREQLO+0]=(uint8_t)f; s[SID_FREQHI+0]=(uint8_t)(f>>8);

    /* Voice 2: saw harmony, same chip/side, for unmistakably non-POKEY tone. */
    s[SID_AD+7]=0x02;
    s[SID_SR+7]=0xC4;
    s[SID_FREQLO+7]=(uint8_t)f2; s[SID_FREQHI+7]=(uint8_t)(f2>>8);

    s[SID_CTRL+0]=0x41;                              /* pulse + gate */
    s[SID_CTRL+7]=0x21;                              /* saw + gate */
}
static void sid_off(uint8_t right)
{
    volatile uint8_t *s=right?SID2:SID1;
    s[SID_CTRL+0]=0x40;                              /* release pulse */
    s[SID_CTRL+7]=0x20;                              /* release saw */
}
static const uint8_t psg_noise[NUM_NOTES] = { 5,6,4,3,3,2 };
static void psg_on(uint8_t n, uint8_t right)
{
    volatile uint8_t *g=right?PSG2:PSG1;
    uint16_t tp=psg_tp[n];
    uint16_t fifth=(uint16_t)(((uint32_t)tp * 2u) / 3u);  /* fifth above root */
    uint16_t env=(uint16_t)(tp << 3);                      /* note-related envelope speed */
    psg_silence(PSG1); psg_silence(PSG2);

    /* AY/YM-ish rather than POKEY-ish: clean square root, square fifth,
     * hardware decay envelope on A, and a separate noise tick on C. */
    g[0x00]=(uint8_t)tp;    g[0x01]=(uint8_t)((tp>>8)&0x0F);       /* tone A root */
    g[0x02]=(uint8_t)fifth; g[0x03]=(uint8_t)((fifth>>8)&0x0F);    /* tone B fifth */
    g[0x04]=0;             g[0x05]=0;                             /* tone C unused */
    g[0x06]=psg_noise[n];                                         /* noise period */
    g[0x0B]=(uint8_t)env;  g[0x0C]=(uint8_t)(env>>8);             /* envelope period */
    g[0x0D]=0x00;                                                 /* one-shot decay */
    g[0x07]=0x1C;                                                 /* tone A+B, noise C */
    g[0x08]=0x10;                                                 /* A uses envelope */
    g[0x09]=0x08;                                                 /* B fixed quieter */
    g[0x0A]=0x06;                                                 /* C noise tick */
}
static void psg_off(uint8_t right){ psg_silence(right?PSG2:PSG1); }
/* COVOX PCM player.
 *
 * COVOX_PCM[] is unsigned 8-bit PCM, recorded at C4 at 12 kHz, to match
 * the timer output rate below (so step 256 = 1.0x = recorded pitch).
 * We pace output from POKEY timer 1 at ~15.3 kHz, but reach the handler
 * through the GENERIC IRQ vector VIMIRQ ($0216) instead of VTIMR1: the OS
 * ROM IRQ handler does JMP (VIMIRQ) before any IRQST polling, so this is
 * the lowest-latency path and lets the trimmed ISR finish inside the
 * ~117-cycle timer period (it could NOT through the OS dispatcher, which
 * is why playback ran at ~half speed and notes were held too long).
 *
 * The keyboard IRQ is disabled for the duration of each COVOX note so that
 * timer 1 is the only source reaching VIMIRQ (no source check needed in
 * the ISR).  HELP is therefore not sampled during a COVOX note, only
 * between notes - acceptable for a short test tone.
 *
 * Pitch: Q8 step relative to C4 (256 = 1.0x).  Stop is by sample end PAGE
 * (high byte of pointer) - coarse to ~256 bytes, fine for ending a note.
 * Hot-path state (pointer, fraction, step) lives in zero page; the C side
 * fills it via covox_setup_zp() before enabling the timer.
 */
/* Q8 pitch step per note, indexed by the NOTE enum (G4,F4,Bb4,C5,D5,F5) -
 * NOT phrase order.  256 = 1.0x = recorded C4 pitch.  (D5 and F5 were
 * previously swapped here, which made COVOX's last three notes climb
 * C->F->up instead of C->F->D.) */
static const uint16_t covox_c4_step_q8[NUM_NOTES] = {
    384, 342, 456, 512, 575, 683
    /* G4   F4   Bb4  C5   D5   F5 */
};

extern void covox_irq_player(void);
extern void covox_setup_zp(void);
volatile uint8_t  covox_irq_active;
volatile uint8_t  covox_irq_right;
volatile uint8_t  covox_irq_step_i;
volatile uint8_t  covox_irq_step_f;
volatile uint8_t  covox_irq_bg;
volatile uint16_t covox_irq_ptr;
volatile uint8_t  covox_irq_endpg;     /* stop when ptr high byte >= this */

#define COVOX_TIMER_AUDF 145       /* 1.79 MHz / (145+4) ~= 12.0 kHz       */
                                   /* matches the 12 kHz source recording  */

static uint16_t saved_vimirq;

static void covox_irq_stop(void)
{
    __asm__("sei");
    covox_irq_active = 0;
    /* disable timer-1 IRQ, restore keyboard IRQ and the OS IRQ vector */
    POKMSK = saved_pokmsk;
    POKEY1[IRQEN] = saved_pokmsk;
    VIMIRQ = saved_vimirq;
    __asm__("cli");
    /* The ISR was writing the sample value to COLBK; put the background
     * back.  (The OS VBI also re-copies the COLOR4 shadow each frame, but
     * restore now to avoid a one-frame flash of the last sample value.) */
    COLBK = covox_irq_bg;
    covox_cleanup_sample_engine();
}

static void covox_irq_start(uint8_t n, uint8_t right)
{
    uint16_t step = covox_c4_step_q8[n];
    uint16_t endp = (uint16_t)COVOX_PCM + (uint16_t)COVOX_PCM_BYTES;

    covox_cleanup_sample_engine();

    covox_irq_ptr    = (uint16_t)COVOX_PCM;
    covox_irq_endpg  = (uint8_t)(endp >> 8);
    covox_irq_step_i = (uint8_t)(step >> 8);
    covox_irq_step_f = (uint8_t)step;
    covox_irq_right  = right;
    covox_irq_bg     = COLOR4;
    covox_setup_zp();              /* load ptr/frac/step into zero page */

    __asm__("sei");
    /* Take over the generic IRQ vector (fast path, skips OS dispatch). */
    saved_vimirq = VIMIRQ;
    VIMIRQ = (uint16_t)covox_irq_player;
    /* Timer 1 paces playback; channel stays silent (AUDC1=0). */
    POKEY1[AUDC1]  = 0;
    POKEY1[AUDCTL] |= 0x40;        /* clock channel 1 at 1.79 MHz */
    POKEY1[AUDF1]  = COVOX_TIMER_AUDF;
    covox_irq_active = 1;
    /* Only timer-1 IRQ enabled during the note (keyboard off). */
    POKMSK = IRQEN_TIMER1;
    POKEY1[IRQEN] = IRQEN_TIMER1;
    POKEY1[STIMER] = 0;
    __asm__("cli");
}

static void covox_play(uint8_t n, uint8_t right, uint8_t frames)
{
    covox_irq_start(n, right);
    wait_frames(frames);
    covox_irq_stop();
}

/* Embedded sample data.  This table is signed 4-bit ADPCM loaded into
 * PokeyMAX sample RAM at startup.  */
#define PIANO_SAMPLE_BYTES 3896
static const uint8_t piano_sample[PIANO_SAMPLE_BYTES] = {
    247, 119, 245, 63, 178, 140, 204, 115, 235, 129, 51, 22, 52, 32, 203, 153,
    170, 170, 141, 208, 9, 132, 42, 178, 26, 133, 42, 21, 41, 162, 115, 128,
    24, 186, 114, 234, 17, 139, 144, 18, 68, 16, 68, 12, 184, 139, 161, 13,
    177, 156, 176, 25, 4, 12, 132, 73, 193, 72, 184, 52, 131, 114, 186, 19,
    139, 162, 27, 217, 171, 2, 32, 119, 49, 168, 156, 160, 25, 186, 137, 169,
    173, 144, 48, 165, 65, 171, 52, 187, 49, 193, 119, 25, 129, 24, 160, 137,
    168, 138, 161, 59, 151, 114, 18, 11, 201, 137, 136, 169, 146, 143, 168, 9,
    217, 51, 152, 48, 201, 51, 191, 7, 32, 0, 8, 8, 8, 152, 11, 192,
    26, 176, 68, 87, 32, 155, 169, 145, 155, 161, 42, 250, 24, 235, 17, 144,
    18, 155, 39, 12, 145, 67, 33, 1, 1, 42, 132, 25, 177, 140, 144, 186,
    7, 118, 24, 171, 152, 138, 184, 32, 217, 8, 203, 144, 155, 70, 138, 131,
    56, 187, 132, 68, 32, 1, 42, 135, 25, 136, 136, 132, 28, 160, 55, 83,
    9, 202, 26, 201, 24, 169, 0, 190, 152, 171, 4, 24, 160, 83, 25, 201,
    4, 48, 51, 9, 130, 96, 129, 137, 132, 16, 190, 160, 87, 96, 137, 136,
    171, 0, 170, 129, 172, 152, 174, 177, 32, 155, 3, 68, 156, 144, 0, 38,
    40, 130, 24, 52, 137, 160, 68, 24, 159, 195, 115, 16, 8, 155, 152, 171,
    160, 140, 168, 174, 218, 16, 8, 169, 38, 40, 152, 137, 6, 33, 16, 0,
    18, 64, 200, 40, 7, 59, 250, 36, 34, 50, 138, 152, 172, 169, 154, 201,
    10, 237, 184, 16, 171, 146, 49, 34, 189, 129, 21, 50, 32, 131, 96, 128,
    140, 146, 117, 172, 144, 34, 84, 16, 136, 9, 184, 138, 186, 1, 143, 235,
    129, 11, 168, 145, 51, 24, 201, 0, 23, 56, 146, 50, 19, 89, 250, 54,
    25, 170, 152, 54, 65, 16, 24, 154, 152, 155, 168, 51, 254, 168, 136, 154,
    169, 129, 64, 152, 155, 130, 98, 144, 64, 129, 115, 172, 147, 66, 9, 190,
    162, 67, 51, 50, 9, 154, 153, 253, 18, 10, 203, 153, 137, 157, 176, 17,
    33, 13, 184, 35, 128, 66, 186, 118, 25, 185, 34, 66, 11, 234, 2, 52,
    81, 33, 136, 0, 191, 144, 16, 155, 188, 152, 138, 217, 136, 1, 49, 218,
    1, 168, 83, 140, 133, 81, 138, 168, 20, 65, 171, 186, 20, 53, 98, 0,
    1, 10, 217, 129, 0, 172, 186, 152, 188, 171, 160, 99, 154, 144, 155, 7,
    42, 176, 69, 32, 139, 192, 68, 16, 203, 152, 130, 85, 48, 3, 32, 188,
    185, 18, 10, 205, 153, 170, 156, 218, 18, 24, 130, 156, 177, 98, 171, 1,
    70, 40, 186, 163, 114, 137, 154, 187, 6, 66, 18, 66, 24, 186, 184, 49,
    156, 155, 233, 137, 206, 144, 8, 2, 25, 201, 52, 0, 187, 21, 67, 43,
    218, 21, 32, 136, 174, 176, 19, 50, 55, 34, 26, 187, 0, 8, 137, 236,
    128, 157, 186, 9, 161, 80, 169, 160, 82, 10, 168, 22, 82, 155, 160, 64,
    35, 10, 252, 128, 17, 34, 69, 49, 139, 152, 152, 18, 174, 169, 26, 249,
    138, 184, 3, 41, 202, 19, 51, 157, 192, 86, 41, 153, 0, 2, 66, 158,
    170, 128, 16, 20, 115, 16, 152, 169, 130, 43, 216, 9, 186, 188, 204, 161,
    34, 154, 184, 52, 80, 191, 131, 66, 8, 8, 152, 69, 32, 173, 169, 128,
    152, 84, 66, 32, 155, 177, 32, 187, 136, 172, 186, 191, 233, 129, 24, 153,
    152, 70, 10, 200, 19, 49, 24, 154, 133, 83, 41, 218, 137, 155, 146, 115,
    67, 41, 201, 16, 137, 152, 139, 185, 140, 252, 160, 129, 0, 187, 150, 65,
    156, 145, 34, 51, 10, 154, 7, 97, 9, 168, 139, 185, 17, 87, 65, 137,
    136, 9, 136, 9, 171, 129, 191, 202, 168, 17, 27, 232, 37, 25, 169, 128,
    52, 50, 139, 184, 87, 32, 168, 9, 186, 155, 148, 118, 40, 128, 136, 137,
    8, 154, 144, 137, 221, 187, 160, 49, 206, 130, 34, 26, 170, 130, 84, 33,
    156, 147, 98, 1, 8, 170, 170, 235, 22, 82, 16, 0, 153, 0, 153, 185,
    136, 138, 253, 202, 2, 11, 186, 19, 66, 10, 187, 18, 117, 41, 185, 35,
    66, 34, 154, 128, 223, 184, 38, 34, 33, 137, 129, 136, 186, 152, 152, 13,
    254, 152, 24, 154, 168, 19, 33, 153, 172, 7, 66, 154, 129, 18, 68, 8,
    129, 43, 253, 128, 19, 52, 16, 8, 1, 154, 138, 186, 36, 175, 250, 128,
    136, 171, 144, 33, 34, 141, 200, 68, 32, 137, 128, 38, 48, 128, 50, 159,
    201, 144, 36, 66, 0, 1, 136, 9, 171, 146, 104, 236, 170, 129, 154, 217,
    128, 18, 49, 220, 130, 51, 33, 154, 148, 114, 8, 19, 33, 205, 187, 145,
    84, 33, 33, 8, 0, 12, 200, 34, 27, 253, 153, 8, 170, 170, 160, 68,
    10, 201, 17, 67, 40, 201, 37, 33, 1, 67, 42, 191, 202, 2, 51, 51,
    48, 3, 57, 235, 161, 67, 143, 202, 144, 169, 155, 218, 131, 64, 154, 169,
    5, 81, 10, 145, 66, 128, 37, 50, 25, 221, 185, 17, 37, 49, 17, 34,
    10, 219, 3, 80, 204, 169, 170, 136, 191, 184, 18, 16, 140, 184, 68, 33,
    170, 20, 16, 18, 53, 67, 27, 251, 154, 2, 67, 34, 35, 66, 141, 200,
    35, 26, 203, 203, 160, 139, 252, 128, 1, 24, 171, 162, 99, 9, 128, 18,
    2, 33, 87, 65, 154, 188, 169, 19, 51, 51, 85, 32, 188, 129, 33, 9,
    191, 201, 8, 156, 187, 161, 19, 43, 234, 19, 64, 128, 128, 19, 41, 5,
    116, 32, 138, 219, 169, 34, 50, 37, 83, 26, 184, 1, 19, 43, 253, 152,
    136, 189, 171, 129, 34, 173, 161, 34, 18, 9, 2, 80, 152, 39, 82, 48,
    156, 203, 160, 34, 1, 86, 49, 137, 152, 130, 50, 158, 187, 136, 155, 236,
    186, 131, 42, 202, 129, 20, 32, 136, 52, 8, 128, 55, 114, 16, 156, 202,
    0, 25, 3, 115, 17, 10, 152, 19, 48, 190, 169, 153, 155, 254, 152, 16,
    137, 153, 129, 49, 136, 19, 50, 9, 162, 119, 51, 56, 205, 169, 8, 160,
    38, 52, 32, 138, 144, 52, 25, 171, 187, 128, 191, 252, 128, 8, 10, 169,
    18, 16, 128, 19, 49, 153, 132, 117, 67, 43, 202, 153, 154, 152, 70, 66,
    32, 170, 2, 50, 8, 204, 160, 25, 252, 186, 128, 8, 187, 160, 34, 25,
    130, 84, 24, 152, 129, 119, 81, 137, 153, 138, 154, 161, 69, 67, 9, 152,
    17, 34, 27, 234, 1, 139, 251, 201, 0, 138, 170, 145, 32, 9, 4, 50,
    3, 143, 164, 116, 32, 136, 153, 155, 186, 161, 117, 49, 136, 136, 3, 64,
    155, 168, 1, 175, 219, 168, 8, 154, 201, 1, 25, 145, 34, 38, 41, 218,
    22, 82, 32, 136, 138, 170, 219, 131, 115, 33, 136, 144, 52, 25, 155, 144,
    40, 223, 202, 144, 136, 172, 144, 24, 136, 8, 3, 113, 10, 200, 39, 50,
    32, 9, 137, 174, 201, 19, 68, 32, 138, 2, 51, 25, 201, 130, 26, 253,
    186, 144, 155, 186, 137, 129, 9, 160, 85, 65, 173, 145, 68, 66, 16, 129,
    11, 220, 184, 20, 82, 24, 136, 19, 49, 138, 168, 33, 140, 254, 169, 136,
    170, 169, 160, 8, 137, 161, 117, 25, 170, 129, 85, 49, 1, 32, 155, 236,
    168, 36, 65, 24, 128, 35, 64, 169, 144, 50, 143, 234, 152, 153, 171, 169,
    152, 24, 188, 134, 65, 9, 170, 149, 68, 18, 33, 32, 157, 218, 168, 53,
    34, 8, 130, 52, 16, 154, 3, 89, 204, 188, 153, 171, 172, 186, 1, 140,
    184, 52, 67, 139, 217, 36, 67, 51, 67, 40, 175, 202, 144, 53, 32, 128,
    17, 66, 25, 160, 50, 41, 252, 185, 170, 171, 220, 144, 9, 170, 168, 54,
    49, 171, 184, 69, 51, 67, 53, 16, 204, 202, 130, 51, 17, 8, 39, 33,
    152, 129, 50, 11, 235, 172, 169, 204, 186, 136, 138, 172, 161, 98, 32, 186,
    144, 53, 52, 68, 51, 41, 221, 185, 1, 66, 24, 130, 53, 32, 152, 3,
    49, 155, 237, 168, 170, 219, 185, 152, 139, 203, 148, 66, 25, 171, 130, 54,
    35, 100, 52, 10, 203, 186, 20, 33, 0, 18, 99, 25, 129, 34, 34, 174,
    202, 154, 189, 203, 153, 136, 172, 201, 19, 49, 25, 216, 0, 35, 68, 101,
    49, 138, 204, 152, 18, 17, 8, 37, 50, 0, 0, 37, 40, 187, 188, 172,
    205, 187, 152, 137, 204, 153, 20, 33, 153, 153, 131, 17, 87, 68, 50, 155,
    219, 144, 19, 8, 2, 83, 34, 8, 4, 51, 25, 188, 187, 191, 204, 186,
    137, 155, 189, 160, 50, 33, 139, 161, 25, 132, 118, 68, 48, 170, 202, 144,
    32, 129, 18, 84, 32, 0, 20, 34, 10, 170, 171, 206, 234, 169, 152, 156,
    186, 161, 36, 24, 152, 8, 144, 128, 119, 98, 16, 155, 185, 0, 0, 128,
    36, 82, 16, 0, 53, 32, 137, 168, 139, 251, 219, 168, 154, 187, 234, 130,
    32, 129, 9, 129, 140, 165, 116, 67, 24, 171, 185, 128, 25, 130, 68, 66,
    0, 19, 52, 72, 152, 9, 155, 253, 186, 168, 169, 205, 168, 8, 17, 25,
    130, 26, 201, 132, 119, 50, 8, 170, 168, 8, 153, 2, 83, 33, 17, 21,
    66, 0, 136, 0, 159, 203, 202, 160, 172, 188, 153, 129, 24, 1, 32, 139,
    219, 23, 100, 50, 25, 185, 168, 136, 169, 51, 69, 16, 18, 52, 51, 25,
    130, 42, 250, 236, 184, 139, 186, 204, 160, 8, 128, 18, 17, 142, 200, 20,
    116, 49, 136, 169, 9, 153, 152, 36, 51, 1, 20, 69, 40, 0, 16, 128,
    175, 249, 153, 152, 171, 202, 136, 152, 0, 20, 24, 187, 202, 55, 115, 48,
    9, 137, 137, 169, 145, 50, 67, 0, 55, 49, 18, 41, 4, 27, 235, 219,
    169, 171, 219, 186, 168, 138, 148, 50, 41, 220, 169, 55, 115, 17, 136, 136,
    153, 168, 128, 36, 40, 4, 65, 36, 41, 2, 48, 139, 223, 186, 138, 202,
    189, 168, 153, 168, 3, 50, 25, 221, 168, 101, 51, 32, 8, 137, 169, 170,
    149, 56, 3, 50, 69, 49, 1, 34, 34, 142, 235, 171, 170, 205, 185, 170,
    172, 145, 17, 35, 27, 251, 161, 116, 65, 17, 25, 128, 171, 161, 16, 2,
    48, 39, 66, 17, 17, 4, 56, 170, 252, 153, 171, 219, 154, 171, 202, 136,
    18, 81, 139, 219, 148, 99, 51, 48, 2, 140, 185, 8, 130, 32, 131, 115,
    36, 48, 20, 34, 16, 158, 203, 155, 205, 170, 171, 188, 170, 144, 19, 67,
    175, 200, 16, 84, 49, 18, 40, 154, 169, 152, 33, 146, 49, 101, 34, 34,
    33, 68, 40, 189, 185, 190, 202, 154, 202, 186, 201, 137, 38, 24, 171, 171,
    7, 66, 35, 51, 136, 138, 200, 8, 1, 9, 22, 65, 35, 34, 20, 67,
    9, 187, 173, 235, 186, 203, 173, 185, 187, 163, 66, 0, 191, 184, 37, 35,
    98, 17, 24, 153, 153, 128, 8, 129, 52, 67, 67, 17, 55, 33, 152, 10,
    203, 220, 169, 189, 185, 173, 185, 1, 19, 42, 250, 136, 19, 83, 83, 33,
    8, 153, 152, 9, 136, 2, 52, 84, 49, 3, 114, 0, 24, 137, 191, 201,
    155, 186, 188, 219, 138, 146, 65, 154, 172, 160, 36, 85, 50, 33, 25, 153,
    128, 153, 0, 16, 7, 82, 130, 51, 66, 24, 20, 141, 202, 156, 202, 139,
    203, 172, 200, 2, 17, 155, 171, 153, 39, 69, 50, 33, 9, 136, 10, 144,
    41, 178, 115, 18, 50, 100, 16, 3, 56, 202, 173, 218, 139, 202, 172, 203,
    152, 2, 41, 153, 188, 184, 54, 84, 51, 32, 8, 25, 184, 40, 185, 34,
    100, 1, 55, 48, 130, 66, 136, 11, 235, 155, 203, 156, 203, 218, 144, 16,
    0, 137, 203, 168, 54, 69, 50, 1, 40, 160, 9, 136, 154, 53, 57, 134,
    97, 1, 33, 18, 32, 202, 189, 187, 187, 204, 205, 185, 136, 0, 0, 11,
    203, 152, 7, 114, 2, 17, 8, 128, 128, 154, 163, 80, 177, 99, 17, 50,
    3, 98, 9, 155, 204, 171, 187, 175, 203, 185, 152, 0, 40, 170, 175, 176,
    83, 67, 51, 33, 24, 145, 44, 216, 65, 169, 34, 52, 66, 16, 53, 50,
    25, 187, 220, 187, 156, 205, 187, 170, 176, 24, 130, 13, 234, 129, 52, 67,
    52, 56, 129, 57, 202, 33, 137, 136, 5, 84, 17, 1, 67, 65, 128, 156,
    202, 170, 157, 234, 155, 186, 137, 128, 34, 190, 186, 146, 98, 53, 65, 0,
    49, 154, 128, 0, 9, 184, 69, 66, 32, 4, 82, 33, 8, 158, 185, 138,
    218, 203, 203, 170, 170, 18, 40, 191, 200, 16, 4, 114, 0, 34, 24, 137,
    129, 24, 155, 145, 55, 49, 17, 37, 66, 19, 43, 218, 154, 187, 205, 219,
    154, 203, 152, 2, 59, 249, 136, 168, 85, 17, 34, 49, 16, 168, 17, 0,
    190, 130, 52, 48, 3, 82, 53, 65, 153, 169, 187, 156, 251, 186, 202, 189,
    161, 34, 186, 153, 218, 132, 66, 51, 53, 50, 136, 136, 34, 11, 204, 133,
    64, 1, 33, 35, 115, 16, 9, 187, 137, 235, 203, 217, 157, 202, 17, 136,
    136, 171, 185, 4, 82, 36, 67, 34, 137, 1, 19, 142, 184, 35, 18, 66,
    0, 100, 50, 48, 155, 137, 187, 207, 233, 137, 205, 144, 136, 0, 137, 171,
    168, 34, 53, 54, 66, 16, 9, 3, 56, 202, 144, 2, 83, 8, 36, 53,
    82, 8, 8, 184, 28, 251, 144, 175, 170, 153, 128, 0, 155, 186, 160, 49,
    38, 99, 52, 24, 145, 52, 138, 138, 184, 68, 1, 17, 131, 117, 17, 16,
    168, 18, 207, 160, 153, 189, 186, 170, 129, 8, 157, 169, 136, 1, 38, 69,
    50, 137, 34, 32, 24, 203, 130, 19, 65, 144, 100, 35, 82, 138, 20, 11,
    219, 152, 173, 188, 203, 169, 0, 9, 202, 153, 153, 0, 23, 115, 16, 1,
    17, 34, 139, 170, 146, 98, 137, 35, 3, 119, 8, 1, 18, 172, 168, 154,
    172, 204, 202, 168, 0, 138, 186, 169, 8, 217, 87, 34, 17, 128, 51, 16,
    10, 218, 36, 9, 34, 153, 87, 33, 136, 51, 24, 187, 201, 170, 190, 205,
    186, 144, 137, 138, 202, 1, 204, 129, 84, 51, 8, 19, 35, 66, 203, 145,
    0, 35, 27, 181, 117, 24, 1, 50, 25, 187, 186, 186, 175, 251, 170, 152,
    8, 188, 129, 154, 202, 164, 100, 16, 17, 1, 67, 9, 153, 137, 4, 48,
    202, 39, 65, 128, 19, 65, 9, 171, 170, 137, 223, 187, 202, 1, 170, 185,
    128, 156, 219, 21, 51, 66, 144, 53, 49, 136, 138, 176, 83, 12, 161, 84,
    16, 1, 52, 50, 9, 219, 128, 156, 189, 235, 152, 137, 170, 169, 33, 221,
    160, 2, 99, 24, 130, 67, 18, 24, 203, 4, 56, 172, 132, 66, 0, 3,
    67, 82, 155, 153, 168, 138, 254, 186, 137, 137, 203, 1, 11, 188, 186, 55,
    65, 128, 34, 51, 66, 138, 200, 51, 27, 217, 37, 48, 17, 2, 116, 16,
    153, 154, 130, 157, 219, 202, 8, 188, 169, 24, 137, 206, 160, 67, 16, 17,
    19, 69, 33, 170, 146, 51, 175, 128, 19, 50, 137, 39, 66, 17, 155, 160,
    8, 143, 219, 160, 155, 202, 153, 1, 12, 250, 130, 34, 17, 17, 20, 114,
    24, 169, 19, 25, 170, 145, 84, 40, 146, 37, 83, 41, 169, 144, 33, 238,
    185, 138, 171, 188, 176, 34, 206, 185, 2, 34, 64, 128, 55, 66, 137, 145,
    18, 9, 187, 133, 64, 16, 136, 70, 50, 8, 170, 3, 44, 251, 153, 170,
    172, 218, 129, 42, 204, 154, 2, 51, 40, 145, 119, 40, 136, 0, 33, 12,
    152, 18, 50, 138, 132, 84, 51, 11, 160, 65, 158, 171, 201, 138, 206, 184,
    17, 138, 203, 169, 19, 81, 11, 134, 82, 16, 137, 18, 32, 171, 168, 55,
    40, 152, 1, 101, 33, 169, 2, 24, 171, 235, 160, 142, 203, 152, 16, 155,
    189, 184, 37, 41, 160, 53, 82, 25, 129, 35, 24, 189, 146, 51, 16, 154,
    151, 114, 24, 128, 1, 0, 175, 169, 8, 174, 187, 128, 8, 154, 236, 145,
    49, 9, 152, 85, 49, 136, 17, 51, 27, 217, 2, 35, 58, 217, 55, 66,
    128, 8, 34, 26, 236, 144, 9, 220, 185, 136, 0, 158, 201, 1, 17, 138,
    147, 114, 33, 136, 20, 49, 155, 153, 132, 67, 156, 162, 83, 51, 8, 2,
    82, 141, 202, 129, 141, 203, 186, 145, 25, 236, 170, 19, 24, 185, 6, 67,
    40, 145, 82, 32, 154, 170, 39, 40, 169, 130, 68, 50, 144, 36, 64, 174,
    160, 8, 172, 190, 184, 0, 10, 220, 160, 33, 138, 153, 39, 65, 136, 2,
    51, 32, 173, 145, 52, 9, 169, 132, 83, 32, 129, 70, 26, 186, 136, 128,
    175, 218, 168, 17, 157, 202, 0, 16, 139, 177, 85, 16, 0, 18, 67, 41,
    202, 2, 50, 27, 200, 37, 67, 9, 6, 65, 137, 170, 161, 25, 207, 188,
    144, 40, 190, 169, 129, 24, 172, 131, 67, 17, 8, 54, 81, 10, 168, 3,
    49, 155, 169, 86, 56, 161, 68, 33, 154, 186, 2, 10, 239, 201, 1, 10,
    188, 185, 34, 139, 201, 20, 50, 32, 145, 116, 33, 138, 160, 35, 33, 174,
    164, 65, 8, 130, 83, 48, 171, 170, 4, 27, 255, 168, 1, 138, 204, 145,
    24, 155, 144, 4, 65, 136, 132, 68, 32, 169, 136, 37, 42, 186, 36, 49,
    137, 6, 98, 8, 154, 152, 35, 143, 203, 168, 33, 175, 186, 1, 9, 170,
    160, 53, 56, 137, 23, 66, 24, 154, 130, 81, 139, 144, 37, 24, 160, 55,
    50, 24, 202, 130, 33, 175, 218, 128, 41, 204, 169, 1, 137, 187, 146, 68,
    25, 161, 84, 52, 25, 185, 36, 32, 171, 147, 82, 137, 146, 71, 51, 138,
    186, 19, 64, 207, 200, 1, 138, 203, 168, 16, 154, 218, 3, 65, 153, 130,
    70, 65, 137, 160, 50, 41, 186, 36, 33, 153, 145, 119, 48, 138, 153, 35,
    58, 253, 144, 0, 154, 218, 152, 0, 156, 184, 35, 32, 154, 147, 119, 32,
    137, 129, 34, 138, 144, 34, 48, 188, 150, 100, 16, 154, 144, 66, 12, 203,
    144, 24, 174, 187, 145, 25, 234, 144, 19, 24, 171, 7, 68, 24, 153, 34,
    32, 153, 145, 53, 26, 186, 23, 98, 16, 171, 3, 65, 158, 186, 1, 9,
    191, 185, 0, 138, 202, 128, 51, 11, 201, 55, 82, 136, 145, 18, 0, 153,
    131, 66, 140, 168, 23, 98, 9, 168, 19, 48, 220, 169, 1, 27, 236, 152,
    8, 155, 187, 2, 65, 140, 176, 71, 49, 137, 16, 33, 24, 169, 38, 16,
    139, 200, 71, 49, 138, 161, 52, 9, 190, 160, 17, 157, 187, 152, 136, 204,
    184, 3, 48, 191, 146, 68, 32, 128, 1, 50, 154, 145, 68, 32, 190, 161,
    100, 48, 170, 2, 51, 12, 219, 146, 41, 190, 187, 128, 154, 219, 185, 53,
    25, 218, 3, 82, 17, 128, 35, 16, 154, 131, 100, 26, 190, 130, 114, 24,
    153, 3, 50, 158, 170, 2, 9, 219, 185, 152, 156, 219, 161, 66, 140, 184,
    20, 66, 24, 2, 51, 24, 185, 21, 83, 11, 250, 20, 66, 9, 152, 37,
    40, 172, 168, 18, 141, 187, 185, 9, 175, 202, 2, 32, 187, 184, 55, 34,
    0, 19, 50, 137, 169, 39, 97, 155, 200, 37, 50, 153, 145, 68, 25, 202,
    145, 17, 173, 187, 169, 25, 252, 184, 18, 24, 188, 161, 67, 33, 1, 67,
    50, 139, 200, 116, 32, 188, 160, 55, 40, 137, 131, 66, 138, 201, 130, 8,
    173, 202, 129, 155, 251, 160, 34, 138, 187, 147, 68, 16, 35, 52, 80, 156,
    147, 99, 25, 203, 146, 83, 41, 168, 38, 48, 155, 169, 17, 24, 237, 168,
    0, 173, 218, 128, 33, 154, 186, 3, 35, 34, 37, 84, 25, 186, 21, 83,
    138, 202, 132, 51, 10, 144, 99, 24, 155, 153, 20, 11, 250, 161, 8, 221,
    185, 129, 32, 171, 169, 17, 51, 32, 55, 113, 9, 184, 38, 48, 139, 201,
    35, 65, 153, 132, 66, 24, 187, 145, 51, 175, 202, 1, 11, 236, 185, 17,
    25, 171, 153, 19, 32, 131, 118, 48, 139, 177, 71, 16, 155, 160, 52, 24,
    152, 35, 83, 10, 187, 3, 80, 205, 168, 2, 141, 220, 144, 0, 8, 170,
    144, 17, 8, 133, 99, 32, 187, 147, 116, 24, 187, 147, 51, 136, 161, 70,
    49, 139, 185, 37, 26, 204, 144, 33, 191, 217, 152, 16, 137, 169, 128, 16,
    152, 23, 82, 42, 170, 133, 68, 10, 185, 3, 33, 138, 130, 85, 33, 171,
    144, 51, 12, 234, 130, 41, 221, 201, 144, 1, 154, 169, 1, 9, 169, 87,
    65, 9, 186, 38, 50, 155, 168, 34, 32, 138, 133, 99, 41, 186, 130, 66,
    158, 185, 34, 10, 251, 202, 128, 24, 155, 152, 16, 156, 162, 116, 65, 154,
    176, 69, 32, 154, 144, 18, 24, 169, 23, 66, 25, 185, 19, 72, 204, 144,
    33, 139, 252, 185, 0, 9, 170, 128, 26, 188, 147, 119, 40, 154, 145, 82,
    16, 169, 145, 34, 10, 176, 85, 65, 138, 160, 51, 27, 234, 129, 33, 158,
    204, 169, 0, 138, 168, 0, 153, 219, 132, 117, 8, 169, 131, 66, 8, 154,
    2, 48, 170, 162, 117, 32, 154, 130, 50, 156, 202, 2, 49, 191, 219, 152,
    9, 154, 136, 1, 156, 234, 22, 66, 138, 168, 20, 50, 9, 168, 19, 40,
    203, 133, 99, 24, 169, 19, 33, 189, 185, 20, 33, 222, 186, 152, 138, 153,
    144, 32, 207, 185, 69, 49, 154, 160, 37, 33, 10, 128, 34, 26, 218, 22,
    66, 10, 136, 35, 24, 203, 184, 53, 41, 236, 186, 152, 154, 170, 2, 57,
    255, 145, 51, 33, 171, 145, 83, 49, 153, 130, 66, 157, 184, 70, 33, 137,
    145, 50, 26, 204, 144, 67, 11, 220, 186, 168, 171, 200, 52, 13, 219, 130,
    83, 24, 170, 131, 67, 32, 153, 20, 48, 204, 161, 99, 32, 152, 2, 51,
    140, 219, 3, 50, 140, 235, 186, 153, 219, 162, 81, 173, 202, 4, 50, 25,
    170, 19, 82, 24, 144, 67, 26, 219, 132, 82, 24, 144, 3, 50, 174, 185,
    19, 50, 157, 219, 169, 172, 233, 2, 32, 205, 169, 20, 50, 137, 169, 36,
    50, 9, 3, 98, 140, 185, 20, 81, 25, 128, 36, 40, 188, 168, 20, 34,
    190, 186, 153, 236, 168, 19, 42, 222
};

static void sample_build_once(void)
{
    uint16_t i;
    SAM_DMA[0]=0; SAM_CFG[0]=0x33;
    SAM_RAMADDRL[0]=0; SAM_RAMADDRH[0]=0;
    for (i=0;i<PIANO_SAMPLE_BYTES;i++) SAM_RAMDATAI[0]=piano_sample[i];
}
static void sample_on(uint8_t n, uint8_t right)
{
    uint8_t chan=right?2:1; uint16_t per=sample_period[n];
    uint16_t PIANO_SAMPLE_LEN = PIANO_SAMPLE_BYTES*2-1;
    SAM_CFG[0]=0x33;
    SAM_CHANSEL[0]=chan; SAM_DMA[0]=0;
    SAM_ADDRL[0]=0; SAM_ADDRH[0]=0;
    SAM_LENL[0]=PIANO_SAMPLE_LEN&0xff; SAM_LENH[0]=PIANO_SAMPLE_LEN>>8;
    SAM_PERL[0]=(uint8_t)per; SAM_PERH[0]=(uint8_t)(per>>8);
    SAM_VOL[0]=0x30; SAM_DMA[0]=right?0x02:0x01;
    SAM_LENL[0]=0;
    SAM_LENH[0]=0;

}
static void sample_off(uint8_t right){ SAM_DMA[0]=0; SAM_CHANSEL[0]=right?2:1; SAM_VOL[0]=0; SAM_CFG[0]=0x30; }

/* ---- device table ---------------------------------------------------- */
typedef void (*on_fn)(uint8_t, uint8_t);
typedef void (*off_fn)(uint8_t);
typedef struct { const char *name; const char *label_l; const char *label_r; on_fn on; off_fn off; uint8_t cap; } device_t;
static const device_t devices[] = {
    { "POKEY",  "POKEY L",  "POKEY R",  pokey_on,   pokey_off,   0          },
    { "QPOKEY", "QPOKEY L", "QPOKEY R", qpokey_on,  qpokey_off,  CAP_QPOKEY },
    { "SID",    "SID L",    "SID R",    sid_on,     sid_off,     CAP_SID    },
    { "PSG",    "PSG L",    "PSG R",    psg_on,     psg_off,     CAP_PSG    },
    { "COVOX",  "COVOX L",  "COVOX R",  0,          0,           CAP_COVOX  },
    { "SAMPLE", "SAMPLE L", "SAMPLE R", sample_on,  sample_off,  CAP_SAMPLE },
};
#define NUM_DEVICES (sizeof(devices)/sizeof(devices[0]))

/* ---- capabilities ---------------------------------------------------- */
static uint8_t read_caps(uint8_t *ver8)
{
    uint8_t caps,i;
    *CONFIG=CFG_ENABLE; caps=*CFG_CAPS;
    if (ver8) for(i=0;i<8;i++){ *CFG_VERSION=i; ver8[i]=*CFG_VERSION; }
    *CONFIG=CFG_DISABLE;
    return caps;
}
static uint8_t pokeymax_present(void){ return (*CONFIG==1); }


/* ---- play phrase on a device ----------------------------------------- */
static uint8_t play_phrase_on(const device_t *d)
{
    uint8_t i;
    draw_staff_lines();
    draw_treble_clef();
    set_status_for_device(d->name);
    for (i=0;i<6;i++){
        uint8_t n=phrase_note[i];
        uint8_t right = (uint8_t)(i & 1);          /* strict L/R/L/R per note */
        draw_note_original(i);                     /* original XL/XE DVN graphics */
        set_status_for_device(right ? d->label_r : d->label_l);
        silence_all();
        if (d->on==0) covox_play(n, right, phrase_dur[i]);
        else { d->on(n,right); wait_frames(phrase_dur[i]); d->off(right); }
        silence_all();
        wait_frames(4);
        if (help_pressed()) return KEY_ESC;
    }
    return 0;
}

/* ---- main ------------------------------------------------------------ */
int main(void)
{
    uint8_t caps, ver[8], di;

    av_alloc();              /* assign aligned screen/dlist pointers first */
    av_init_title();
    draw_staff_lines();
    draw_treble_clef();

    display_save();
    build_dlist();
    set_colours();
    CHBAS = 0xE0;
    install_dlist();

    if (!pokeymax_present()) {
        set_status_for_device("NO POKEYMAX");
        while (!help_pressed()){}
        display_release();
        return 0;
    }

    caps = read_caps(ver);
    audio_acquire();
    sample_build_once();

    
    for (;;) {
        for (di=0; di<NUM_DEVICES; di++){
            const device_t *d=&devices[di];
            if (d->cap == CAP_QPOKEY) {
                if ((caps & CAP_POKEY_MASK) != CAP_POKEY_MASK) continue;
            } else if (d->cap && !(caps & d->cap)) continue;
            if (play_phrase_on(d)==KEY_ESC){ audio_release(); display_release(); return 0; }
            silence_all();
            wait_frames(30);
        }

        draw_staff_lines();
        draw_treble_clef();
        set_status_for_device("READY");
        wait_frames(8);
        if (help_pressed()){ audio_release(); display_release(); return 0; }
    }
}
