-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwavplay.tal
354 lines (318 loc) · 9.66 KB
/
wavplay.tal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
|00 @System |08 &r |0a &g |0c &b |0e &debug |0f &state
|10 @Console &vector |12 &read |17 &type |18 &write
|20 @Screen &vector |22 &width |24 &height |26 &auto |28 &x |2a &y |2c &addr |2e &pixel |2f &sprite
|30 @Audio0 |32 &position |3a &length |3c &addr |3e &volume |3f &pitch
|40 @Audio1 |42 &position |4a &length |4c &addr |4e &volume |4f &pitch
|50 @Audio2 |52 &position |5a &length |5c &addr |5e &volume |5f &pitch
|60 @Audio3 |62 &position |6a &length |6c &addr |6e &volume |6f &pitch
|80 @Controller &vector |82 &button |83 &key
|90 @Mouse &vector |92 &x |94 &y |96 &state
|a0 @File0 |a2 &success |a8 &name |aa &length |ac &read
%ZERO2 { DUP2k EOR2 } ( equivalent to #0000. requires 2 bytes already on the stack! )
%BUFSIZE { #2000 } ( size of sample buffer for one audio device )
%HBUFSIZE { #1000 } ( half buffer size )
%DBUFSIZE { #4000 } ( double buffer size )
%NHBUFSIZE { #f000 } ( negative half buffer size )
|4000
@filebuf $4000 @filebuf-end ( DBUFSIZE )
@wavbuf-l-hi $2000 ( BUFSIZE )
@wavbuf-r-hi $2000
@wavbuf-l-lo $2000
@wavbuf-r-lo $2000
|0000
@file-name $30
@data-len &hi $2 &lo $2
@play-pos &hi $2 &lo $2
@buf $1 ( which half of the buffers should be written next 0/1 )
@at-end $1
@pointer &x $2 &y $2
|0100 @Reset ( -> 0* file-name-zptr )
#0000 ( useful for ZERO2 macro )
.Console/type DEI ?&no-usage
print: "Usage: 20 "wavplay.rom 20 "file.wav 0a00
&no-usage ( 0* ~> 0* file-name-zptr )
;on-console .Console/vector DEO2
.file-name
BRK
@on-console ( file-name-zptr -> )
.Console/type DEI #03 GTH ?&done-args ( not supported in Uxn32! )
.Console/read DEI [ DUP #0a EQU ?&newline ] OVR STZ INC
BRK
&newline ( file-name-zptr char ~+ file-name-zptr )
POP
&done-args ( file-name-zptr ~> )
POP
ZERO2 .Console/vector DEO2
#0100 .Screen/width DEO2
#0040 .Screen/height DEO2
#5f7f .System/r DEO2
#7fe0 .System/g DEO2
#afc0 .System/b DEO2
restart-file
print: "Playing 2000
[ ;file-name print-str POP2 ] print: 0a00
ZERO2 read-wav-data ( fill buffers with initial data )
HBUFSIZE read-wav-data
LIT2 f0 -Audio0/volume DEO
LIT2 0f -Audio1/volume DEO
LIT2 10 -Audio2/volume DEO ( for playing lower 4 bits of 12-bit PCM )
LIT2 01 -Audio3/volume DEO
;wavbuf-l-hi .Audio0/addr DEO2
;wavbuf-r-hi .Audio1/addr DEO2
;wavbuf-l-lo .Audio2/addr DEO2
;wavbuf-r-lo .Audio3/addr DEO2
;on-vblank .Screen/vector DEO2
;on-controller .Controller/vector DEO2
;on-mouse .Mouse/vector DEO2
BUFSIZE play-all-channels
BRK
@restart-file ( -- )
;file-name .File0/name DEO2
read-wav-header
DBUFSIZE .File0/length DEO2
ZERO2 DUP2 .play-pos/hi STZ2 .play-pos/lo STZ2
JMP2r
@play-all-channels ( length* -- )
DUP2k DUP2 .Audio0/length DEO2 .Audio1/length DEO2 .Audio2/length DEO2 .Audio3/length DEO2
LIT2 3c -Audio0/pitch OVR .Audio1/pitch OVR .Audio2/pitch OVR .Audio3/pitch
DEO DEO DEO DEO ( start them all as close together as possible )
JMP2r
@on-controller ( -> )
.Controller/key DEI #20 NEQ ?¬-space
.Audio0/length DEI ?&is-playing
BUFSIZE play-all-channels
BRK
&is-playing
ZERO2 play-all-channels
BRK
¬-space
.Controller/button DEI #40 NEQ ?¬-left
ZERO2 skip-to
¬-left
BRK
@on-mouse ( -> )
;pointer-icn .Screen/addr DEO2
( clear cursor )
.pointer/x LDZ2 .Screen/x DEO2
.pointer/y LDZ2 .Screen/y DEO2
LIT2 40 -Screen/sprite DEO
( draw cursor )
.Mouse/x DEI2 [ DUP2 .pointer/x STZ2 ] .Screen/x DEO2
.Mouse/y DEI2 [ DUP2 .pointer/y STZ2 ] .Screen/y DEO2
LIT2 42 -Screen/sprite DEO
.Mouse/state DEI #01 NEQ ?¬-pressed
.data-len/hi LDZ2 ZERO2 ( h l 0 0 ) ROT SWP2 SWP ( 0 l 0 h )
.Mouse/x DEI2 MUL2
SWP2 .Mouse/x DEI2 MUL2 #08 SFT2 ADD2
skip-to
¬-pressed
BRK
@on-vblank ( -> )
.Audio0/length DEI ?&is-playing
BRK
&is-playing
( check if position has reached one half of the buffer, then load the other half )
.Audio0/position DEI2 HBUFSIZE LTH2
.buf LDZ ?&buf1
( buf0 )
?&done
LIT2 01 -buf STZ
ZERO2 [ DUP2 read-wav-data ] draw-vis
&done ( ~> )
BRK
&buf1 ( first-half? ~> )
?&load-buf1
BRK
&load-buf1 ( ~> )
LIT2 00 -buf STZ
HBUFSIZE [ DUP2 read-wav-data ] draw-vis
BRK
@draw-vis ( offset* -- )
[ ZERO2 .Screen/x DEO2 ] #0028 [ DUP2 .Screen/y DEO2 ]
#0100 #0010 #00 fill-rect ( clear VU area )
.Screen/y DEO2
( VU meters )
[ DUP2 ;wavbuf-l-hi ADD2 channel-level ] #00 SWP #0008 #01 fill-rect
#0030 .Screen/y DEO2
[ ;wavbuf-r-hi ADD2 channel-level ] #00 SWP #0008 #01 fill-rect
#0008 .Screen/y DEO2
#0100 #0010 #00 fill-rect ( clear playhead area )
[ #0010 .Screen/y DEO2 ] [ LIT2 01 -Screen/auto DEO ]
#0100 #01 draw-line
.data-len/hi LDZ2 INC2 INC2 #02 SFT2 ORAk ?&draw-time ( avoid div/0 )
POP2
JMP2r
&draw-time ( data-len-hi-rsh-2* ~- )
[ [ .play-pos/hi LDZ2 #40 SFT2 ] [ .play-pos/lo LDZ2 #0c SFT2 ] ADD2 ]
SWP2 DIV2 #20 SFT2 .Screen/x DEO2
[ #0008 .Screen/y DEO2 ] [ LIT2 02 -Screen/auto DEO ]
#0010 #01 !draw-line ( length* pix >> )
@read-wav-data ( out-offset* -- )
.at-end LDZ ?exit
read-next
check-end
( we're loading 16-bit PCM, interleaved stereo )
( so we need to de-interleave the channels, and copy the high/low bits to the hi/lo buffers )
SWP2
&loop ( in* out-offset* ~- )
SWP2k LDA2
#80 ADD SWP2 ;wavbuf-l-hi ADD2 STAk
[ SWP2 SWP #04 SFT #80 ADD SWP2 ] DBUFSIZE ADD2 STA POP ( lo )
SWP2k INC2 INC2 LDA2
#80 ADD SWP2 ;wavbuf-r-hi ADD2 STAk
[ SWP2 SWP #04 SFT #80 ADD SWP2 ] DBUFSIZE ADD2 STA POP
SWP2 #0004 ADD2 SWP2 INC2
OVR2 ;filebuf-end LTH2 ?&loop
POP2 POP2
JMP2r
@read-next ( -- buf* )
;filebuf [ DUP2 .File0/read DEO2 ]
.play-pos/lo LDZ2 [ DUP2 DBUFSIZE ADD2 ]
DUP2 .play-pos/lo STZ2
LTH2 ?&no-carry [ .play-pos/hi LDZ2k INC2 ROT STZ2 ] &no-carry
JMP2r
@check-end ( -- )
.play-pos/hi LDZ2 .data-len/hi LDZ2 LTH2k ?¬-at-end-hi
GTH2 ?&at-end
.play-pos/lo LDZ2 .data-len/lo LDZ2 LTH2 ?¬-at-end-lo
&at-end ( ~+ )
LIT2 01 -at-end STZ
( fill end of buffer with zeroes )
[ ;filebuf-end [ .play-pos/lo LDZ2 .data-len/lo LDZ2 SUB2 ] SUB2 ] ZERO2 SWP2
&zeroloop ( 0* ptr* ~+ 0* ptr* )
STA2k INC2 INC2 DUP2 ;filebuf-end LTH2 ?&zeroloop
¬-at-end-hi ( a* b* ~+ )
POP2 POP2
¬-at-end-lo ( ~- )
JMP2r
@skip-to ( hi* -- )
DUP2 .data-len/hi LDZ2 GTH2 ?&done-loop
DUP2 .play-pos/hi LDZ2 GTH2 ?&dont-restart
restart-file
DUP2 ZERO2 EQU2 ?&done-loop
&dont-restart ( hi* ~+ hi* )
&loop ( hi* ~+ hi* )
read-next POP2
DUP2 .play-pos/hi LDZ2 GTH2 ?&loop
&done-loop ( hi* ~> )
POP2
.Audio0/length DEI ?&is-playing
ZERO2 DUP2 read-wav-data
HBUFSIZE read-wav-data
LIT2 00 -buf STZ
draw-vis
&is-playing ( ~- )
JMP2r
@channel-level ( buf* -- level )
LITr 00
NHBUFSIZE
&loop ( buf* -i* `max ~- )
OVR2 LDA
DUP #80 GTH ?&pos
#00 SWP SUB
&pos
#80 SUB STH
GTHkr LITr _>h JCNr
SWPr
>h POPr
SWP2 INC2 SWP2 INC2 ORAk ?&loop
POP2 POP2
STHr DUP ADD
JMP2r
( skip to the data section )
@read-wav-header ( -- )
( https://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html )
#000c .File0/length DEO2
;filebuf [ DUP2 .File0/read DEO2 ]
.File0/success DEI2 ZERO2 EQU2 ?fail-empty
DUP2 LIT2 "RI LIT2 "FF fourcc-neq ?fail-format
#08 ADD LIT2 "WA LIT2 "VE fourcc-neq ?fail-format
&loop ( ~- )
read-wav-chunk ?&loop
JMP2r
@read-wav-chunk ( -- keep-reading? )
#0008 .File0/length DEO2
;filebuf [ DUP2 .File0/read DEO2 ]
DUP2 LIT2 "da LIT2 "ta fourcc-neq ?¬-data
#04 ADD LDA2k SWP .data-len/lo STZ2
INC INC LDA2 SWP .data-len/hi STZ2
#00 JMP2r
¬-data ( filebuf* ~+ filebuf* )
OVR #06 LDA2 ORA ?fail-metadata ( chunk length larger than 64k )
OVR #04 LDA2 SWP #fe AND ( chunk length )
.File0/length DEO2
OVR LIT2 08 -File0/read DEO2
DUP2 LIT2 "fm LIT2 "t 20 fourcc-neq ?¬-fmt
( validate format chunk. all these values are byte-swapped little endian )
OVR #0a LDA2 #0200 NEQ2 ?fail-channels ( nChannels )
OVR #16 LDA2 #1000 NEQ2 ?fail-bits ( wBitsPerSample )
OVR #08 LDA2 #0100 NEQ2 ?fail-audio-format ( wFormatTag )
OVR #0c LDA2 #44ac NEQ2 ?&warn-sample-rate ( nSamplesPerSec )
OVR #0e LDA2 ZERO2 EQU2 ?¬-fmt
&warn-sample-rate
print: "Warning: 20 "WAV 20 "sample 20 "rate 20 "is 20 "not 20 "44,100 20 "Hz. 0a
2020 "Audio 20 "will 20 "play 20 "at 20 "incorrect 20 "speed! 0a00
¬-fmt ( filebuf* ~- keep-reading? )
POP2
#01
JMP2r
@fourcc-neq ( addr* val0* val1* -- neq? )
ROT2 ROT2 OVR2 ( val1* addr* val0* addr* )
LDA2 NEQ2 ?&first-neq
INC2 INC2 LDA2 NEQ2
JMP2r
&first-neq ( val1* addr* ~- neq? )
POP2 POP2 #01
JMP2r
@fail-empty fail: "Empty 20 "or 20 "missing 20 "file 00
@fail-format fail: "Unrecognized 20 "format 00
@fail-metadata fail: "Unrecognized 20 "metadata 00
@fail-audio-format fail: "Unsupported 20 "audio 20 "format 00
@fail-channels fail: "WAV 20 "must 20 "be 20 "stereo 00
@fail-bits fail: "WAV 20 "must 20 "be 20 "16-bit 20 "PCM 00
@fail: ( `msg* -- )
print: "Error: 2000
[ STH2r print-str POP2 ] print: 0a00
teardown
LIT2 01 -System/state DEO
BRK
@exit ( -- )
teardown
LIT2 80 -System/state DEO
BRK
@teardown ( -- )
ZERO2
[ DUP2 .Console/vector DEO2 ] [ DUP2 .Screen/vector DEO2 ]
[ DUP2 .Controller/vector DEO2 ] [ DUP2 .Mouse/vector DEO2 ]
!play-all-channels ( >> )
@print: ( `str* -- )
STH2r
print-str
INC2 JMP2
@print-str ( str* -- end* )
LDAk .Console/write DEO
INC2 LDAk ?print-str
JMP2r
@draw-line ( length* pix -- )
STH
ZERO2 SWP2 SUB2 ( make negative )
&loop ( -length* `pix ~- )
ORAk ?&continue ( check first to handle 0 length )
POP2 POPr JMP2r
&continue
DUPr LITr -Screen/pixel DEOr
INC2 !&loop
@fill-rect ( width* height* pix -- )
LIT2 01 -Screen/auto DEO
STH
ZERO2 SWP2 SUB2 ( make negative )
&loop ( width* -height* `pix ~- )
ORAk ?&continue
POP2 POP2 POPr JMP2r
&continue
OVR2 STHkr draw-line
OVR2 .Screen/x DEI2 SWP2 SUB2 .Screen/x DEO2
.Screen/y DEI2k INC2 ROT DEO2
INC2 !&loop
@pointer-icn ( credit: Devine Lu Linvega )
80c0 e0f0 f8e0 1000