Browse code

Added Uxn-based synth

Andrew Alderwick authored on 07/04/2021 20:50:35
Showing 6 changed files
... ...
@@ -6,6 +6,7 @@ clang-format -i src/uxn.h
6 6
 clang-format -i src/uxn.c
7 7
 clang-format -i src/emulator.c
8 8
 clang-format -i src/debugger.c
9
+clang-format -i src/apu.c
9 10
 
10 11
 echo "Cleaning.."
11 12
 rm -f ./bin/assembler
... ...
@@ -19,12 +20,12 @@ if [ "${1}" = '--debug' ];
19 20
 then
20 21
 	echo "[debug]"
21 22
     cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/assembler.c -o bin/assembler
22
-	cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/emulator.c -L/usr/local/lib -lSDL2 -o bin/emulator
23
+	cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/emulator.c src/apu.c -L/usr/local/lib -lSDL2 -o bin/emulator
23 24
     cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/debugger.c -o bin/debugger
24 25
 else
25 26
 	cc src/assembler.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -o bin/assembler
26 27
 	cc src/uxn.c src/debugger.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -o bin/debugger
27
-	cc src/uxn.c src/emulator.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -L/usr/local/lib -lSDL2 -o bin/emulator
28
+	cc src/uxn.c src/emulator.c src/apu.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -L/usr/local/lib -lSDL2 -o bin/emulator
28 29
 fi
29 30
 
30 31
 echo "Assembling.."
... ...
@@ -11,7 +11,7 @@
11 11
 |0140 ;Keys { key 1 }
12 12
 |0150 ;Mouse { x 2 y 2 state 1 chord 1 }
13 13
 |0160 ;File { pad 8 name 2 length 2 load 2 save 2 }
14
-|0170 ;Audio { ch1asdr 2 ch2asdr 2 ch3asdr 2 ch4asdr 2 ch1pitch 1 ch1vol 1 ch2pitch 1 ch2vol 1 ch3pitch 1 ch3vol 1 ch4pitch 1 ch4vol 1 }
14
+|0180 ;Audio { wave 2 envelope 2 pad 4 volume 1 pitch 1 play 1 value 2 delay 2 finish 1 }
15 15
 |01F0 ;System { pad 8 r 2 g 2 b 2 }
16 16
 
17 17
 ( vectors )
