%import textio %import diskio %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. ; ; Note that 8 bit wav files are *unsigned* values whereas Vera wants *signed* values ; so these have to be converted on the fly. 16 bit wav files are signed already. ; ; 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 = "?"*32 txt.print("name of .wav file to play on drive 8: ") while 0==txt.input_chars(MUSIC_FILENAME) { ; until user types a name... } bool wav_ok = false txt.print("\nchecking ") txt.print(MUSIC_FILENAME) txt.nl() if diskio.f_open(MUSIC_FILENAME) { void diskio.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 > 48828 or wavfile.bits_per_sample>16 { error("unsupported format!") } if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM { 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 = %10101011 ; mono 16 bit, volume 11 if wavfile.nchannels==2 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 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(MUSIC_FILENAME) { uword block_size = 1024 if wavfile.wavefmt==wavfile.WAVE_FORMAT_DVI_ADPCM 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 repeat { interrupt.wait_and_clear_aflow_semaphore() ;; cx16.vpoke(1,$fa00, $a0) ; paint a screen color uword size = diskio.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++ } } } 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 - }} } }