.EQU AUDIO_BASE $A00 .EQU IRQC_REG $980 .EQU IRQC_EN $80 .EQU CPU_FREQ 77000000 ; args: sample rate START_PCMAUDIO: ; calculate clock divider LOADCP CPU_FREQ SWAP LOADCP _DIV CALL LOADC AUDIO_BASE + 1 SWAP ; put clock divider on ToS STOREI 1 LOADCP 32768 ; set amplitude to biased 0 STOREI DROP LOADC AUDIO_BASE LOADC 1 ; enable channel STOREI DROP RET STOP_AUDIO: LOADC AUDIO_BASE LOADC 0 STOREI DROP RET ; args: pointer to pascal string, sample rate .EQU PS_PTR 0 .EQU PS_COUNT 4 .EQU PS_FS 12 PLAYSAMPLE: FPADJ -PS_FS LOADCP START_PCMAUDIO CALL DUP LOADI ; get string size from header SHR ; divide by 4 to get word count SHR STORE PS_COUNT INC 8 ; skip rest of header STORE PS_PTR ; store sample data pointer PS_L0: LOAD PS_PTR ; load pointer INC.S1.X2Y 4 ; increment and keep old value STORE PS_PTR ; store incremented value LOADI ; load 32 bit word DUP BROT ; get upper 16 bit word BROT LOADCP $FFFF AND LOADCP PLAY_1SAMPLE CALL LOADCP $FFFF ; get lower 16 bit word AND LOADCP PLAY_1SAMPLE CALL LOAD PS_COUNT ; load word count DEC 1 ; decrement DUP STORE PS_COUNT CBRANCH.NZ PS_L0 ; loop if not zero LOADCP STOP_AUDIO CALL FPADJ PS_FS RET ; play one sample, waiting ; for the clock divider, which ; is visible via the phase flag ; args: 16-bit unsigned sample PLAY_1SAMPLE: PLAY1_L0: LOADC AUDIO_BASE LOADI LOADC 8 ; get fifo_full flag AND CBRANCH.NZ PLAY1_L0 ; loop if fifo is full LOADC AUDIO_BASE+2 ; store amplitude value SWAP STOREI DROP RET ; set sample queue count and pointer from string header ; args: pointer to string/SndBufPtr _STR2SMPLQPTR: LOADCP SMPLQ_COUNT OVER LOADI ; get string size from header SHR ; divide by 4 to get word count SHR STOREI DROP LOADCP SMPLQ_PTR SWAP INC 8 ; skip rest of header STOREI ; store sample data pointer DROP RET ; start interrupt-driven sample playback ; args: pointer to pascal string, loop flag, sample rate SAMPLEQSTART: LOADCP START_PCMAUDIO ; sample rate is on ToS as arg to subroutine CALL SWAP ; swap loop flag and buf ptr LOADCP _STR2SMPLQPTR CALL ; loop flag is now on ToS CBRANCH.Z SQ_S_1 ; if nonzero, set loop ptr LOADCP SMPLQ_PTR LOADI DEC 8 ; subtract offset for string header again BRANCH SQ_S_0 SQ_S_1: LOADC 0 SQ_S_0: LOADCP SMPLQ_NEXT SWAP STOREI DROP LOADC AUDIO_BASE LOADC 17 ; enable channel, enable interrupt STOREI DROP LOADCP SMPLQ_ISR ; set interrupt handler STOREREG IV LOADC IRQC_REG ; enable irq LOADC IRQC_EN STOREI DROP RET SAMPLEQSTOP: LOADCP SMPLQ_PTR LOADC 0 STOREI DROP LOADCP STOP_AUDIO CALL LOADC IRQC_REG ; disable irq LOADC 0 STOREI DROP RET SAMPLEQSIZE: LOADCP SMPLQ_COUNT LOADI RET SMPLQ_PTR: .WORD 0 SMPLQ_COUNT: .WORD 0 SMPLQ_NEXT: .WORD 0 SMPLQ_ISR: LOADC IRQC_REG LOADI LOADC 4 ; check for audio interrupt AND CBRANCH.Z SMPLQ_I_XT ; if flag not set, exit SMPLQ_I_L: LOADCP SMPLQ_PTR LOADI ; load word pointer DUP CBRANCH.NZ SMPLQ_I_B ; check for null pointer DROP BRANCH SMPLQ_I_XT ; if null, end interrupt routine SMPLQ_I_B: LOADI ; load next word which contains two samples DUP BROT ; get high half-word BROT LOADCP $FFFF AND LOADC AUDIO_BASE+2 SWAP STOREI ; write sample, keep addr SWAP ; addr to NoS, lower halfword on ToS LOADCP $FFFF AND STOREI ; write sample DROP ; decrement word count LOADCP SMPLQ_COUNT LOADI.S1.X2Y ; load counter, keep addr DEC 1 DUP CBRANCH.Z SMPLQ_I_END ; end if zero STOREI ; store new counter value DROP ; increment pointer LOADCP SMPLQ_PTR LOADI.S1.X2Y INC 4 STOREI DROP ; put up to 16 samples into the sample queue LOADCP SMPLQ_COUNT LOADI ; load word counter again LOADC 7 ; check if count modulo 7 = 0 AND CBRANCH.NZ SMPLQ_I_L ; if not, next two samples ; check if fifo is full ; does not work reliably when running in DRAM, ; maybe because at least one sample has already played ; since start of ISR? ; LOADC AUDIO_BASE ; LOADI ; LOADC 8 ; fifo_full ; AND ; CBRANCH.Z SMPLQ_I_L ; next sample if not full BRANCH SMPLQ_I_XT ; end of sample buffer, check for next SMPLQ_I_END: DROP DROP LOADCP SMPLQ_NEXT ; skip to end LOADI ; if NEXT ptr is zero DUP CBRANCH.Z SMPLQ_I_END1 LOADCP _STR2SMPLQPTR CALL BRANCH SMPLQ_I_XT ; end playback, set ptr and counter to zero SMPLQ_I_END1: DROP LOADCP SMPLQ_PTR LOADC 0 STOREI DROP LOADCP SMPLQ_COUNT LOADC 0 STOREI DROP ; set amplitude out to zero (biased) LOADC AUDIO_BASE+2 LOADCP 32768 STOREI DROP ; exit without enabling interrupts for this channel BRANCH SMPLQ_I_XT2 SMPLQ_I_XT: LOADC AUDIO_BASE LOADC 17 ; re-enable channel interrupt STOREI DROP SMPLQ_I_XT2: LOADC IRQC_REG ; re-enable interrupts LOADC IRQC_EN STOREI DROP LOADREG IR ; jump via interrupt return register JUMP