restructure play-adpcm example code, stream-wav can now play stereo adpcm wavs

This commit is contained in:
Irmen de Jong 2023-10-10 22:41:07 +02:00
parent 836bc9d456
commit f75fd0811e
5 changed files with 285 additions and 185 deletions

View File

@ -1,8 +1,6 @@
TODO
====
- stream-wav example: add stereo adpcm support
- [on branch: shortcircuit] investigate McCarthy evaluation again? this may also reduce code size perhaps for things like if a>4 or a<2 ....
- [on branch: ir-less-branch-opcodes] IR: reduce the number of branch instructions such as BEQ, BEQR, etc (gradually), replace with CMP(I) + status branch instruction
- IR: reduce amount of CMP/CMPI after instructions that set the status bits correctly (LOADs? INC? etc), but only after setting the status bits is verified!

View File

@ -12,6 +12,9 @@ adpcm {
; $ 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
; And/or use a tool such as https://github.com/dbry/adpcm-xq (make sure to set the correct block size, -b8)
;
; NOTE: for speed reasons this implementation doesn't guard against clipping errors.
; if the output sounds distorted, lower the volume of the source waveform to 80% and try again etc.
; IMA-ADPCM file data stream format:
@ -40,8 +43,8 @@ adpcm {
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
32767]
uword @zp predict ; decoded 16 bit pcm sample for first channel.
uword @zp predict_2 ; decoded 16 bit pcm sample for second channel.
uword @requirezp predict ; decoded 16 bit pcm sample for first channel.
uword @requirezp predict_2 ; decoded 16 bit pcm sample for second channel.
ubyte @requirezp index
ubyte @requirezp index_2
uword @zp pstep
@ -76,8 +79,17 @@ adpcm {
pstep >>= 1
cx16.r0s += pstep
if nibble & %1000
cx16.r0s = -cx16.r0s
predict += cx16.r0s as uword
predict -= cx16.r0s
else
predict += cx16.r0s
; NOTE: the original C/Python code uses a 32 bits prediction value and clips it to a 16 bit word
; but for speed reasons we only work with 16 bit words here all the time (with possible clipping error)
; if predicted > 32767:
; predicted = 32767
; elif predicted < -32767:
; predicted = - 32767
index += t_index[nibble]
if_neg ; was: if index & 128
index = 0
@ -101,8 +113,17 @@ adpcm {
pstep_2 >>= 1
cx16.r0s += pstep_2
if nibble_2 & %1000
cx16.r0s = -cx16.r0s
predict_2 += cx16.r0s as uword
predict_2 -= cx16.r0s
else
predict_2 += cx16.r0s
; NOTE: the original C/Python code uses a 32 bits prediction value and clips it to a 16 bit word
; but for speed reasons we only work with 16 bit words here all the time (with possible clipping error)
; if predicted > 32767:
; predicted = 32767
; elif predicted < -32767:
; predicted = - 32767
index_2 += t_index[nibble_2]
if_neg ; was: if index & 128
index_2 = 0

View File

@ -18,11 +18,11 @@
main {
ubyte adpcm_blocks_left
uword @requirezp nibblesptr
uword vera_rate_hz
ubyte vera_rate
ubyte num_adpcm_blocks
uword adpcm_size
uword @requirezp nibblesptr
sub start() {
if not wavfile.parse_header(&wavdata.wav_data) {
@ -45,10 +45,19 @@ main {
txt.print("\n\n(b)enchmark or (p)layback? ")
when cbm.CHRIN() {
'b' -> when wavfile.nchannels {
1-> benchmark_mono()
2-> benchmark_stereo()
}
'b' -> {
cbm.SETTIM(0,0,0)
when wavfile.nchannels {
1-> {
mono.benchmark()
decoding_report(1 + 252*2)
}
2-> {
stereo.benchmark()
decoding_report(2 + 248*4)
}
}
}
'p' -> playback()
}
}
@ -64,127 +73,6 @@ main {
num_adpcm_blocks = (adpcm_size / 256) as ubyte ; THE ADPCM DATA NEEDS TO BE ENCODED IN 256-byte BLOCKS !
}
sub benchmark_mono() {
nibblesptr = &wavdata.wav_data + wavfile.data_offset
txt.print("\ndecoding all blocks...\n")
cbm.SETTIM(0,0,0)
repeat num_adpcm_blocks {
adpcm.init(peekw(nibblesptr), @(nibblesptr+2))
nibblesptr += 4
decode_mono_nibbles()
}
decoding_report(1 + 252*2)
}
sub decode_mono_nibbles() {
; slightly unrolled
ubyte @zp nibble
repeat 252/2 {
unroll 2 {
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++
}
}
}
uword[8] left
uword[8] right
sub benchmark_stereo() {
nibblesptr = &wavdata.wav_data + wavfile.data_offset
txt.print("\n\ndecoding all blocks...\n")
cbm.SETTIM(0,0,0)
repeat num_adpcm_blocks {
adpcm.init(peekw(nibblesptr), @(nibblesptr+2))
nibblesptr += 4
adpcm.init_second(peekw(nibblesptr), @(nibblesptr+2))
nibblesptr += 4
repeat 248/8 {
decode_stereo_nibbles()
nibblesptr += 8
copy_stereo_to_fifo()
}
}
decoding_report(2 + 248*4)
}
asmsub copy_stereo_to_fifo() clobbers(A, Y) {
%asm {{
; copy to vera PSG fifo buffer
ldy #0
- lda p8_left,y
sta cx16.VERA_AUDIO_DATA
lda p8_left+1,y
sta cx16.VERA_AUDIO_DATA
lda p8_right,y
sta cx16.VERA_AUDIO_DATA
lda p8_right+1,y
sta cx16.VERA_AUDIO_DATA
iny
iny
cpy #16
bne -
}}
}
sub decode_stereo_nibbles() {
; decode 4 left channel nibbles
ubyte @zp nibble = @(nibblesptr)
adpcm.decode_nibble(nibble & 15) ; first word
left[0] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[1] = adpcm.predict
nibble = @(nibblesptr+1)
adpcm.decode_nibble(nibble & 15) ; first word
left[2] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[3] = adpcm.predict
nibble = @(nibblesptr+2)
adpcm.decode_nibble(nibble & 15) ; first word
left[4] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[5] = adpcm.predict
nibble = @(nibblesptr+3)
adpcm.decode_nibble(nibble & 15) ; first word
left[6] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[7] = adpcm.predict
; decode 4 right channel nibbles
nibble = @(nibblesptr+4)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[0] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[1] = adpcm.predict_2
nibble = @(nibblesptr+5)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[2] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[3] = adpcm.predict_2
nibble = @(nibblesptr+6)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[4] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[5] = adpcm.predict_2
nibble = @(nibblesptr+7)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[6] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[7] = adpcm.predict_2
}
sub decoding_report(float pcm_words_per_block) {
const float REFRESH_RATE = 25.0e6/(525.0*800) ; Vera VGA refresh rate is not precisely 60 hz!
float duration_secs = (cbm.RDTIM16() as float) / REFRESH_RATE
@ -212,23 +100,25 @@ main {
nibblesptr = &wavdata.wav_data + wavfile.data_offset
adpcm_blocks_left = num_adpcm_blocks
when wavfile.nchannels {
1 -> cx16.VERA_AUDIO_CTRL = %10101111 ; mono 16 bit
2 -> cx16.VERA_AUDIO_CTRL = %10111111 ; stereo 16 bit
}
sys.set_irqd()
cx16.VERA_AUDIO_RATE = 0 ; halt playback
repeat 1024 {
cx16.VERA_AUDIO_DATA = 0
}
sys.set_irqd()
when wavfile.nchannels {
1 -> cx16.CINV = &irq_handler_mono
2 -> cx16.CINV = &irq_handler_stereo
1 -> {
cx16.VERA_AUDIO_CTRL = %10101011 ; mono 16 bit, volume 11
cx16.CINV = &mono.irq_handler
}
2 -> {
cx16.VERA_AUDIO_CTRL = %10111011 ; stereo 16 bit, volume 11
cx16.CINV = &stereo.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")
@ -243,22 +133,48 @@ main {
; txt.print("audio off.\n")
}
sub irq_handler_mono() {
}
mono {
sub benchmark() {
main.nibblesptr = &wavdata.wav_data + wavfile.data_offset
txt.print("\ndecoding all blocks...\n")
repeat main.num_adpcm_blocks
decode_block()
}
sub decode_block() {
; refill the fifo buffer with one decoded adpcm block (1010 bytes of pcm data)
adpcm.init(peekw(main.nibblesptr), @(main.nibblesptr+2))
cx16.VERA_AUDIO_DATA = lsb(adpcm.predict)
cx16.VERA_AUDIO_DATA = msb(adpcm.predict)
main.nibblesptr += 4
ubyte @zp nibble
repeat 252/2 {
unroll 2 {
nibble = @(main.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)
main.nibblesptr++
}
}
}
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
decode_mono_nibbles()
adpcm_blocks_left--
if adpcm_blocks_left==0 {
decode_block()
main.adpcm_blocks_left--
if main.adpcm_blocks_left==0 {
; restart adpcm data from the beginning
nibblesptr = &wavdata.wav_data + wavfile.data_offset
adpcm_blocks_left = num_adpcm_blocks
main.nibblesptr = &wavdata.wav_data + wavfile.data_offset
main.adpcm_blocks_left = main.num_adpcm_blocks
txt.print("end of data, restarting.\n")
}
@ -276,33 +192,108 @@ main {
}}
}
sub irq_handler_stereo() {
}
stereo {
sub benchmark() {
main.nibblesptr = &wavdata.wav_data + wavfile.data_offset
txt.print("\n\ndecoding all blocks...\n")
repeat main.num_adpcm_blocks
decode_block()
}
sub decode_block() {
; refill the fifo buffer with one decoded adpcm block (1010 bytes of pcm data)
adpcm.init(peekw(main.nibblesptr), @(main.nibblesptr+2)) ; left channel
cx16.VERA_AUDIO_DATA = lsb(adpcm.predict)
cx16.VERA_AUDIO_DATA = msb(adpcm.predict)
adpcm.init_second(peekw(main.nibblesptr+4), @(main.nibblesptr+6)) ; right channel
cx16.VERA_AUDIO_DATA = lsb(adpcm.predict_2)
cx16.VERA_AUDIO_DATA = msb(adpcm.predict_2)
main.nibblesptr += 8
repeat 248/8
decode_nibbles_unrolled()
}
sub decode_nibbles_unrolled() {
; decode 4 left channel nibbles
uword[8] left
uword[8] right
ubyte @requirezp nibble = @(main.nibblesptr)
adpcm.decode_nibble(nibble & 15) ; first word
left[0] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[1] = adpcm.predict
nibble = @(main.nibblesptr+1)
adpcm.decode_nibble(nibble & 15) ; first word
left[2] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[3] = adpcm.predict
nibble = @(main.nibblesptr+2)
adpcm.decode_nibble(nibble & 15) ; first word
left[4] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[5] = adpcm.predict
nibble = @(main.nibblesptr+3)
adpcm.decode_nibble(nibble & 15) ; first word
left[6] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[7] = adpcm.predict
; decode 4 right channel nibbles
nibble = @(main.nibblesptr+4)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[0] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[1] = adpcm.predict_2
nibble = @(main.nibblesptr+5)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[2] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[3] = adpcm.predict_2
nibble = @(main.nibblesptr+6)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[4] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[5] = adpcm.predict_2
nibble = @(main.nibblesptr+7)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[6] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[7] = adpcm.predict_2
main.nibblesptr += 8
%asm {{
; copy to vera PSG fifo buffer
ldy #0
- lda p8_left,y
sta cx16.VERA_AUDIO_DATA
lda p8_left+1,y
sta cx16.VERA_AUDIO_DATA
lda p8_right,y
sta cx16.VERA_AUDIO_DATA
lda p8_right+1,y
sta cx16.VERA_AUDIO_DATA
iny
iny
cpy #16
bne -
}}
}
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)
; left channel
adpcm.init(peekw(nibblesptr), @(nibblesptr+2))
cx16.VERA_AUDIO_DATA = lsb(adpcm.predict)
cx16.VERA_AUDIO_DATA = msb(adpcm.predict)
nibblesptr += 4
; right channel
adpcm.init_second(peekw(nibblesptr), @(nibblesptr+2))
cx16.VERA_AUDIO_DATA = lsb(adpcm.predict_2)
cx16.VERA_AUDIO_DATA = msb(adpcm.predict_2)
nibblesptr += 4
repeat 31 {
decode_stereo_nibbles()
nibblesptr += 8
copy_stereo_to_fifo()
}
adpcm_blocks_left--
if adpcm_blocks_left==0 {
decode_block()
main.adpcm_blocks_left--
if main.adpcm_blocks_left==0 {
; restart adpcm data from the beginning
nibblesptr = &wavdata.wav_data + wavfile.data_offset
adpcm_blocks_left = num_adpcm_blocks
main.nibblesptr = &wavdata.wav_data + wavfile.data_offset
main.adpcm_blocks_left = main.num_adpcm_blocks
txt.print("end of data, restarting.\n")
}

View File

@ -79,17 +79,17 @@ main {
}
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM {
if(wavfile.block_align!=256 or wavfile.nchannels!=1) {
error("unsupported format!")
if(wavfile.block_align!=256) {
error("unsupported block alignment!")
}
}
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
cx16.VERA_AUDIO_CTRL = %10101011 ; mono 16 bit, volume 11
if wavfile.nchannels==2
cx16.VERA_AUDIO_CTRL = %10111111 ; stereo 16 bit
cx16.VERA_AUDIO_CTRL = %10111011 ; stereo 16 bit, volume 11
if(wavfile.bits_per_sample==8)
cx16.VERA_AUDIO_CTRL &= %11011111 ; set to 8 bit instead
repeat 1024
@ -103,7 +103,7 @@ main {
if diskio.f_open(MUSIC_FILENAME) {
uword block_size = 1024
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM
block_size = wavfile.block_align
block_size = wavfile.block_align * 2 ; read 2 adpcm blocks at a time (512 bytes)
void diskio.f_read(buffer, wavfile.data_offset) ; skip to actual sample data start
void diskio.f_read(buffer, block_size) ; preload buffer
cx16.VERA_AUDIO_RATE = vera_rate ; start playback
@ -144,6 +144,7 @@ main {
interrupt {
bool aflow_semaphore
uword @requirezp nibblesptr
asmsub wait_and_clear_aflow_semaphore() {
%asm {{
@ -159,8 +160,17 @@ interrupt {
if cx16.VERA_ISR & %00001000 {
; AFLOW irq occurred, refill buffer
aflow_semaphore = 0
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM
adpcm_block()
if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM {
nibblesptr = main.start.buffer
if wavfile.nchannels==2 {
adpcm_block_stereo()
adpcm_block_stereo()
}
else {
adpcm_block_mono()
adpcm_block_mono()
}
}
else if wavfile.bits_per_sample==16
uncompressed_block_16()
else
@ -235,9 +245,8 @@ interrupt {
; }
}
sub adpcm_block() {
sub adpcm_block_mono() {
; 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)
@ -256,4 +265,85 @@ interrupt {
}
}
}
sub adpcm_block_stereo() {
; refill the fifo buffer with one decoded adpcm block (1010 bytes of pcm data)
adpcm.init(peekw(nibblesptr), @(nibblesptr+2)) ; left channel
cx16.VERA_AUDIO_DATA = lsb(adpcm.predict)
cx16.VERA_AUDIO_DATA = msb(adpcm.predict)
adpcm.init_second(peekw(nibblesptr+4), @(nibblesptr+6)) ; right channel
cx16.VERA_AUDIO_DATA = lsb(adpcm.predict_2)
cx16.VERA_AUDIO_DATA = msb(adpcm.predict_2)
nibblesptr += 8
repeat 248/8
decode_nibbles_unrolled()
}
sub decode_nibbles_unrolled() {
; decode 4 left channel nibbles
uword[8] left
uword[8] right
ubyte @requirezp nibble = @(nibblesptr)
adpcm.decode_nibble(nibble & 15) ; first word
left[0] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[1] = adpcm.predict
nibble = @(nibblesptr+1)
adpcm.decode_nibble(nibble & 15) ; first word
left[2] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[3] = adpcm.predict
nibble = @(nibblesptr+2)
adpcm.decode_nibble(nibble & 15) ; first word
left[4] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[5] = adpcm.predict
nibble = @(nibblesptr+3)
adpcm.decode_nibble(nibble & 15) ; first word
left[6] = adpcm.predict
adpcm.decode_nibble(nibble>>4) ; second word
left[7] = adpcm.predict
; decode 4 right channel nibbles
nibble = @(nibblesptr+4)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[0] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[1] = adpcm.predict_2
nibble = @(nibblesptr+5)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[2] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[3] = adpcm.predict_2
nibble = @(nibblesptr+6)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[4] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[5] = adpcm.predict_2
nibble = @(nibblesptr+7)
adpcm.decode_nibble_second(nibble & 15) ; first word
right[6] = adpcm.predict_2
adpcm.decode_nibble_second(nibble>>4) ; second word
right[7] = adpcm.predict_2
nibblesptr += 8
%asm {{
; copy to vera PSG fifo buffer
ldy #0
- lda p8_left,y
sta cx16.VERA_AUDIO_DATA
lda p8_left+1,y
sta cx16.VERA_AUDIO_DATA
lda p8_right,y
sta cx16.VERA_AUDIO_DATA
lda p8_right+1,y
sta cx16.VERA_AUDIO_DATA
iny
iny
cpy #16
bne -
}}
}
}

View File

@ -5,4 +5,4 @@ org.gradle.daemon=true
kotlin.code.style=official
javaVersion=11
kotlinVersion=1.9.10
version=9.5-SNAPSHOT
version=9.5