diff --git a/compiler/res/prog8lib/cx16/syslib.p8 b/compiler/res/prog8lib/cx16/syslib.p8 index 43a64e8fa..5cd27d939 100644 --- a/compiler/res/prog8lib/cx16/syslib.p8 +++ b/compiler/res/prog8lib/cx16/syslib.p8 @@ -1195,6 +1195,7 @@ asmsub set_sprcol_irq_handler(uword address @AY) clobbers(A) { asmsub set_aflow_irq_handler(uword address @AY) clobbers(A) { ; Sets the AFLOW irq handler to use with enable_irq_handlers(). Also enables AFLOW irqs. ; NOTE: unless a proper irq handler is already running, you should enclose this call in set_irqd() / clear_irqd() to avoid system crashes. + ; NOTE: the handler itself must fill the audio fifo buffer to at least 25% full again (1 KB) or the aflow irq will keep triggering! %asm {{ php sei diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 7cc6d3d38..90f6bdc92 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -108,6 +108,7 @@ class TestCompilerOnExamplesCx16: FunSpec({ "vtui/testvtui", "pcmaudio/play-adpcm", "pcmaudio/stream-wav", + "pcmaudio/stream-pcm-simple", "pcmaudio/vumeter", "sprites/dragon", "sprites/dragons", diff --git a/docs/source/targetsystem.rst b/docs/source/targetsystem.rst index 44e23677a..a30b6de9d 100644 --- a/docs/source/targetsystem.rst +++ b/docs/source/targetsystem.rst @@ -224,6 +224,8 @@ Here they are, all available in ``cx16``: ``set_aflow_irq_handler (uword address)`` Sets the audio buffer underrun interrupt handler routine. Also enables AFLOW interrupts. + Note: the handler must fill the Vera's audio fifo buffer by itself with at least 25% worth of data (1 kb) + otherwise the aflow irq keeps triggering. ``disable_irq_handlers ()`` Hand control back to the system default IRQ handler. diff --git a/examples/cx16/pcmaudio/stream-pcm-simple.p8 b/examples/cx16/pcmaudio/stream-pcm-simple.p8 new file mode 100644 index 000000000..4d4ddf2d6 --- /dev/null +++ b/examples/cx16/pcmaudio/stream-pcm-simple.p8 @@ -0,0 +1,124 @@ +; This program can stream a simple uncompressed PCM file from the sdcard. +; Currently it has been set up to play a 16 bit stereo PCM file, +; and it plays it in the given sample frequency typed in by the user. +; +; It uses a AFLOW irq handler to refill the vera's PCM fifo buffer, +; and sets a flag that signals the main program to load the next block of +; data from disk. It is problematic to call kernal I/O routines inside an irq handler, +; otherwise the aflow handler itself could have loaded the pcm data straight into +; the vera's fifo buffer. But this could lead to race conditions so we need explicit buffering. +; +; The IRQ handlers are installed using Prog8's support routines for irq handlers. +; The sample data is simply read using diskio.f_read() routine, but that uses MACPTR internally for fast loads. + + +%import diskio +%import textio +%option no_sysinit + +main { + + + sub start() { + str MUSIC_FILENAME = "?"*32 + txt.print("what sample rate (hz) do you want to play at: ") + void txt.input_chars(MUSIC_FILENAME) + music.vera_rate_hz = conv.str2uword(MUSIC_FILENAME) + if music.vera_rate_hz==0 + music.vera_rate_hz=44100 + music.calculate_vera_rate(music.vera_rate_hz) + + txt.print("\nname of raw .pcm file to play on drive 8: ") + while 0==txt.input_chars(MUSIC_FILENAME) { + ; until user types a name... + } + + if diskio.f_open(MUSIC_FILENAME) { + cx16.rombank(0) + void diskio.fastmode(1) + music.init() + interrupts.setup() + music.start() + } else { + txt.print("\nio error") + } + + repeat { + interrupts.wait() + if interrupts.aflow { + ; read the next 1024 bytes of audio data into the buffer + txt.chrout('.') + if diskio.f_read(interrupts.audio_buffer, 1024) != 1024 + break + interrupts.aflow = false + } + } + } +} + + +interrupts { + bool aflow + uword audio_buffer = memory("audiobuffer", 1024, 0) + + sub setup() { + aflow = false + sys.memset(audio_buffer, 1024, 0) + cx16.enable_irq_handlers(true) + cx16.set_aflow_irq_handler(&aflow_handler) + ; no other irqs in this example. + } + + asmsub wait() { + %asm {{ + wai + }} + } + + sub aflow_handler() -> bool { + ; copy 1024 bytes of audio data from the buffer into vera's fifo, quickly! + %asm {{ + lda p8v_audio_buffer + sta _loop+1 + sta _lp2+1 + lda p8v_audio_buffer+1 + sta _loop+2 + sta _lp2+2 + ldx #4 + ldy #0 +_loop lda $ffff,y + sta cx16.VERA_AUDIO_DATA + iny +_lp2 lda $ffff,y + sta cx16.VERA_AUDIO_DATA + iny + bne _loop + inc _loop+2 + inc _lp2+2 + dex + bne _loop + }} + aflow = true ; signal main program to read the next block of audio data: it is unsafe to to I/O in a irq handler! + return false + } +} + +music { + uword vera_rate_hz + ubyte vera_rate + + sub init() { + cx16.VERA_AUDIO_RATE = 0 ; halt playback + cx16.VERA_AUDIO_CTRL = %10111011 ; stereo 16 bit, volume 11 + } + + sub start() { + cx16.VERA_AUDIO_RATE = vera_rate ; start playback + } + + sub calculate_vera_rate(uword sample_rate) { + const uword vera_freq_factor = 25_000_000 / 65536 + vera_rate = (sample_rate / vera_freq_factor) as ubyte + 1 + vera_rate_hz = vera_rate * vera_freq_factor + } +} diff --git a/examples/cx16/pcmaudio/stream-wav.p8 b/examples/cx16/pcmaudio/stream-wav.p8 index 446cf60c8..b7edb92a5 100644 --- a/examples/cx16/pcmaudio/stream-wav.p8 +++ b/examples/cx16/pcmaudio/stream-wav.p8 @@ -128,10 +128,6 @@ main { repeat { interrupts.wait() - if interrupts.vsync { - interrupts.vsync=false - ; ...do something triggered by vsync irq... - } if interrupts.aflow { interrupts.aflow=false if not music.load_next_block(block_size) @@ -158,13 +154,12 @@ interrupts { sub set_handler() { sys.set_irqd() - cx16.CINV = &handler ; handles both AFLOW and VSYNC - cx16.VERA_IEN = %00001001 ; enable AFLOW + VSYNC + cx16.CINV = &handler ; handler for AFLOW + cx16.VERA_IEN = %00001000 ; enable AFLOW only sys.clear_irqd() } bool aflow - bool vsync asmsub wait() { %asm {{ @@ -173,6 +168,8 @@ interrupts { } sub handler() { + ; we only handle aflow in this example. + if cx16.VERA_ISR & %00001000 !=0 { ; Filling the fifo is the only way to clear the Aflow irq. ; So we do this here, otherwise the aflow irq will keep triggering. @@ -183,10 +180,6 @@ interrupts { cx16.restore_virtual_registers() aflow = true } - if cx16.VERA_ISR & %00000001 !=0 { - cx16.VERA_ISR = %00000001 - vsync = true - } %asm {{ ply @@ -233,23 +226,30 @@ music { } asmsub uncompressed_block_8() { - ; optimized loop to put 1024 bytes of data into the fifo as fast as possible - ; converting unsigned wav 8 bit samples to signed 8 bit on the fly + ; copy 1024 bytes of audio data from the buffer into vera's fifo, quickly! + ; converting unsigned wav 8 bit samples to signed 8 bit on the fly. %asm {{ lda p8v_buffer - sta cx16.r0L + sta _loop+1 + sta _lp2+1 lda p8v_buffer+1 - sta cx16.r0H + sta _loop+2 + sta _lp2+2 ldx #4 -- ldy #0 -- lda (cx16.r0),y - eor #$80 ; convert to signed 8-bit + ldy #0 +_loop lda $ffff,y + eor #$80 ; convert to signed sta cx16.VERA_AUDIO_DATA iny - bne - - inc cx16.r0H +_lp2 lda $ffff,y + eor #$80 ; convert to signed + sta cx16.VERA_AUDIO_DATA + iny + bne _loop + inc _loop+2 + inc _lp2+2 dex - bne -- + bne _loop rts }} @@ -264,22 +264,27 @@ music { } asmsub uncompressed_block_16() { - ; optimized loop to put 1024 bytes of data into the fifo as fast as possible + ; copy 1024 bytes of audio data from the buffer into vera's fifo, quickly! %asm {{ lda p8v_buffer - sta cx16.r0L + sta _loop+1 + sta _lp2+1 lda p8v_buffer+1 - sta cx16.r0H + sta _loop+2 + sta _lp2+2 ldx #4 -- ldy #0 -- lda (cx16.r0),y + ldy #0 +_loop lda $ffff,y sta cx16.VERA_AUDIO_DATA iny - bne - - inc cx16.r0H +_lp2 lda $ffff,y + sta cx16.VERA_AUDIO_DATA + iny + bne _loop + inc _loop+2 + inc _lp2+2 dex - bne -- - rts + bne _loop }} ; original prog8 code: ; uword @requirezp ptr = main.start.buffer