.EQU AUDIO_BASE $A00 .EQU IRQC_REG $980 .EQU IRQC_EN $80 ; args: sample rate START_PCMAUDIO: ; calculate clock divider LOADCP 77000000 SWAP LOADCP _DIV CALL LOADC AUDIO_BASE + 1 SWAP ; put clock divider on ToS ; LOADCP 4812 ; clock divider for 16KHz sample rate ; LOADCP 2406 ; clock divider for 32KHz sample rate STOREI 1 LOADCP 32768 ; set amplitude to biased 0 STOREI DROP LOADC AUDIO_BASE LOADC 17 ; enable channel, enable interrupt 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 ; start interrupt-driven sample playback ; args: pointer to pascal string, sample rate SAMPLEQSTART: LOADCP START_PCMAUDIO CALL 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 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_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 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 ; check if fifo is full LOADC AUDIO_BASE LOADI LOADC 8 ; fifo_full AND CBRANCH.Z SMPLQ_I_L ; next sample if not full LOADC AUDIO_BASE LOADC 17 ; re-enable channel interrupt STOREI DROP BRANCH SMPLQ_I_XT ; end playback, set ptr and counter to zero SMPLQ_I_END: DROP 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 SMPLQ_I_XT: LOADC IRQC_REG ; re-enable interrupts LOADC IRQC_EN STOREI DROP LOADREG IR ; jump via interrupt return register JUMP