... ...
@@ -6,6 +6,9 @@
6 6
 %++  { #0001 ADD2 }
7 7
 %MOD { DUP2 DIV MUL SUB }
8 8
 %TRACK { ,track.ch1 #00 ~track.active #0020 MUL2 ADD2 }
9
+%SOUND { STH #00 =Audio.value STHr #00 =Audio.delay }
10
+%SOUND2 { =Audio.value =Audio.delay }
11
+%SOUND_FINISH { #00 =Audio.finish }
9 12
 
10 13
 ( variables )
11 14
 
... ...
@@ -19,6 +22,8 @@
19 22
 ;knob { x 2 y 2 value 1 }
20 23
 ;head { pos 1 }
21 24
 ;track { active 1 ch1 20 ch2 20 ch3 20 ch4 20 }
25
+;adsr { ch1a 1 ch1d 1 ch1s 1 ch1r 1 ch2a 1 ch2d 1 ch2s 1 ch2r 1 ch3a 1 ch3d 1 ch3s 1 ch3r 1 ch4a 1 ch4d 1 ch4s 1 ch4r 1 }
26
+;volume { ch1 1 ch2 1 ch3 1 ch4 1 }
22 27
 
23 28
 ( devices )
24 29
 
... ...
@@ -30,7 +35,7 @@
30 35
 |0150 ;Keys { key 1 }
31 36
 |0160 ;Mouse  { vector 2 x 2 y 2 state 1 chord 1 }
32 37
 |0170 ;File { pad 8 name 2 length 2 load 2 save 2 }
33
-|0180 ;Audio { ch1adsr 2 ch2adsr 2 ch3adsr 2 ch4adsr 2 ch1vol 1 ch1pitch 1 ch2vol 1 ch2pitch 1 ch3vol 1 ch3pitch 1 ch4vol 1 ch4pitch 1 }
38
+|0180 ;Audio { wave 2 envelope 2 pad 4 volume 1 pitch 1 play 1 value 2 delay 2 finish 1 }
34 39
 
35 40
 ( vectors )
36 41
 
... ...
@@ -51,9 +56,10 @@
51 56
 	~trkframe.x2 =ctlframe.x2 ~chnframe.y2 =ctlframe.y2
52 57
 
53 58
 	( default settings )
54
-	#048c =Audio.ch1adsr #88 =Audio.ch1vol
55
-	#159d =Audio.ch2adsr #88 =Audio.ch2vol
56
-	#26ae =Audio.ch3adsr #88 =Audio.ch3vol
59
+	,adsr-envelope =Audio.envelope
60
+	#00 =adsr.ch1a #40 =adsr.ch1d #80 =adsr.ch1s #c0 =adsr.ch1r #88 =volume.ch1
61
+	#10 =adsr.ch2a #50 =adsr.ch2d #90 =adsr.ch2s #d0 =adsr.ch2r #88 =volume.ch2
62
+	#20 =adsr.ch3a #60 =adsr.ch3d #a0 =adsr.ch3s #e0 =adsr.ch3r #88 =volume.ch3
57 63
 
58 64
 	,draw-timeline JSR2
59 65
 	,draw-controls JSR2
... ...
@@ -110,29 +116,29 @@ BRK
110 116
 	
111 117
 	~Mouse.x ~ctlframe.x1 SUB2 8- 8/ SWP POP #02 DIV
112 118
 	DUP #00 NEQ ^$no-a JNZ
113
-		,Audio #00 ~track.active #02 MUL ADD2 PEK2
114
-		#10 ~Mouse.state #10 EQU #e0 MUL ADD ADD 
115
-		,Audio #00 ~track.active #02 MUL ADD2 POK2 $no-a
119
+		,adsr #00 ~track.active #04 MUL ADD2 PEK2
120
+		#10 ~Mouse.state #10 EQU #e0 MUL ADD ADD
121
+		,adsr #00 ~track.active #04 MUL ADD2 POK2 $no-a
116 122
 	DUP #01 NEQ ^$no-d JNZ
117
-		,Audio #00 ~track.active #02 MUL ADD2 PEK2
118
-		DUP #f0 AND STH #01 ~Mouse.state #10 EQU #0e MUL ADD ADD #0f AND STHr ADD
119
-		,Audio #00 ~track.active #02 MUL ADD2 POK2 $no-d
123
+		,adsr #00 ~track.active #04 MUL ADD2 #0001 ADD2 PEK2
124
+		#10 ~Mouse.state #10 EQU #e0 MUL ADD ADD
125
+		,adsr #00 ~track.active #04 MUL ADD2 #0001 ADD2 POK2 $no-d
120 126
 	DUP #02 NEQ ^$no-s JNZ
121
-		,Audio #00 ~track.active #02 MUL ADD2 ++ PEK2
122
-		#10 ~Mouse.state #10 EQU #e0 MUL ADD ADD 
123
-		,Audio #00 ~track.active #02 MUL ADD2 ++ POK2 $no-s
127
+		,adsr #00 ~track.active #04 MUL ADD2 #0002 ADD2 PEK2
128
+		#10 ~Mouse.state #10 EQU #e0 MUL ADD ADD
129
+		,adsr #00 ~track.active #04 MUL ADD2 #0002 ADD2 POK2 $no-s
124 130
 	DUP #03 NEQ ^$no-r JNZ
125
-		,Audio #00 ~track.active #02 MUL ADD2 ++ PEK2
126
-		DUP #f0 AND STH #01 ~Mouse.state #10 EQU #0e MUL ADD ADD #0f AND STHr ADD
127
-		,Audio #00 ~track.active #02 MUL ADD2 ++ POK2 $no-r
131
+		,adsr #00 ~track.active #04 MUL ADD2 #0003 ADD2 PEK2
132
+		#10 ~Mouse.state #10 EQU #e0 MUL ADD ADD
133
+		,adsr #00 ~track.active #04 MUL ADD2 #0003 ADD2 POK2 $no-r
128 134
 	DUP #05 NEQ ^$no-left JNZ
129
-		,Audio 8+ #00 ~track.active #02 MUL ADD2 PEK2
130
-		#10 ~Mouse.state #10 EQU #e0 MUL ADD ADD 
131
-		,Audio 8+ #00 ~track.active #02 MUL ADD2 POK2 $no-left
135
+		,volume #00 ~track.active ADD2 PEK2
136
+		#10 ~Mouse.state #10 EQU #e0 MUL ADD ADD
137
+		,volume #00 ~track.active ADD2 POK2 $no-left
132 138
 	DUP #06 NEQ ^$no-right JNZ
133
-		,Audio 8+ #00 ~track.active #02 MUL ADD2 PEK2
139
+		,volume #00 ~track.active ADD2 PEK2
134 140
 		DUP #f0 AND STH #01 ~Mouse.state #10 EQU #0e MUL ADD ADD #0f AND STHr ADD
135
-		,Audio 8+ #00 ~track.active #02 MUL ADD2 POK2 $no-right
141
+		,volume #00 ~track.active ADD2 POK2 $no-right
136 142
 	POP
137 143
 	( release ) #00 =Mouse.state
138 144
 	,draw-controls JSR2
... ...
@@ -146,21 +152,30 @@ BRK
146 152
 	DUP #ff NEQ ^$skip1 JNZ
147 153
 		POP ^$listen2 JMP
148 154
 	$skip1
149
-	#00 SWP ,notes ADD2 PEK2 =Audio.ch1pitch
155
+	#00 SWP ,notes ADD2 PEK2 =Audio.pitch
156
+	~volume.ch1 =Audio.volume
157
+	,square-wave =Audio.wave
158
+	#00 =Audio.play
150 159
 	$listen2
151 160
 	,track.ch2 #00 ~head.pos #08 DIV ADD2 PEK2 
152 161
 	#01 SUB
153 162
 	DUP #ff NEQ ^$skip2 JNZ
154 163
 		POP ^$listen3 JMP
155 164
 	$skip2
156
-	#00 SWP ,notes ADD2 PEK2 =Audio.ch2pitch
165
+	#00 SWP ,notes ADD2 PEK2 =Audio.pitch
166
+	~volume.ch2 =Audio.volume
167
+	,square-wave =Audio.wave
168
+	#01 =Audio.play
157 169
 	$listen3
158 170
 	,track.ch3 #00 ~head.pos #08 DIV ADD2 PEK2 
159 171
 	#01 SUB
160 172
 	DUP #ff NEQ ^$skip3 JNZ
161 173
 		POP ^$end JMP
162 174
 	$skip3
163
-	#00 SWP ,notes ADD2 PEK2 =Audio.ch3pitch
175
+	#00 SWP ,notes ADD2 PEK2 =Audio.pitch
176
+	~volume.ch3 =Audio.volume
177
+	,triangle-wave =Audio.wave
178
+	#02 =Audio.play
164 179
 	$end
165 180
 
166 181
 RTN
... ...
@@ -264,18 +279,18 @@ RTN
264 279
 	~trkframe.x1 #0018 SUB2 DUP2 ~trkframe.y1 ,draw-octave JSR2
265 280
 	~trkframe.y1 #0038 ADD2 ,draw-octave JSR2
266 281
 	~trkframe.x1 #0028 SUB2 =Sprite.x
267
-	~trkframe.y1 =Sprite.y
268
-	,font_hex #0060 ADD2 =Sprite.addr
282
+	~trkframe.y1 #0030 ADD2 =Sprite.y
283
+	,font_hex #0028 ADD2 =Sprite.addr
269 284
 	#03 =Sprite.color 
270 285
 	~trkframe.x1 #0030 SUB2 =Sprite.x
271
-	,font_hex #0020 ADD2 =Sprite.addr
286
+	,font_hex #0060 ADD2 =Sprite.addr
272 287
 	#03 =Sprite.color 
273 288
 	~trkframe.x1 #0028 SUB2 =Sprite.x
274
-	~trkframe.y1 #0038 ADD2 =Sprite.y
275
-	,font_hex #0060 ADD2 =Sprite.addr
289
+	~trkframe.y1 #0068 ADD2 =Sprite.y
290
+	,font_hex #0020 ADD2 =Sprite.addr
276 291
 	#03 =Sprite.color 
277 292
 	~trkframe.x1 #0030 SUB2 =Sprite.x
278
-	,font_hex #0018 ADD2 =Sprite.addr
293
+	,font_hex #0060 ADD2 =Sprite.addr
279 294
 	#03 =Sprite.color 
280 295
 
281 296
 RTN
... ...
@@ -312,24 +327,24 @@ RTN
312 327
 	( env )
313 328
 	~ctlframe.x1 8+ ~ctlframe.y1 8+ #02 ,env_txt ,draw-label JSR2
314 329
 	~ctlframe.x1 8+ ~ctlframe.y1 #0010 ADD2 
315
-		,Audio #00 ~track.active #02 MUL ADD2 PEK2 #04 SFT
330
+		,adsr #00 ~track.active #04 MUL ADD2 PEK2 #04 SFT
316 331
 		,draw-knob JSR2
317 332
 	~ctlframe.x1 #0018 ADD2 ~ctlframe.y1 #0010 ADD2
318
-		,Audio #00 ~track.active #02 MUL ADD2 PEK2 #0f AND
333
+		,adsr #00 ~track.active #04 MUL ADD2 #0001 ADD2 PEK2 #04 SFT
319 334
 		,draw-knob JSR2
320 335
 	~ctlframe.x1 #0028 ADD2 ~ctlframe.y1 #0010 ADD2
321
-		,Audio #00 ~track.active #02 MUL ADD2 ++ PEK2 #04 SFT
336
+		,adsr #00 ~track.active #04 MUL ADD2 #0002 ADD2 PEK2 #04 SFT
322 337
 		,draw-knob JSR2
323 338
 	~ctlframe.x1 #0038 ADD2 ~ctlframe.y1 #0010 ADD2 
324
-		,Audio #00 ~track.active #02 MUL ADD2 ++ PEK2 #0f AND
339
+		,adsr #00 ~track.active #04 MUL ADD2 #0003 ADD2 PEK2 #04 SFT
325 340
 		,draw-knob JSR2
326 341
 	( vol )
327 342
 	~ctlframe.x1 #0058 ADD2 ~ctlframe.y1 8+ #02 ,vol_txt ,draw-label JSR2
328 343
 	~ctlframe.x1 #0058 ADD2 ~ctlframe.y1 #0010 ADD2
329
-		,Audio 8+ #00 ~track.active #02 MUL ADD2 PEK2 #04 SFT
344
+		,volume #00 ~track.active ADD2 PEK2 #04 SFT
330 345
 	,draw-knob JSR2
331 346
 	~ctlframe.x1 #0068 ADD2 ~ctlframe.y1 #0010 ADD2 
332
-		,Audio 8+ #00 ~track.active #02 MUL ADD2 PEK2 #0f AND
347
+		,volume #00 ~track.active ADD2 PEK2 #0f AND
333 348
 	,draw-knob JSR2
334 349
 
335 350
 RTN
... ...
@@ -415,6 +430,27 @@ RTN
415 430
 
416 431
 RTN
417 432
 
433
+@adsr-envelope ( -- )
434
+	#7f ,adsr #00 ~Audio.play #04 MUL ADD2 PEK2 SOUND
435
+	#40 ,adsr #00 ~Audio.play #04 MUL ADD2 #0001 ADD2 PEK2 SOUND
436
+	#40 ,adsr #00 ~Audio.play #04 MUL ADD2 #0002 ADD2 PEK2 SOUND
437
+	#00 ,adsr #00 ~Audio.play #04 MUL ADD2 #0003 ADD2 PEK2 SOUND
438
+	SOUND_FINISH
439
+	BRK
440
+
441
+@square-wave ( -- )
442
+	#5800 SOUND
443
+	#5880 SOUND
444
+	#a800 SOUND
445
+	#a880 SOUND
446
+	BRK
447
+
448
+@triangle-wave ( -- )
449
+	#7f40 SOUND
450
+	#8180 SOUND
451
+	#0040 SOUND
452
+	BRK
453
+
418 454
 @ch1_txt [ CHN0 00 ]
419 455
 @ch2_txt [ CHN1 00 ]
420 456
 @ch3_txt [ CHN2 00 ]
... ...
@@ -458,13 +494,13 @@ RTN
458 494
 ]
459 495
 
460 496
 @knob_offsetx [
461
-	04 05 06 07 08 07 06 05 
462
-	04 04 03 02 01 00 01 02
497
+	01 00 00 00 00 01 02 03
498
+	05 06 07 08 08 08 08 07
463 499
 ]
464 500
 
465 501
 @knob_offsety [
466
-	00 01 02 03 04 05 06 07 
467
-	08 07 06 05 04 04 03 02
502
+	07 06 05 03 02 01 00 00
503
+	00 00 01 02 03 05 06 07
468 504
 ]
469 505
 
470 506
 @font_hex ( 0-F ) 
... ...
@@ -17,7 +17,6 @@
17 17
 |0100 ;System { vector 2 pad 6 r 2 g 2 b 2 }
18 18
 |0120 ;Screen { vector 2 width 2 height 2 pad 2 x 2 y 2 color 1 }
19 19
 |0130 ;Sprite { vector 2 pad 6 x 2 y 2 addr 2 color 1 }
20
-|0180 ;Audio { ch1adsr 2 ch2adsr 2 ch3adsr 2 ch4adsr 2 ch1vol 1 ch1pitch 1 ch2vol 1 ch2pitch 1 ch3vol 1 ch3pitch 1 ch4vol 1 ch4pitch 1 }
21 20
 |01a0 ;Time { year 2 month 1 day 1 hour 1 minute 1 second 1 dow 1 doy 2 isdst 1 get 1 }
22 21
 
23 22
 ( program )
... ...
@@ -27,12 +26,6 @@
27 26
 	( theme ) #0ff8 =System.r #0f08 =System.g #0f08 =System.b
28 27
 	( vectors ) ,FRAME =Screen.vector
29 28
 
30
-	#1000 =Audio.ch1adsr
31
-	#66 =Audio.ch1vol
32
-
33
-	#0003 =Audio.ch2adsr
34
-	#66 =Audio.ch2vol
35
-
36 29
 BRK
37 30
 
38 31
 @FRAME
... ...
@@ -43,13 +36,6 @@ BRK
43 36
 	~Time.second ~current.second NEQ #01 JNZ BRK
44 37
 	~Time.second =current.second
45 38
 
46
-	( play sounds )
47
-	#0d =Audio.ch1pitch
48
-
49
-	~Time.second #0f MOD #00 NEQ ^$no-tone JNZ
50
-		#0d #02 MUL =Audio.ch2pitch
51
-	$no-tone
52
-
53 39
 	( clear )
54 40
 	#0080 SCALEX #0080 SCALEY ~needles.sx ~needles.sy #00 ,draw-line JSR2
55 41
 	#0080 SCALEX #0080 SCALEY ~needles.mx ~needles.my #00 ,draw-line JSR2
56 42
new file mode 100644
... ...
@@ -0,0 +1,164 @@
1
+#include <SDL2/SDL.h>
2
+#include <stdlib.h>
3
+
4
+/*
5
+Copyright (c) 2021 Devine Lu Linvega
6
+Copyright (c) 2021 Andrew Alderwick
7
+
8
+Permission to use, copy, modify, and distribute this software for any
9
+purpose with or without fee is hereby granted, provided that the above
10
+copyright notice and this permission notice appear in all copies.
11
+
12
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13
+WITH REGARD TO THIS SOFTWARE.
14
+*/
15
+
16
+#include "uxn.h"
17
+
18
+#define SAMPLE_FREQUENCY 48000
19
+
20
+extern SDL_AudioDeviceID audio_id;
21
+int error(char *msg, const char *err);
22
+
23
+static Uint32 note_advances[12] = {
24
+	0x82d01286 / (SAMPLE_FREQUENCY / 30), /* C7 */
25
+	0x8a976073 / (SAMPLE_FREQUENCY / 30),
26
+	0x92d5171d / (SAMPLE_FREQUENCY / 30), /* D7 */
27
+	0x9b904100 / (SAMPLE_FREQUENCY / 30),
28
+	0xa4d053c8 / (SAMPLE_FREQUENCY / 30), /* E7 */
29
+	0xae9d36b0 / (SAMPLE_FREQUENCY / 30), /* F7 */
30
+	0xb8ff493e / (SAMPLE_FREQUENCY / 30),
31
+	0xc3ff6a72 / (SAMPLE_FREQUENCY / 30), /* G7 */
32
+	0xcfa70054 / (SAMPLE_FREQUENCY / 30),
33
+	0xdc000000 / (SAMPLE_FREQUENCY / 30), /* A7 */
34
+	0xe914f623 / (SAMPLE_FREQUENCY / 30),
35
+	0xf6f11003 / (SAMPLE_FREQUENCY / 30) /* B7 */
36
+};
37
+
38
+typedef struct {
39
+	Uint16 *dat;
40
+	Uint8 i, n, sz, ends;
41
+} Queue;
42
+
43
+typedef struct {
44
+	Uint32 count, advance, period;
45
+	Uint16 vector;
46
+	Sint16 start_value, end_value;
47
+	Queue queue;
48
+} WaveformGenerator;
49
+
50
+typedef struct {
51
+	WaveformGenerator wv[2];
52
+	Sint8 volume[2], playing;
53
+} Note;
54
+
55
+static Note *notes = NULL;
56
+static int n_notes = 0;
57
+static Queue *q;
58
+static Uint16 id_addr;
59
+
60
+static void
61
+play_note(Uxn *u, int note_i, Sint16 *samples, int n_samples)
62
+{
63
+	int i;
64
+	Note *note = &notes[note_i];
65
+	while(n_samples--) {
66
+		Sint32 sample = 1;
67
+		for(i = 0; i < 2; ++i) {
68
+			WaveformGenerator *wv = &note->wv[i];
69
+			q = &wv->queue;
70
+			wv->count += wv->advance;
71
+			while(wv->count > wv->period) {
72
+				wv->count -= wv->period;
73
+				wv->start_value = wv->end_value;
74
+				if(q->i == q->n) {
75
+					q->i = q->n = 0;
76
+					if(!q->ends) {
77
+						u->ram.dat[id_addr] = note_i;
78
+						evaluxn(u, wv->vector);
79
+					}
80
+				}
81
+				if(!q->n) {
82
+					note->playing = 0;
83
+					return;
84
+				}
85
+				wv->end_value = (Sint16)q->dat[q->i++];
86
+				wv->period = (30 << 4) * q->dat[q->i++];
87
+			}
88
+			if(wv->period >> 9)
89
+				sample *= wv->start_value + (Sint32)(wv->end_value - wv->start_value) * (Sint32)(wv->count >> 10) / (Sint32)(wv->period >> 10);
90
+			else
91
+				sample *= wv->end_value;
92
+		}
93
+		for(i = 0; i < 2; ++i)
94
+			*(samples++) += sample / 0xf * note->volume[i] / 0x10000;
95
+	}
96
+}
97
+
98
+static void
99
+play_all_notes(void *u, Uint8 *stream, int len)
100
+{
101
+	int i;
102
+	SDL_memset(stream, 0, len);
103
+	for(i = 0; i < n_notes; ++i)
104
+		if(notes[i].playing) play_note(u, i, (Sint16 *)stream, len >> 2);
105
+	q = NULL;
106
+}
107
+
108
+static Note *
109
+get_note(Uint8 i)
110
+{
111
+	if(i >= n_notes) notes = SDL_realloc(notes, (i + 1) * sizeof(Note));
112
+	while(i >= n_notes) SDL_zero(notes[n_notes++]);
113
+	return &notes[i];
114
+}
115
+
116
+static Uint8
117
+audio_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1)
118
+{
119
+	Uint8 *m = u->ram.dat + ptr;
120
+	int i;
121
+	if(b0 == 0xa) {
122
+		Note *note = get_note(b1);
123
+		note->playing = 1;
124
+		for(i = 0; i < 2; ++i) {
125
+			note->volume[i] = 0xf & (m[0x8] >> 4 * (1 - i));
126
+			note->wv[i].vector = (m[0x0 + i * 2] << 8) + m[0x1 + i * 2];
127
+			note->wv[i].count = note->wv[i].period = 0;
128
+			note->wv[i].end_value = 0;
129
+			note->wv[i].queue.n = note->wv[i].queue.i = 0;
130
+			note->wv[i].queue.ends = 0;
131
+		}
132
+		note->wv[0].advance = note_advances[m[0x9] % 12] >> (8 - m[0x9] / 12);
133
+		note->wv[1].advance = (30 << 20) / SAMPLE_FREQUENCY;
134
+	} else if(b0 == 0xe && q != NULL) {
135
+		if(q->n == q->sz) {
136
+			q->sz = q->sz < 4 ? 4 : q->sz * 2;
137
+			q->dat = SDL_realloc(q->dat, q->sz * sizeof(*q->dat));
138
+		}
139
+		q->dat[q->n++] = (m[0xb] << 8) + m[0xc];
140
+		q->dat[q->n++] = (m[0xd] << 8) + b1;
141
+	} else if(b0 == 0xf && q != NULL) {
142
+		q->ends = 1;
143
+		return b1;
144
+	}
145
+}
146
+
147
+int
148
+initapu(Uxn *u, Uint8 id)
149
+{
150
+	SDL_AudioSpec as;
151
+	SDL_zero(as);
152
+	as.freq = SAMPLE_FREQUENCY;
153
+	as.format = AUDIO_S16;
154
+	as.channels = 2;
155
+	as.callback = play_all_notes;
156
+	as.samples = 2048;
157
+	as.userdata = u;
158
+	audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0);
159
+	if(!audio_id)
160
+		return error("Audio", SDL_GetError());
161
+	id_addr = portuxn(u, id, "audio", audio_poke)->addr + 0xa;
162
+	SDL_PauseAudioDevice(audio_id, 0);
163
+	return 1;
164
+}
... ...
@@ -15,6 +15,9 @@ WITH REGARD TO THIS SOFTWARE.
15 15
 
16 16
 #include "uxn.h"
17 17
 
18
+int initapu(Uxn *u, Uint8 id);
19
+void stepapu(Uxn *u);
20
+
18 21
 #define HOR 48
19 22
 #define VER 32
20 23
 #define PAD 2
... ...
@@ -49,38 +52,12 @@ Uint8 font[][8] = {
49 52
 	{0x00, 0x7e, 0x40, 0x7c, 0x40, 0x40, 0x7e, 0x00},
50 53
 	{0x00, 0x7e, 0x40, 0x40, 0x7c, 0x40, 0x40, 0x00}};
51 54
 
52
-#define SAMPLE_FREQUENCY 48000
53
-
54
-static Uint32 note_periods[12] = {
55
-	/* middle C (C4) is note 60 */
56
-	(Uint32)0xfa7e * SAMPLE_FREQUENCY, /* C-1 */
57
-	(Uint32)0xec6f * SAMPLE_FREQUENCY,
58
-	(Uint32)0xdf2a * SAMPLE_FREQUENCY, /* D-1 */
59
-	(Uint32)0xd2a4 * SAMPLE_FREQUENCY,
60
-	(Uint32)0xc6d1 * SAMPLE_FREQUENCY, /* E-1 */
61
-	(Uint32)0xbba8 * SAMPLE_FREQUENCY, /* F-1 */
62
-	(Uint32)0xb120 * SAMPLE_FREQUENCY,
63
-	(Uint32)0xa72f * SAMPLE_FREQUENCY, /* G-1 */
64
-	(Uint32)0x9dcd * SAMPLE_FREQUENCY,
65
-	(Uint32)0x94f2 * SAMPLE_FREQUENCY, /* A-1 */
66
-	(Uint32)0x8c95 * SAMPLE_FREQUENCY,
67
-	(Uint32)0x84b2 * SAMPLE_FREQUENCY /* B-1 */
68
-};
69
-
70
-typedef struct audio_channel {
71
-	Uint32 period, count;
72
-	Sint32 age, a, d, s, r;
73
-	Sint16 value[2];
74
-	Sint8 volume[2], phase;
75
-} Channel;
76
-Channel channels[4];
77
-
78 55
 static SDL_Window *gWindow;
79 56
 static SDL_Renderer *gRenderer;
80 57
 static SDL_Texture *gTexture;
81
-static SDL_AudioDeviceID audio_id;
58
+SDL_AudioDeviceID audio_id;
82 59
 static Screen screen;
83
-static Device *devsystem, *devscreen, *devmouse, *devkey, *devctrl, *devaudio;
60
+static Device *devsystem, *devscreen, *devmouse, *devkey, *devctrl;
84 61
 
85 62
 #pragma mark - Helpers
86 63
 
... ...
@@ -241,61 +218,6 @@ togglezoom(Uxn *u)
241 218
 	redraw(pixels, u);
242 219
 }
