From 00bc99cc7bab3e3f5621b48a7974aad7666923c1 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 3 Mar 2023 00:47:12 +0100 Subject: [PATCH] added cx16/stream-wav example, refactor pcmaudio code --- compiler/res/prog8lib/conv.p8 | 2 +- compiler/res/prog8lib/cx16/syslib.p8 | 4 +- docs/source/todo.rst | 2 + examples/cx16/pcmaudio/adpcm.p8 | 223 +----------------- examples/cx16/pcmaudio/pcmplay1.asm | 2 +- examples/cx16/pcmaudio/pcmplay2.asm | 2 +- examples/cx16/pcmaudio/play-adpcm.p8 | 170 +++++++++++++ .../{adpcm-mono.wav => small-adpcm-mono.wav} | Bin .../{pcm-mono.bin => small-pcm-mono.bin} | Bin examples/cx16/pcmaudio/stream-wav.p8 | 221 +++++++++++++++++ examples/cx16/pcmaudio/wavfile.p8 | 60 +++++ 11 files changed, 469 insertions(+), 217 deletions(-) create mode 100644 examples/cx16/pcmaudio/play-adpcm.p8 rename examples/cx16/pcmaudio/{adpcm-mono.wav => small-adpcm-mono.wav} (100%) rename examples/cx16/pcmaudio/{pcm-mono.bin => small-pcm-mono.bin} (100%) create mode 100644 examples/cx16/pcmaudio/stream-wav.p8 create mode 100644 examples/cx16/pcmaudio/wavfile.p8 diff --git a/compiler/res/prog8lib/conv.p8 b/compiler/res/prog8lib/conv.p8 index 2e050a2e5..346153422 100644 --- a/compiler/res/prog8lib/conv.p8 +++ b/compiler/res/prog8lib/conv.p8 @@ -521,7 +521,7 @@ asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X { ;Convert 16 bit Hex to Decimal (0-65535) Rev 2 ;By Omegamatrix Further optimizations by tepples -; routine from https://forums.nesdev.org/viewtopic.php?f=2&t=11341&start=15 +; routine from https://forums.nesdev.org/viewtopic.php?p=130363&sid=1944ba8bac4d6afa9c02e3cc42304e6b#p130363 ;HexToDec99 ; start in A diff --git a/compiler/res/prog8lib/cx16/syslib.p8 b/compiler/res/prog8lib/cx16/syslib.p8 index 706221b7e..832d15831 100644 --- a/compiler/res/prog8lib/cx16/syslib.p8 +++ b/compiler/res/prog8lib/cx16/syslib.p8 @@ -643,7 +643,7 @@ asmsub init_system() { sta P8ZP_SCRATCH_REG lda #$80 sta VERA_CTRL ; reset vera - stz $01 ; select rom bank 0 (enable kernal) + stz $01 ; rom bank 0 (kernal) jsr c64.IOINIT jsr c64.RESTOR jsr c64.CINT @@ -688,7 +688,7 @@ asmsub cleanup_at_exit() { lda #1 sta $00 ; ram bank 1 lda #4 - sta $01 ; rom bank 4 (kernal) + sta $01 ; rom bank 4 (basic) stz $2d ; hack to reset machine code monitor bank to 0 rts }} diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 8e0499d74..3f758942b 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,6 +3,8 @@ TODO For next minor release ^^^^^^^^^^^^^^^^^^^^^^ +- can we optimize expressions as function call args? for instance vpoke(1, addr+1, 0) is inefficient + ... diff --git a/examples/cx16/pcmaudio/adpcm.p8 b/examples/cx16/pcmaudio/adpcm.p8 index 94d97ddb1..a80631b3b 100644 --- a/examples/cx16/pcmaudio/adpcm.p8 +++ b/examples/cx16/pcmaudio/adpcm.p8 @@ -1,155 +1,19 @@ -%import textio -%import floats -%option no_sysinit -%zeropage basicsafe - -; -; IMA ADPCM decoding and playback example. -; https://wiki.multimedia.cx/index.php/IMA_ADPCM -; https://wiki.multimedia.cx/index.php/Microsoft_IMA_ADPCM -; -; IMA ADPCM encodes two 16-bit PCM audio samples in 1 byte (1 word per nibble) -; thus compressing the audio data by a factor of 4. -; The encoding precision is about 13 bits per sample so it's a lossy compression scheme. -; -; NOTE: this program requires 16 bits MONO audio, and 256 byte encoded block size! -; HOW TO CREATE SUCH IMA-ADPCM ENCODED AUDIO? Use sox or ffmpeg: -; $ sox --guard source.mp3 -r 8000 -c 1 -e ima-adpcm out.wav trim 01:27.50 00:09 -; $ ffmpeg -i source.mp3 -ss 00:01:27.50 -to 00:01:36.50 -ar 8000 -ac 1 -c:a adpcm_ima_wav -block_size 256 -map_metadata -1 -bitexact out.wav -; Or use a tool such as https://github.com/dbry/adpcm-xq . -; - -main { - - ubyte adpcm_blocks_left - uword @requirezp nibblesptr - - sub start() { - wavfile.parse() - - txt.print_ub(wavfile.num_adpcm_blocks) - txt.print(" blocks = ") - txt.print_uw(wavfile.adpcm_size) - txt.print(" adpcm bytes\nsamplerate = ") - txt.print_uw(wavfile.sample_rate) - txt.print(" vera rate = ") - txt.print_uw(wavfile.vera_rate_hz) - txt.print("\n(b)enchmark or (p)layback? ") - - when c64.CHRIN() { - 'b' -> benchmark() - 'p' -> playback() - } - } - - sub benchmark() { - nibblesptr = wavfile.adpcm_data_ptr - - txt.print("\ndecoding all blocks...\n") - c64.SETTIM(0,0,0) - repeat wavfile.num_adpcm_blocks { - adpcm.init(peekw(nibblesptr), @(nibblesptr+2)) - nibblesptr += 4 - repeat 252 { - ubyte @zp nibble = @(nibblesptr) - adpcm.decode_nibble(nibble & 15) ; first word - adpcm.decode_nibble(nibble>>4) ; second word - nibblesptr++ - } - } - const float REFRESH_RATE = 25.0e6/(525.0*800) ; Vera VGA refresh rate is not precisely 60 hz! - float duration_secs = (c64.RDTIM16() as float) / REFRESH_RATE - floats.print_f(duration_secs) - txt.print(" seconds (approx)\n") - const float PCM_WORDS_PER_BLOCK = 1 + 252*2 - float words_per_second = PCM_WORDS_PER_BLOCK * (wavfile.num_adpcm_blocks as float) / duration_secs - txt.print_uw(words_per_second as uword) - txt.print(" decoded pcm words/sec\n") - float src_per_second = wavfile.adpcm_size as float / duration_secs - txt.print_uw(src_per_second as uword) - txt.print(" adpcm data bytes/sec\n") - } - - sub playback() { - nibblesptr = wavfile.adpcm_data_ptr - adpcm_blocks_left = wavfile.num_adpcm_blocks - - cx16.VERA_AUDIO_CTRL = %10101111 ; mono 16 bit - cx16.VERA_AUDIO_RATE = 0 ; halt playback - repeat 1024 { - cx16.VERA_AUDIO_DATA = 0 - } - - sys.set_irqd() - cx16.CINV = &irq_handler - cx16.VERA_IEN = %00001000 ; enable AFLOW - sys.clear_irqd() - - cx16.VERA_AUDIO_RATE = wavfile.vera_rate ; start playback - - txt.print("\naudio via irq\n") - - repeat { - ; audio will play via the IRQ. - } - - ; not reached: -; cx16.VERA_AUDIO_CTRL = %00100000 -; cx16.VERA_AUDIO_RATE = 0 -; txt.print("audio off.\n") - } - - sub irq_handler() { - if cx16.VERA_ISR & %00001000 { - ; AFLOW irq. - ;; cx16.vpoke(1,$fa0c, $a0) ; paint a screen color - - ; refill the fifo buffer with one decoded adpcm block (1010 bytes of pcm data) - adpcm.init(peekw(nibblesptr), @(nibblesptr+2)) - cx16.VERA_AUDIO_DATA = lsb(adpcm.predict) - cx16.VERA_AUDIO_DATA = msb(adpcm.predict) - nibblesptr += 4 - repeat 252 { - ubyte @zp nibble = @(nibblesptr) - adpcm.decode_nibble(nibble & 15) ; first word - cx16.VERA_AUDIO_DATA = lsb(adpcm.predict) - cx16.VERA_AUDIO_DATA = msb(adpcm.predict) - adpcm.decode_nibble(nibble>>4) ; second word - cx16.VERA_AUDIO_DATA = lsb(adpcm.predict) - cx16.VERA_AUDIO_DATA = msb(adpcm.predict) - nibblesptr++ - } - - adpcm_blocks_left-- - if adpcm_blocks_left==0 { - ; restart adpcm data from the beginning - nibblesptr = wavfile.adpcm_data_ptr - adpcm_blocks_left = wavfile.num_adpcm_blocks - txt.print("end of data, restarting.\n") - } - - } else { - ; TODO not AFLOW, handle other IRQ - } - - ;; cx16.vpoke(1,$fa0c, 0) ; back to other screen color - - %asm {{ - ply - plx - pla - rti - }} - } - -} - adpcm { ; IMA ADPCM decoder. ; https://wiki.multimedia.cx/index.php/IMA_ADPCM ; https://wiki.multimedia.cx/index.php/Microsoft_IMA_ADPCM + ; IMA ADPCM encodes two 16-bit PCM audio samples in 1 byte (1 word per nibble) + ; thus compressing the audio data by a factor of 4. + ; The encoding precision is about 13 bits per sample so it's a lossy compression scheme. + ; + ; HOW TO CREATE IMA-ADPCM ENCODED AUDIO? Use sox or ffmpeg: + ; $ sox --guard source.mp3 -r 8000 -c 1 -e ima-adpcm out.wav trim 01:27.50 00:09 + ; $ ffmpeg -i source.mp3 -ss 00:01:27.50 -to 00:01:36.50 -ar 8000 -ac 1 -c:a adpcm_ima_wav -block_size 256 -map_metadata -1 -bitexact out.wav + ; Or use a tool such as https://github.com/dbry/adpcm-xq (make sure to set the correct block size) + + ubyte[] t_index = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8] uword[] t_step = [ 7, 8, 9, 10, 11, 12, 13, 14, @@ -166,7 +30,7 @@ adpcm { 32767] uword @zp predict - ubyte @zp index + ubyte @requirezp index uword @zp pstep sub init(uword startPredict, ubyte startIndex) { @@ -199,68 +63,3 @@ adpcm { pstep = t_step[index] } } - - -wavfile { - - const ubyte WAVE_FORMAT_PCM = $1 - const ubyte WAVE_FORMAT_ADPCM = $2 - const ubyte WAVE_FORMAT_IEEE_FLOAT = $3 - const ubyte WAVE_FORMAT_ALAW = $6 - const ubyte WAVE_FORMAT_MULAW = $7 - const ubyte WAVE_FORMAT_DVI_ADPCM = $11 - - uword sample_rate - uword vera_rate_hz - ubyte vera_rate - uword adpcm_size - uword adpcm_data_ptr - ubyte num_adpcm_blocks - - sub parse() { - ; "RIFF" , filesize (int32) , "WAVE", "fmt ", fmtsize (int32) - ; we assume file sizes are <= 64Kb so don't have to worry about the upper 16 bits - uword @zp header = &wavdata.wav_data - if header[0]!=iso:'R' or header[1]!=iso:'I' or header[2]!=iso:'F' or header[3]!=iso:'F' - or header[8]!=iso:'W' or header[9]!=iso:'A' or header[10]!=iso:'V' or header[11]!=iso:'E' - or header[12]!=iso:'f' or header[13]!=iso:'m' or header[14]!=iso:'t' or header[15]!=iso:' ' { - txt.print("not a valid wav file\n") - sys.exit(1) - } - ; uword filesize = peekw(header+4) - uword chunksize = peekw(header+16) - uword wavefmt = peekw(header+20) - uword nchannels = peekw(header+22) - sample_rate = peekw(header+24) ; we assume sample rate <= 65535 so we can ignore the upper word - uword block_align = peekw(header+32) - - if block_align!=256 or nchannels!=1 or wavefmt!=WAVE_FORMAT_DVI_ADPCM { - txt.print("invalid wav specs\n") - sys.exit(1) - } - - ; skip chunks until we reach the 'data' chunk - header += chunksize + 20 - repeat { - chunksize = peekw(header+4) ; assume chunk size never exceeds 64kb so ignore upper word - if header[0]==iso:'d' and header[1]==iso:'a' and header[2]==iso:'t' and header[3]==iso:'a' - break - header += 8 + chunksize - } - adpcm_data_ptr = header + 8 - adpcm_size = chunksize - num_adpcm_blocks = (chunksize / 256) as ubyte ; NOTE: THE ADPCM DATA NEEDS TO BE ENCODED IN 256-byte BLOCKS ! - - const float vera_freq_factor = 25e6 / 65536.0 - vera_rate = (sample_rate as float / vera_freq_factor) + 1.0 as ubyte - vera_rate_hz = (vera_rate as float) * vera_freq_factor as uword - } -} - -wavdata { - %option align_page -wav_data: - %asmbinary "adpcm-mono.wav" -wav_data_end: - -} diff --git a/examples/cx16/pcmaudio/pcmplay1.asm b/examples/cx16/pcmaudio/pcmplay1.asm index 0053ff611..2d6a13743 100644 --- a/examples/cx16/pcmaudio/pcmplay1.asm +++ b/examples/cx16/pcmaudio/pcmplay1.asm @@ -145,5 +145,5 @@ _done: rts .align $0100 pcm_data: - .binary "pcm-mono.bin" + .binary "small-pcm-mono.bin" pcm_data_end: diff --git a/examples/cx16/pcmaudio/pcmplay2.asm b/examples/cx16/pcmaudio/pcmplay2.asm index a1a448b2e..e47eea405 100644 --- a/examples/cx16/pcmaudio/pcmplay2.asm +++ b/examples/cx16/pcmaudio/pcmplay2.asm @@ -163,5 +163,5 @@ _done: rts .align $0100 pcm_data: - .binary "pcm-mono.bin" + .binary "small-pcm-mono.bin" pcm_data_end: diff --git a/examples/cx16/pcmaudio/play-adpcm.p8 b/examples/cx16/pcmaudio/play-adpcm.p8 new file mode 100644 index 000000000..575b1ffa0 --- /dev/null +++ b/examples/cx16/pcmaudio/play-adpcm.p8 @@ -0,0 +1,170 @@ +%import textio +%import floats +%import wavfile +%import adpcm +%option no_sysinit +%zeropage basicsafe + +; +; Simple IMA ADPCM playback example. (factor 4 lossy compressed pcm audio) +; +; NOTE: this program requires 16 bits MONO audio, and 256 byte encoded block size! +; HOW TO CREATE SUCH IMA-ADPCM ENCODED AUDIO? Use sox or ffmpeg: +; $ sox --guard source.mp3 -r 8000 -c 1 -e ima-adpcm out.wav trim 01:27.50 00:09 +; $ ffmpeg -i source.mp3 -ss 00:01:27.50 -to 00:01:36.50 -ar 8000 -ac 1 -c:a adpcm_ima_wav -block_size 256 -map_metadata -1 -bitexact out.wav +; Or use a tool such as https://github.com/dbry/adpcm-xq (make sure to set correct block size) +; + +main { + + ubyte adpcm_blocks_left + uword @requirezp nibblesptr + uword vera_rate_hz + ubyte vera_rate + ubyte num_adpcm_blocks + uword adpcm_size + + sub start() { + if not wavfile.parse_header(&wavdata.wav_data) { + txt.print("invalid wav\n") + sys.exit(1) + } + + calculate_vera_rate() + calculate_adpcm_blocks() + + txt.print_ub(num_adpcm_blocks) + txt.print(" blocks = ") + txt.print_uw(adpcm_size) + txt.print(" adpcm bytes\nsamplerate = ") + txt.print_uw(wavfile.sample_rate) + txt.print(" vera rate = ") + txt.print_uw(vera_rate_hz) + txt.print("\n(b)enchmark or (p)layback? ") + + when c64.CHRIN() { + 'b' -> benchmark() + 'p' -> playback() + } + } + + sub calculate_vera_rate() { + const float vera_freq_factor = 25e6 / 65536.0 + vera_rate = (wavfile.sample_rate as float / vera_freq_factor) + 1.0 as ubyte + vera_rate_hz = (vera_rate as float) * vera_freq_factor as uword + } + + sub calculate_adpcm_blocks() { + adpcm_size = wavfile.data_size_lo ; we assume the data is <64Kb so only low word is enough + num_adpcm_blocks = (adpcm_size / 256) as ubyte ; THE ADPCM DATA NEEDS TO BE ENCODED IN 256-byte BLOCKS ! + } + + sub benchmark() { + nibblesptr = &wavdata.wav_data + wavfile.data_offset + + txt.print("\ndecoding all blocks...\n") + c64.SETTIM(0,0,0) + repeat num_adpcm_blocks { + adpcm.init(peekw(nibblesptr), @(nibblesptr+2)) + nibblesptr += 4 + repeat 252 { + ubyte @zp nibble = @(nibblesptr) + adpcm.decode_nibble(nibble & 15) ; first word + adpcm.decode_nibble(nibble>>4) ; second word + nibblesptr++ + } + } + const float REFRESH_RATE = 25.0e6/(525.0*800) ; Vera VGA refresh rate is not precisely 60 hz! + float duration_secs = (c64.RDTIM16() as float) / REFRESH_RATE + floats.print_f(duration_secs) + txt.print(" seconds (approx)\n") + const float PCM_WORDS_PER_BLOCK = 1 + 252*2 + float words_per_second = PCM_WORDS_PER_BLOCK * (num_adpcm_blocks as float) / duration_secs + txt.print_uw(words_per_second as uword) + txt.print(" decoded pcm words/sec\n") + float src_per_second = adpcm_size as float / duration_secs + txt.print_uw(src_per_second as uword) + txt.print(" adpcm data bytes/sec\n") + } + + sub playback() { + nibblesptr = &wavdata.wav_data + wavfile.data_offset + adpcm_blocks_left = num_adpcm_blocks + + cx16.VERA_AUDIO_CTRL = %10101111 ; mono 16 bit + cx16.VERA_AUDIO_RATE = 0 ; halt playback + repeat 1024 { + cx16.VERA_AUDIO_DATA = 0 + } + + sys.set_irqd() + cx16.CINV = &irq_handler + cx16.VERA_IEN = %00001000 ; enable AFLOW + sys.clear_irqd() + + cx16.VERA_AUDIO_RATE = vera_rate ; start playback + + txt.print("\naudio via irq\n") + + repeat { + ; audio will play via the IRQ. + } + + ; not reached: +; cx16.VERA_AUDIO_CTRL = %00100000 +; cx16.VERA_AUDIO_RATE = 0 +; txt.print("audio off.\n") + } + + sub irq_handler() { + if cx16.VERA_ISR & %00001000 { + ; AFLOW irq. + ;; cx16.vpoke(1,$fa0c, $a0) ; paint a screen color + + ; refill the fifo buffer with one decoded adpcm block (1010 bytes of pcm data) + adpcm.init(peekw(nibblesptr), @(nibblesptr+2)) + cx16.VERA_AUDIO_DATA = lsb(adpcm.predict) + cx16.VERA_AUDIO_DATA = msb(adpcm.predict) + nibblesptr += 4 + repeat 252 { + ubyte @zp nibble = @(nibblesptr) + adpcm.decode_nibble(nibble & 15) ; first word + cx16.VERA_AUDIO_DATA = lsb(adpcm.predict) + cx16.VERA_AUDIO_DATA = msb(adpcm.predict) + adpcm.decode_nibble(nibble>>4) ; second word + cx16.VERA_AUDIO_DATA = lsb(adpcm.predict) + cx16.VERA_AUDIO_DATA = msb(adpcm.predict) + nibblesptr++ + } + + adpcm_blocks_left-- + if adpcm_blocks_left==0 { + ; restart adpcm data from the beginning + nibblesptr = &wavdata.wav_data + wavfile.data_offset + adpcm_blocks_left = num_adpcm_blocks + txt.print("end of data, restarting.\n") + } + + } else { + ; TODO not AFLOW, handle other IRQ + } + + ;; cx16.vpoke(1,$fa0c, 0) ; back to other screen color + + %asm {{ + ply + plx + pla + rti + }} + } + +} + +wavdata { + %option align_page +wav_data: + %asmbinary "small-adpcm-mono.wav" +wav_data_end: + +} diff --git a/examples/cx16/pcmaudio/adpcm-mono.wav b/examples/cx16/pcmaudio/small-adpcm-mono.wav similarity index 100% rename from examples/cx16/pcmaudio/adpcm-mono.wav rename to examples/cx16/pcmaudio/small-adpcm-mono.wav diff --git a/examples/cx16/pcmaudio/pcm-mono.bin b/examples/cx16/pcmaudio/small-pcm-mono.bin similarity index 100% rename from examples/cx16/pcmaudio/pcm-mono.bin rename to examples/cx16/pcmaudio/small-pcm-mono.bin diff --git a/examples/cx16/pcmaudio/stream-wav.p8 b/examples/cx16/pcmaudio/stream-wav.p8 new file mode 100644 index 000000000..4a741804e --- /dev/null +++ b/examples/cx16/pcmaudio/stream-wav.p8 @@ -0,0 +1,221 @@ +%import textio +%import diskio +%import cx16diskio +%import floats +%import adpcm +%import wavfile +%option no_sysinit + +; +; This program can stream a regular .wav file from the sdcard. +; It can be uncompressed or IMA-adpcm compressed (factor 4 lossy compression). +; See the "adpcm" module source for tips how to create those files. +; +; Sample width must be 16 bit because 8 bit wav files use unsigned values +; and that is not compatible with the Vera (requires signed values). +; You could ofcourse create a raw file with the correct 8 bit values +; and stream that, but this example deals with wav files only for now. +; +; The playback is done via AFLOW irq handler that fills the audio fifo buffer +; with around 1 Kb of new audio data. (copies raw pcm data or decodes adpcm block) +; In the meantime the main program loop reads new data blocks from the wav file +; as it is being played. +; +; NOTE: stripping the wav header and just having the raw pcm data in the file +; is slightly more efficient because the data blocks are then sector-aligned on the disk +; + +main { + uword vera_rate_hz + ubyte vera_rate + + sub start() { + uword buffer = memory("buffer", 1024, 256) + str MUSIC_FILENAME = "music.wav" + + bool wav_ok = false + txt.print("\nchecking ") + txt.print(MUSIC_FILENAME) + txt.nl() + if diskio.f_open(8, MUSIC_FILENAME) { + void cx16diskio.f_read(buffer, 128) + wav_ok = wavfile.parse_header(buffer) + diskio.f_close() + } + if not wav_ok { + error("no good wav file!") + } + + calculate_vera_rate() + txt.print("wav format: ") + txt.print_ub(wavfile.wavefmt) + txt.print("\nchannels: ") + txt.print_ub(wavfile.nchannels) + txt.print("\nsample rate: ") + txt.print_uw(wavfile.sample_rate) + txt.print("\nbits per sample: ") + txt.print_uw(wavfile.bits_per_sample) + txt.print("\ndata size: ") + txt.print_uwhex(wavfile.data_size_hi, true) + txt.print_uwhex(wavfile.data_size_lo, false) + txt.print("\nvera rate: ") + txt.print_ub(vera_rate) + txt.print(" = ") + txt.print_uw(vera_rate_hz) + txt.print(" hz\n") + if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM { + txt.print("adpcm block size: ") + txt.print_uw(wavfile.block_align) + txt.nl() + } + + if wavfile.nchannels>2 or + (wavfile.wavefmt!=wavfile.WAVE_FORMAT_DVI_ADPCM and wavfile.wavefmt!=wavfile.WAVE_FORMAT_PCM) or + wavfile.sample_rate > 44100 or + (wavfile.bits_per_sample!=16) { + error("unsupported format!") + } + + if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM { + if(wavfile.block_align!=256 or wavfile.nchannels!=1) { + error("unsupported format!") + } + } + + txt.print("\ngood file! playback starts!\n") + cx16.rombank(0) ; activate kernal bank for faster calls + cx16.VERA_AUDIO_RATE = 0 ; halt playback + cx16.VERA_AUDIO_CTRL = %10101111 ; mono 16 bit + if wavfile.nchannels==2 + cx16.VERA_AUDIO_CTRL = %10111111 ; stereo 16 bit +; if(wavfile.bits_per_sample==8) +; cx16.VERA_AUDIO_CTRL &= %11011111 ; set to 8 bit instead + repeat 1024 + cx16.VERA_AUDIO_DATA = 0 ; fill buffer with short silence + + sys.set_irqd() + cx16.CINV = &interrupt.handler + cx16.VERA_IEN = %00001000 ; enable AFLOW only for now + sys.clear_irqd() + + if diskio.f_open(8, MUSIC_FILENAME) { + uword block_size = 1024 + if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM + block_size = wavfile.block_align + void cx16diskio.f_read(buffer, wavfile.data_offset) ; skip to actual sample data start + void cx16diskio.f_read(buffer, block_size) ; preload buffer + cx16.VERA_AUDIO_RATE = vera_rate ; start playback + repeat { + interrupt.wait_and_clear_aflow_semaphore() + ;; cx16.vpoke(1,$fa00, $a0) ; paint a screen color + uword size = cx16diskio.f_read(buffer, block_size) + ;; cx16.vpoke(1,$fa00, $00) ; paint a screen color + if size>4) ; second word + cx16.VERA_AUDIO_DATA = lsb(adpcm.predict) + cx16.VERA_AUDIO_DATA = msb(adpcm.predict) + nibblesptr++ + } + } +} diff --git a/examples/cx16/pcmaudio/wavfile.p8 b/examples/cx16/pcmaudio/wavfile.p8 new file mode 100644 index 000000000..a96bef6c7 --- /dev/null +++ b/examples/cx16/pcmaudio/wavfile.p8 @@ -0,0 +1,60 @@ +; +; module to parse the header data of a .wav file +; +; note: the sample rate in hz can be converted to a vera rate via: +; const float vera_freq_factor = 25e6 / 65536.0 +; vera_rate = (wavfile.sample_rate as float / vera_freq_factor) + 1.0 as ubyte +; vera_rate_hz = (vera_rate as float) * vera_freq_factor as uword +; + +wavfile { + + const ubyte WAVE_FORMAT_PCM = $1 + const ubyte WAVE_FORMAT_ADPCM = $2 + const ubyte WAVE_FORMAT_IEEE_FLOAT = $3 + const ubyte WAVE_FORMAT_ALAW = $6 + const ubyte WAVE_FORMAT_MULAW = $7 + const ubyte WAVE_FORMAT_DVI_ADPCM = $11 + + uword sample_rate + ubyte bits_per_sample + uword data_offset + ubyte wavefmt + ubyte nchannels + uword block_align + uword data_size_hi + uword data_size_lo + + sub parse_header(uword wav_data) -> bool { + ; "RIFF" , filesize (int32) , "WAVE", "fmt ", fmtsize (int32) + uword @zp header = wav_data + if header[0]!=iso:'R' or header[1]!=iso:'I' or header[2]!=iso:'F' or header[3]!=iso:'F' + or header[8]!=iso:'W' or header[9]!=iso:'A' or header[10]!=iso:'V' or header[11]!=iso:'E' + or header[12]!=iso:'f' or header[13]!=iso:'m' or header[14]!=iso:'t' or header[15]!=iso:' ' { + return false + } + ; uword filesize = peekw(header+4) + uword chunksize = peekw(header+16) + wavefmt = peek(header+20) + nchannels = peek(header+22) + sample_rate = peekw(header+24) ; we assume sample rate <= 65535 so we can ignore the upper word + block_align = peekw(header+32) + bits_per_sample = peek(header+34) + if wavefmt==WAVE_FORMAT_DVI_ADPCM or wavefmt==WAVE_FORMAT_ADPCM + bits_per_sample *= 4 + + ; skip chunks until we reach the 'data' chunk + header += chunksize + 20 + repeat { + chunksize = peekw(header+4) ; assume chunk size never exceeds 64kb so ignore upper word + if header[0]==iso:'d' and header[1]==iso:'a' and header[2]==iso:'t' and header[3]==iso:'a' + break + header += 8 + chunksize + } + + data_size_lo = chunksize + data_size_hi = peekw(header+6) + data_offset = header + 8 - wav_data + return true + } +}