        .export _covox_irq_player
        .export _covox_setup_zp          ; C calls this to load ZP state
        .import _covox_irq_active
        .import _covox_irq_right
        .import _covox_irq_step_i
        .import _covox_irq_step_f
        .import _covox_irq_ptr            ; start pointer (16-bit)
        .import _covox_irq_endpg          ; stop when ptr high byte >= this

; Dedicated zero-page state, in our OWN ZEROPAGE allocation so it never
; collides with cc65 ptr1/tmp* scratch (which the interrupted foreground
; C code may be using when the IRQ fires).
        .segment "ZEROPAGE"
zp_ptr:  .res 2      ; live sample pointer (lo/hi)
zp_frac: .res 1      ; Q8 fraction accumulator
zp_stpf: .res 1      ; step fraction
zp_stpi: .res 1      ; step integer

        .segment "CODE"

; ---------------------------------------------------------------------------
; COVOX raw 8-bit DAC player, reached via the GENERIC IRQ vector VIMIRQ
; ($0216), NOT VTIMR1.  The OS ROM IRQ handler pushes A then JMP (VIMIRQ)
; before any IRQST polling, so this is the lowest-latency user IRQ path.
;
; Because we bypass the OS dispatch we must acknowledge the POKEY timer-1
; latch ourselves (re-write IRQEN).  During the COVOX pass the keyboard IRQ
; is disabled, so timer 1 is the only source reaching us; no chaining.
;
; Hot-path state lives in zero page for speed:
;   zp_ptr     = live sample pointer (lo/hi)
;   zp_frac     = Q8 fraction accumulator
;   zp_stpf     = step fraction (step_f)
;   zp_stpi     = step integer  (step_i)
; These are loaded once by _covox_setup_zp before play starts.  cc65 does
; not use zp_ptr/zp_frac..zp_stpi across our IRQ because we never call C helpers
; during the COVOX pass.
;
; Stack contract: OS pushed A.  We save X/Y, end with PLA/RTI.
; ---------------------------------------------------------------------------

ICOVOX_L = $D280
ICOVOX_R = $D281
POKMSK   = $0010
IRQEN    = $D20E
IRQEN_TIMER1_BIT = $01
COLBK    = $D01A

.proc _covox_irq_player
        ; Reached via JMP (VIMIRQ) at the very top of the OS IRQ handler,
        ; BEFORE the OS pushes A.  So A is LIVE here - we must save it
        ; ourselves.  Save A and X; end with PLA(X), PLA(A), RTI.
        pha
        txa
        pha

        ; Acknowledge POKEY timer-1 IRQ.  A POKEY timer IRQST latch is NOT
        ; cleared by writing IRQEN with the bit still set - you must clear
        ; the bit (write 0) and then set it again.  Do exactly that:
        ;   IRQEN = POKMSK & ~timer1   (clears the latch)
        ;   IRQEN = POKMSK             (re-enables timer 1)
        lda POKMSK
        and #<~IRQEN_TIMER1_BIT       ; clear timer-1 enable -> resets latch
        sta IRQEN
        lda POKMSK
        sta IRQEN                     ; re-enable timer 1

        lda _covox_irq_active
        beq out                     ; not playing -> just ack+exit

        ; output current sample byte to the selected side
        ldx #$00
        lda (zp_ptr,x)                ; A = *zp_ptr   (X=0)
        ldx _covox_irq_right
        bne r_side
        sta ICOVOX_L                ; CH1
        sta ICOVOX_L+3              ; CH4
        jmp advance
r_side:
        sta ICOVOX_R                ; CH2
        sta ICOVOX_R+1             ; CH3

advance:
        ; Visible "proof of playback": the current sample byte (still in A)
        ; is written to the background colour, so the screen flickers with
        ; the waveform while the CPU is feeding the COVOX DAC.
        and #$f
	ora #$30
	sta COLBK

        ; frac += step_f ; ptr += step_i + carry
        clc
        lda zp_frac
        adc zp_stpf
        sta zp_frac
        lda zp_ptr
        adc zp_stpi                    ; + step_i + carry
        sta zp_ptr
        bcc nohi
        inc zp_ptr+1
nohi:
        ; stop test: high byte reached end page?
        lda zp_ptr+1
        cmp _covox_irq_endpg
        bcc out                     ; still below end -> continue
        ; reached end: silence + mark inactive
        lda #$00
        sta _covox_irq_active
        lda #$80
        sta ICOVOX_L
        sta ICOVOX_L+1
        sta ICOVOX_L+2
        sta ICOVOX_L+3
out:
        pla
        tax                         ; restore X
        pla                         ; restore A
        rti
.endproc

; ---------------------------------------------------------------------------
; _covox_setup_zp: copy start pointer + step into zero page before play.
; void covox_setup_zp(void);  - reads the globals set by C.
; ---------------------------------------------------------------------------
.proc _covox_setup_zp
        lda _covox_irq_ptr
        sta zp_ptr
        lda _covox_irq_ptr+1
        sta zp_ptr+1
        lda #$00
        sta zp_frac                    ; frac = 0
        lda _covox_irq_step_f
        sta zp_stpf
        lda _covox_irq_step_i
        sta zp_stpi
        rts
.endproc