243 220
 
244
-Sint16
245
-audio_envelope(Channel *c)
246
-{
247
-	if(c->age < c->a)
248
-		return 0x0888 * c->age / c->a;
249
-	else if(c->age < c->d)
250
-		return 0x0444 * (2 * c->d - c->a - c->age) / (c->d - c->a);
251
-	else if(c->age < c->s)
252
-		return 0x0444;
253
-	else if(c->age < c->r)
254
-		return 0x0444 * (c->r - c->age) / (c->r - c->s);
255
-	else
256
-		return 0x0000;
257
-}
258
-
259
-void
260
-audio_callback(void *userdata, Uint8 *stream, int len)
261
-{
262
-	Sint16 *samples = (Sint16 *)stream;
263
-	int i, j;
264
-	len >>= 2; /* use len for number of samples, not bytes */
265
-	for(j = len * 2 - 1; j >= 0; --j) samples[j] = 0;
266
-	for(i = 0; i < 4; ++i) {
267
-		Channel *c = &channels[i];
268
-		if(c->period < (1 << 20)) continue;
269
-		for(j = 0; j < len; ++j) {
270
-			c->age += 1;
271
-			c->count += 1 << 20;
272
-			while(c->count > c->period) {
273
-				Sint16 mul;
274
-				c->count -= c->period;
275
-				c->phase = !c->phase;
276
-				mul = (c->phase * 2 - 1) * audio_envelope(c);
277
-				c->value[0] = mul * c->volume[0];
278
-				c->value[1] = mul * c->volume[1];
279
-			}
280
-			samples[j * 2] += c->value[0];
281
-			samples[j * 2 + 1] += c->value[1];
282
-		}
283
-	}
284
-	(void)userdata;
285
-}
286
-
287
-void
288
-silence(void)
289
-{
290
-	int i;
291
-	for(i = 0; i < 4; ++i) {
292
-		Channel *c = &channels[i];
293
-		c->volume[0] = 0;
294
-		c->volume[1] = 0;
295
-		c->period = 0;
296
-	}
297
-}
298
-
299 221
 void
