Try the two programs in the attached zip file on a real XL/XE machine. press the joystick fire button!
1) s15.xex program. This works as expected. Nice colours down the screen and a raygun sound when firing
2) s64.xex program. Same, except the colours glitch and shift when firing.
program 1 syncs an IRQ exactly to a scan line using the ~15KHz clock (using 1.79MHz also works).
program 2 is using the ~64KHz Pokey clock. This doesn't sync exactly to a scan line (2 cycles out every line)
But why is program 2 causing such a big problem? The DLI's are NMI's (Non Maskable) so should always run at the correct time regardless of what the IRQ is doing? They are obviously not though. Note that this behaviour doesn't show up on any emulators I've tried, even those that supposedly have cycle exact POKEY emulation. But it happens on my PAL 800XL and 130XE. I thought I was beginning to understand the POKEY timers, but now I'm not sure

Maybe if an IRQ and NMI happen at exactly the same time the NMI doesn't get done? (I'd always heard to the contrary though)
Here's the code. Yes, I know it can be made faster, but it is only an example. Unfortunately I have passed the 64KHz code off as a good way to do sample playback before, but it's not much good if it doesn't work on real hardware.
[codebox]
; IRQ sample playback
; NB XL/XE only
ATARI=0
.include "equates.asm"
; variables
*=$f0
sample .ds 2
sample_nybble .ds 1
sample_value .ds 1
sample_size .ds 1
table_index .ds 1
trigger .ds 1
*=$02e0
.byte <run,>run ; initial run address
; main code
*=$2000
run
lda #0
sta NMIEN ; turn off NMI's
sei
sta IRQEN ; disable IRQ's
lda #128+32+16+2 ; bit 7 on - ram at $5000, bits 4&5 on - 130XE compatible
sta PORTB ; bit 1 on - disable os & rom, bit 0 off - disable basic
lda #<nmi ; setup custom NMI handler
sta $fffa
lda #>nmi
sta $fffa+1
lda #<irq ; setup custom IRQ handler
sta $fffe
lda #>irq
sta $fffe+1
lda #<dlist ; set up a display list
sta DLISTL
lda #>dlist
sta DLISTH
lda #2+32 ; enable normal width screen+screen dma
sta DMACTL
lda #128+64
sta NMIEN ; turn on NMI's - DLI's and VBI
cli ; enable IRQ's
ldx #0
run1 txa
asl a
sta colour_table,x ; create a table of colours
dex
bne run1
; initialize sample play irq
play jsr init ; initialize the sample to play
wait1 lda TRIG0 ; any other stuff can go here, but...
beq wait1 ; just wait until fire button is pressed
wait2 lda TRIG0
bne wait2
beq play
; end of main code
init
sei
lda #<sample_start
sta sample
lda #>sample_start
sta sample+1
lda #[>sample_end->sample_start]
sta sample_size ; sample size in 256 byte pages
lda #0 ;1 ; 0=POKEY 64KHz, 1=15KHz
sta AUDCTL
lda #15 ;3 ; ~64KHz clock 16 = ~4Khz timer, ~15KHz clock 4 = ~4KHz
sta AUDF1 ; in timer 1
lda #$f0 ; test - no polycounters + volume only
sta AUDC1
lda #1
sta IRQEN ; enable timer 1
lda #0
sta sample_nybble ; initialize nybble
ldx #$f8
stx sample_value ; an initial sample value (with no polycounters + volume only bit)
lda #$70
wait3 cmp VCOUNT
bne wait3 ; sync to a scanline
lda #0
sta SKCTL
lda #3
sta SKCTL ; test - reset pokey and polycounters
sta STIMER ; start timers
cli
rts
; IRQ
irq
pha ; save a
lda sample_value
sta AUDC1 ; play sample ASAP to minimise DMA lag
tya
pha ; save y
ldy #0
sty IRQEN ; reset interrupt
lda #1
sta IRQEN ; re-enable only timer 1
eor sample_nybble ; switch between 0 and 1 (right and left nybble)
sta sample_nybble
beq irq1
lda (sample),y
lsr a
lsr a
lsr a
lsr a ; left nybble of sample
bpl irq2 ; always branch
irq1 lda (sample),y
and #$0f ; right nybble of sample
inc sample ; next lo byte of sample
bne irq2
inc sample+1 ; next hi byte of sample
dec sample_size ; check if at end of sample
bne irq2 ; branch if not
tya
sta IRQEN ; end of sample - reset interrupt
beq irq3 ; always branch
irq2 ora #$f0 ; no polycounters+volume only bit
irq3 sta sample_value ; save sample to play next irq
pla
tay
pla ; restore y and a
rti
; NMI
nmi
pha
bit NMIST ; check for cause of interrupt
bvs vbi ; branch if a VBI
; DLI
dli
txa
pha
ldx table_index
lda colour_table,x
sta WSYNC
sta COLBK
inc table_index
pla
tax
pla
rti
; VBI
; critical vbi
vbi
sta NMIRES ; reset interrupt
txa
pha ; save x
tya
pha
lda #0
sta table_index ; reset table index
sta COLBK
; tsx
; lda $104,x
; and #$04
; bne vbiexit ; exit VBI if VBI interrupted an IRQ
; cli ; allow IRQ to interrupt from now on
; deferred VBI here
vbiexit pla
tay
pla
tax
pla ; restore x and a
rti
; display list with some random DLIS
*=$2400
dlist
.byte $70,$70,$70
.byte $8D+$40,<screen_start,>screen_start
.byte $8D,$D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$D,$8D,$8D,$D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$8D,$8D,$D,$8D,$8D,$8D
.byte $8D,$D,$8D,$D,$8D,$8D,$D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$8D,$D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$8D,$D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$8D,$D,$8D,$8D,$8D,$8D,$8D,$8D
.byte $8D,$D,$D,$D,$D
.byte $41,<dlist,>dlist
; sample data
*=$2800
; NB start on a 256 byte page boundary
; sample will stop playing at nearest page boundary if not a multiple of 256 bytes
sample_start
.incbin "raygun44.raw" ; 4KHz, 4-bit raw sample data
sample_end
; screen data
*=$8000
screen_start
; colour table
*=$9000
colour_table
[/codebox]