( wave.tal ) ( ) ( currently reads PCM data ) ( WAV format (number byte order is little-endian) ) ( ) ( BYTES EXAMPLE BYTES DESCRIPTION ) ( 0-3 "RIFF" 52 49 46 46 riff string ) ( 4-7 10248036 64 5f 9c 00 overall file size - 8 bytes ) ( 8-11 "WAVE" 57 41 56 45 wave string ) ( 12-15 "fmt " 66 6d 74 20 fmt string ) ( 16-19 1 01 00 00 00 length of format data? [???] ) ( 20-21 1 01 00 type of data, 1 is PCM ) ( 22-23 1 01 00 number of channels, 1 is mono ) ( 24-27 44100 44 ac 00 00 sample-rate, 44100 is 44.1 kHz ) ( 28-31 44100 44 ac 00 00 [rate * channels * bits/channel]/8 ) ( 32-33 1 01 00 [bitrate*channels]/8 ) ( 34-35 8 08 00 bitrate, i.e. bits per sample ) ( 36-39 "data" 64 61 74 61 data string ) ( 40-43 10248000 40 5f 9c 00 data size in bytes ) ( 44+ PCM data, little endian byte order ) ( ) ( more complex WAV files may have other structures. ) ( ) ( we can validate whether the WAV is a format we can handle ) ( by comparing byes 4-7 with bytes 40-43. if the former is ) ( 36 bytes larger than the latter, the file has a standard ) ( structure which we can easily parse. ) ( ) ( s16 -> u8: hi-byte + 0x80 ) ( stereo -> mono, take second sample ) ( ) ( data created using: ) ( lame --decode tokyo-skies.mp3 # produced tokyo-skies.wav ) ( sox tokyo-skies.wav -c 1 -e unsigned-integer -b 8 tokyo-skies-mono-u8.wav ) ( sox tokyo-skies-mono-u8.wav tokyo-skies.raw ) ( |00 @System [ &vec $2 &wst $1 &rst $1 &pad $4 &r $2 &g $2 &b $2 &dbg $1 &halt $1 ] ) |10 @Console [ &vec $2 &read $1 &pad $5 &out $1 &err $1 ] |30 @Audio0 [ &vec $2 &pos $2 &out $1 &dur $2 &pad $1 &adsr $2 &len $2 &addr $2 &vol $1 &pitch $1 ] |40 @Audio1 [ &vec $2 &pos $2 &out $1 &dur $2 &pad $1 &adsr $2 &len $2 &addr $2 &vol $1 &pitch $1 ] |a0 @File [ &vec $2 &ok $2 &stat $2 &del $1 &append $1 &name $2 &len $2 &r $2 &w $2 ] |0000 @done $1 @pos $2 @is-stereo $1 @is-8bit $1 |0100 ;filename .pos STZ2 ;read-filename .Console/vec DEO2 BRK @read-filename ( -> ) #12 DEI ( c^ ) DUP #0a EQU ?&exec ( c^ ) DUP #00 EQU ?&exec ( c^ ) .pos LDZ2 STA ( ; $pos<-c ) .pos LDZ2 INC2 .pos STZ2 ( ; pos<-pos+1 ) BRK ( ) &exec ( c^ -> ) POP #00 .pos LDZ2 STA ( ; $pos<-0 ) !start ( ) @start ( -> ) ;filename .File/name DEO2 parse .is-stereo LDZ ?&stereo .is-8bit LDZ ?&mono-8bit LITr 01 ;mono-s16-to-u8 !&done &mono-8bit LITr 00 ;mono-u8-to-u8 !&done &stereo .is-8bit LDZ ?&stereo-8bit LITr 02 ;stereo-s16-to-u8 !&done &stereo-8bit LITr 01 ;stereo-u8-to-u8 !&done &done LIT2 =reload/resample STA2 ( ; save resample function ) LIT2r =reload/sft STAr ( ; save shift size ) #2274 .File/len DEO2 #2274 DUP2 ;a/len STA2 DUP2 ;a/l-buf zero-buf-u8 DUP2 ;a/r-buf zero-buf-u8 DUP2 ;b/len STA2 DUP2 ;b/l-buf zero-buf-u8 ;b/r-buf zero-buf-u8 !play-a @zero-buf-u8 ( len* buf* -> ) STH2k ADD2 STH2 SWP2r ( [limit=buf+len* buf*] ) #80 ( 80^ [limit* buf*] ) &loop ( 80^ [limit* pos*] ) DUP STH2kr STA ( 80^ [limit* pos*] ; pos<-80 ) INC2r GTH2kr STHr ?&loop ( 80^ [limit* pos+1*] ) POP POP2r POP2r JMP2r ( ) ( TODO: validate PCM, freq (44.1 kHz), etc. ) ( TODO: debug/error messages ) @parse ( -> ) #002c .File/len DEO2 ;header .File/r DEO2 .File/ok DEI2 #002c EQU2k ?&ok #0000 DIV &ok POP2 POP2 #0016 #0200 hdr-eq2 .is-stereo STZ ( ; hdr+22 ) #0022 #0800 hdr-eq2 .is-8bit STZ ( ; hdr+34 ) JMP2r @hdr-eq2 ( offset* v* -> eq^ ) STH2 ;header ADD2 LDA2 STH2r EQU2 JMP2r @reload ( l-addr* bl-addr* br-addr* -> ) SWP2 STH2 STH2 ( l-addr* [bl-addr* br-addr*] ) .done LDZ ?&skip ( l-addr* [bl-addr* br-addr*] ) ;scratch .File/r DEO2 ( l-addr* [bl-addr* br-addr*] ) .File/ok DEI2 ( l-addr* read* [bl-addr* br-addr*] ) DUP2 LIT &sft $1 SFT2 ( l-addr* read* read>>sft [bl-addr* br-addr*] ) ROT2 STA2 ( read* [bl-addr* br-addr*] ; l-addr<-read>>sft ) DUP2 #2274 EQU2 ?&end ( read* [bl-addr* br-addr*] ; if we read 0x2274 we are not done ) #01 .done STZ ( read* [bl-addr* br-addr*] ; done<-1 ) &end ( read* [bl-addr* br-addr*] ) ;scratch ( read* scratch* [bl-addr* br-addr*] ) DUP2 ROT2 ADD2 SWP2 ( limit=scratch+read* scratch* [bl-addr* br-addr*] ) INC2 ( limit* scratch+1* [bl-addr* br-addr*] ) &loop ( limit* pos* [bl-pos* br-pos*] ) LIT2 [ &resample $2 ] JSR2 ( limit* pos+n* l-sample^ r-sample^ [bl-pos* br-pos*] ) STH2kr STA INC2 SWP2r ( limit* pos+n* [br-pos+1* bl-pos*] ; br-pos<-sample ) STH2kr STA INC2 SWP2r ( limit* pos+n* [bl-pos+1* br-pos+1*] ; bl-pos<-sample ) GTH2k ?&loop ( limit* pos+n* [bl-pos+1* br-pos+1*] ) POP2r POP2r POP2 POP2 JMP2r ( ) &skip ( l-addr* [bl-addr* br-addr*] ) #2274 DUP2 STH2r zero-buf-u8 ( l-addr* #2274 [bl-addr*] ; clear br-addr ) DUP2 STH2r zero-buf-u8 ( l-addr* #2274 ; clear bl-addr ) SWP2 STA2 JMP2r ( ; l-addr<-2274 ) @mono-u8-to-u8 ( pos* -> pos+1* l-sample^ r-sample^ ) LDAk STH INC2 ( pos+1* [s^] ) STHr DUP JMP2r ( pos+1 l-s^ r-s^ ) @mono-s16-to-u8 ( pos* -> pos+2* l-sample^ r-sample^ ) LDAk #80 ADD STH INC2 INC2 ( pos+2* [s^] ) STHr DUP JMP2r ( pos+2* l-s^ r-s^ ) @stereo-u8-to-u8 ( pos* -> pos+2* l-sample^ r-sample^ ) INC2k SWP2 LDA STH ( pos+1* [l-s^] ) INC2k SWP2 LDA STH ( pos+2* [l-s^ r-s^] ) STH2r JMP2r ( pos+2* l-s^ r-s^ ) @stereo-s16-to-u8 ( pos* -> pos+4* sample^ ) LDAk #80 ADD STH INC2 INC2 ( pos+2* [l-s^] ) LDAk #80 ADD STH INC2 INC2 ( pos+4* [l-s^ r-s^] ) STH2r JMP2r ( pos+4* l-s^ r-s^ ) @play-a ( -> ) ;play-b ;a !play-base @play-b ( -> ) ;play-a ;b !play-base @play-base ( next* base* -> ) SWP2 .Audio0/vec DEO2 ( base* ; vec<-next ) INC2k INC2 STH2k ( l-addr* lb-addr* [lb-addr*] ) #2274 ADD2 STH2 ( l-addr* [lb-addr* rb-addr*] ) ( LDA2k ORAk ?&non-zero ( l-addr* n* [lb-addr* rb-addr*] ) POP2 POP2 POP2r POP2r ( ; clear stack ) #010f BRK ( ; exit ) &non-zero ( l-addr* n* [lb-addr* rb-addr*] ) ) DUP2 STH2kr r-output SWP2r ( l-addr* n* [rb-addr* lb-addr*] ; play rb-addr ) STH2kr l-output SWP2r ( l-addr* [lb-addr* rb-addr*] ; play lb-addr ) SWP2r STH2r STH2r reload BRK ( ; load more data ) @bytes-to-millis ( samples* -> ms* ) #01b9 DIV2 #000a MUL2 JMP2r @l-output ( len* addr* -> ) .Audio0/addr DEO2 ( ; <- write buf addr ) DUP2 .Audio0/len DEO2 ( ; <- write length in bytes/samples ) bytes-to-millis .Audio0/dur DEO2 ( ; <- write duration in milliseconds ) #00f0 .Audio0/adsr DEO2 ( ; <- write ignore envelope ) #f0 .Audio0/vol DEO ( ; <- play 100% volume ) #bc .Audio0/pitch DEO ( ; <- play standard sample once ) JMP2r @r-output ( len* addr* -> ) .Audio1/addr DEO2 ( ; <- write buf addr ) DUP2 .Audio1/len DEO2 ( ; <- write length in bytes/samples ) bytes-to-millis .Audio1/dur DEO2 ( ; <- write duration in milliseconds ) #00f0 .Audio1/adsr DEO2 ( ; <- write ignore envelope ) #0f .Audio1/vol DEO ( ; <- play 100% volume ) #bc .Audio1/pitch DEO ( ; <- play standard sample once ) JMP2r ( buffer size is 0x2274, i.e. 8820. ) ( this is an important number: 8820 = 4 * 5 * 441. ) ( since it is divisible by 4 we know that the buffer will read ) ( an exact number of samples, even with 16-bit stereo. and since ) ( it is divisble by 441 we know it will always contain a multiple ) ( 10 milliseconds of audio. these assumptions help ensure we don't ) ( end up with static, popping, or other problems. ) @filename $100 @header $2c ( @len0 $2 @buf0 $2274 @len1 $2 @buf1 $2274 ) @scratch $2274 @a [ &len $2 &l-buf $2274 &r-buf $2274 ] @b [ &len $2 &l-buf $2274 &r-buf $2274 ]