300 222
 quit(void)
301 223
 {
... ...
@@ -313,7 +235,6 @@ quit(void)
313 235
 int
314 236
 init(void)
315 237
 {
316
-	SDL_AudioSpec as;
317 238
 	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
318 239
 		return error("Init", SDL_GetError());
319 240
 	gWindow = SDL_CreateWindow("Uxn", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH * ZOOM, HEIGHT * ZOOM, SDL_WINDOW_SHOWN);
... ...
@@ -328,18 +249,8 @@ init(void)
328 249
 	if(!(pixels = (Uint32 *)malloc(WIDTH * HEIGHT * sizeof(Uint32))))
329 250
 		return error("Pixels", "Failed to allocate memory");
330 251
 	clear(pixels);
331
-	silence();
332 252
 	SDL_StartTextInput();
333 253
 	SDL_ShowCursor(SDL_DISABLE);
334
-	as.freq = SAMPLE_FREQUENCY;
335
-	as.format = AUDIO_S16;
336
-	as.channels = 2;
337
-	as.callback = audio_callback;
338
-	as.samples = 2048;
339
-	audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0);
340
-	if(!audio_id)
341
-		return error("Audio", SDL_GetError());
342
-	SDL_PauseAudioDevice(audio_id, 0);
343 254
 	screen.x1 = PAD * 8;
344 255
 	screen.x2 = WIDTH - PAD * 8 - 1;
345 256
 	screen.y1 = PAD * 8;
... ...
@@ -504,29 +415,6 @@ file_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1)
504 415
 	return b1;
