mirror of
https://github.com/irmen/prog8.git
synced 2025-02-16 22:30:46 +00:00
added cx16/stream-wav example, refactor pcmaudio code
This commit is contained in:
parent
540b3ae2f4
commit
00bc99cc7b
@ -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
|
||||
|
@ -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
|
||||
}}
|
||||
|
@ -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
|
||||
|
||||
...
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
||||
}
|
||||
|
@ -145,5 +145,5 @@ _done: rts
|
||||
|
||||
.align $0100
|
||||
pcm_data:
|
||||
.binary "pcm-mono.bin"
|
||||
.binary "small-pcm-mono.bin"
|
||||
pcm_data_end:
|
||||
|
@ -163,5 +163,5 @@ _done: rts
|
||||
|
||||
.align $0100
|
||||
pcm_data:
|
||||
.binary "pcm-mono.bin"
|
||||
.binary "small-pcm-mono.bin"
|
||||
pcm_data_end:
|
||||
|
170
examples/cx16/pcmaudio/play-adpcm.p8
Normal file
170
examples/cx16/pcmaudio/play-adpcm.p8
Normal 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:
|
||||
|
||||
}
|
221
examples/cx16/pcmaudio/stream-wav.p8
Normal file
221
examples/cx16/pcmaudio/stream-wav.p8
Normal 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++
|
||||
}
|
||||
}
|
||||
}
|
60
examples/cx16/pcmaudio/wavfile.p8
Normal file
60
examples/cx16/pcmaudio/wavfile.p8
Normal 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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user