added cx16/stream-wav example, refactor pcmaudio code

This commit is contained in:
Irmen de Jong 2023-03-03 00:47:12 +01:00
parent 540b3ae2f4
commit 00bc99cc7b
11 changed files with 469 additions and 217 deletions

View File

@ -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

View File

@ -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
}}

View File

@ -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
...

View File

@ -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:
}

View File

@ -145,5 +145,5 @@ _done: rts
.align $0100
pcm_data:
.binary "pcm-mono.bin"
.binary "small-pcm-mono.bin"
pcm_data_end:

View File

@ -163,5 +163,5 @@ _done: rts
.align $0100
pcm_data:
.binary "pcm-mono.bin"
.binary "small-pcm-mono.bin"
pcm_data_end:

View File

@ -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:
}

View File

@ -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<block_size
break
txt.chrout('.')
}
diskio.f_close()
} else {
txt.print("load error")
}
cx16.VERA_AUDIO_RATE = 0 ; halt playback
txt.print("done!\n")
repeat {
}
}
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 error(str msg) {
txt.print(msg)
repeat {
}
}
}
interrupt {
bool aflow_semaphore
asmsub wait_and_clear_aflow_semaphore() {
%asm {{
- wai
lda aflow_semaphore
bne -
inc aflow_semaphore
rts
}}
}
sub handler() {
if cx16.VERA_ISR & %00001000 {
; AFLOW irq occurred, refill buffer
aflow_semaphore = 0
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM
adpcm_block()
else
uncompressed_block()
} else if cx16.VERA_ISR & %00000001 {
cx16.VERA_ISR = %00000001
; TODO handle vsync irq
}
%asm {{
ply
plx
pla
rti
}}
}
sub uncompressed_block() {
; optimized loop to put 1024 bytes of data into the fifo as fast as possible
%asm {{
lda main.start.buffer
sta cx16.r0L
lda main.start.buffer+1
sta cx16.r0H
ldx #4
- ldy #0
- lda (cx16.r0),y
sta cx16.VERA_AUDIO_DATA
iny
bne -
inc cx16.r0H
dex
bne --
rts
}}
; original prog8 code:
; uword @requirezp ptr = main.start.buffer
; repeat 1024 {
; cx16.VERA_AUDIO_DATA = @(ptr)
; ptr++
; }
}
sub adpcm_block() {
; refill the fifo buffer with one decoded adpcm block (1010 bytes of pcm data)
uword @requirezp nibblesptr = main.start.buffer
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++
}
}
}

View File

@ -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
}
}