505 416
 }
506 417
 
507
-Uint8
508
-audio_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1)
509
-{
510
-	Uint8 *m = u->ram.dat;
511
-	m[PAGE_DEVICE + 0x0070 + b0] = b1;
512
-	if(b0 > 0x08 && b0 & 1) {
513
-		Uint16 addr = ptr + (b0 & 0x6);
514
-		Channel *c = &channels[(b0 & 0x6) >> 1];
515
-		SDL_LockAudioDevice(audio_id);
516
-		c->period = note_periods[m[addr + 9] % 12] >> (m[addr + 9] / 12);
517
-		c->count %= c->period;
518
-		c->volume[0] = (m[addr + 8] >> 4) & 0xf;
519
-		c->volume[1] = m[addr + 8] & 0xf;
520
-		c->age = 0;
521
-		c->a = (SAMPLE_FREQUENCY >> 4) * ((m[addr] >> 4) & 0xf);
522
-		c->d = c->a + (SAMPLE_FREQUENCY >> 4) * (m[addr] & 0xf);
523
-		c->s = c->d + (SAMPLE_FREQUENCY >> 4) * ((m[addr + 1] >> 4) & 0xf);
524
-		c->r = c->s + (SAMPLE_FREQUENCY >> 4) * (m[addr + 1] & 0xf);
525
-		SDL_UnlockAudioDevice(audio_id);
526
-	}
527
-	return b1;
528
-}
529
-
530 418
 Uint8
531 419
 midi_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1)
532 420
 {
... ...
@@ -586,9 +474,13 @@ start(Uxn *u)
586 474
 	while(1) {
587 475
 		SDL_Event event;
588 476
 		double elapsed, start = SDL_GetPerformanceCounter();
477
+		SDL_LockAudioDevice(audio_id);
589 478
 		while(SDL_PollEvent(&event) != 0) {
590 479
 			switch(event.type) {
591
-			case SDL_QUIT: quit(); break;
480
+			case SDL_QUIT:
481
+				SDL_UnlockAudioDevice(audio_id);
482
+				quit();
483
+				break;
592 484
 			case SDL_MOUSEBUTTONUP:
593 485
 			case SDL_MOUSEBUTTONDOWN:
594 486
 			case SDL_MOUSEMOTION:
... ...
@@ -611,6 +503,7 @@ start(Uxn *u)
611 503
 			}
612 504
 		}
613 505
 		evaluxn(u, devscreen->vector);
506
+		SDL_UnlockAudioDevice(audio_id);
614 507
 		if(screen.reqdraw)
615 508
 			redraw(pixels, u);
616 509
 		elapsed = (SDL_GetPerformanceCounter() - start) / (double)SDL_GetPerformanceFrequency() * 1000.0f;
... ...
@@ -641,7 +534,8 @@ main(int argc, char **argv)
641 534
 	devkey = portuxn(&u, 0x05, "key", ppnil);
642 535
 	devmouse = portuxn(&u, 0x06, "mouse", ppnil);
643 536
 	portuxn(&u, 0x07, "file", file_poke);
644
-	devaudio = portuxn(&u, 0x08, "audio", audio_poke);
537
+	if(!initapu(&u, 0x08))
538
+		return 1;
645 539
 	portuxn(&u, 0x09, "midi", ppnil);
646 540
 	portuxn(&u, 0x0a, "datetime", datetime_poke);
647 541
 	portuxn(&u, 0x0b, "---", ppnil);