0 | 12 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,332 @@ |
1 |
+#define _GNU_SOURCE |
|
2 |
+ |
|
3 |
+#include <errno.h> |
|
4 |
+#include <fcntl.h> |
|
5 |
+#include <math.h> |
|
6 |
+#include <signal.h> |
|
7 |
+#include <stdarg.h> |
|
8 |
+#include <stdio.h> |
|
9 |
+#include <stdlib.h> |
|
10 |
+#include <string.h> |
|
11 |
+#include <sys/select.h> |
|
12 |
+#include <sys/stat.h> |
|
13 |
+#include <sys/time.h> |
|
14 |
+#include <sys/types.h> |
|
15 |
+#include <sys/wait.h> |
|
16 |
+#include <unistd.h> |
|
17 |
+ |
|
18 |
+#define WIDTH 512 |
|
19 |
+#define HEIGHT 320 |
|
20 |
+ |
|
21 |
+#define str(x) stra(x) |
|
22 |
+#define stra(x) #x |
|
23 |
+ |
|
24 |
+#define die(fnname) \ |
|
25 |
+ do { \ |
|
26 |
+ perror(fnname); \ |
|
27 |
+ exit(EXIT_FAILURE); \ |
|
28 |
+ } while(0) |
|
29 |
+ |
|
30 |
+#define x(fn, ...) \ |
|
31 |
+ do { \ |
|
32 |
+ if(fn(__VA_ARGS__) < 0) { \ |
|
33 |
+ perror(#fn); \ |
|
34 |
+ exit(EXIT_FAILURE); \ |
|
35 |
+ } \ |
|
36 |
+ } while(0) |
|
37 |
+ |
|
38 |
+int fix_fft(short *fr, short *fi, short m, short inverse); |
|
39 |
+ |
|
40 |
+static pid_t |
|
41 |
+launch_xvfb(void) |
|
42 |
+{ |
|
43 |
+ char displayfd[16]; |
|
44 |
+ int r; |
|
45 |
+ pid_t pid; |
|
46 |
+ int fds[2]; |
|
47 |
+ x(pipe2, &fds[0], 0); |
|
48 |
+ pid = fork(); |
|
49 |
+ if(pid < 0) { |
|
50 |
+ die("fork"); |
|
51 |
+ } else if(!pid) { |
|
52 |
+ x(snprintf, displayfd, sizeof(displayfd), "%d", fds[1]); |
|
53 |
+ x(close, fds[0]); |
|
54 |
+ execlp("Xvfb", "Xvfb", "-screen", "0", str(WIDTH) "x" str(HEIGHT) "x24", "-fbdir", ".", "-displayfd", displayfd, "-nolisten", "tcp", NULL); |
|
55 |
+ die("execl"); |
|
56 |
+ exit(EXIT_FAILURE); |
|
57 |
+ } |
|
58 |
+ x(close, fds[1]); |
|
59 |
+ r = read(fds[0], &displayfd[1], sizeof(displayfd) - 1); |
|
60 |
+ if(r < 0) die("read"); |
|
61 |
+ x(close, fds[0]); |
|
62 |
+ displayfd[r] = '\0'; |
|
63 |
+ displayfd[0] = ':'; |
|
64 |
+ x(setenv, "DISPLAY", displayfd, 1); |
|
65 |
+ x(setenv, "ALSA_CONFIG_PATH", "asoundrc", 1); |
|
66 |
+ return pid; |
|
67 |
+} |
|
68 |
+ |
|
69 |
+static pid_t |
|
70 |
+launch_uxnemu(int *write_fd, int *read_fd, int *sound_fd) |
|
71 |
+{ |
|
72 |
+ pid_t pid; |
|
73 |
+ int fds[6]; |
|
74 |
+ x(pipe2, &fds[0], O_CLOEXEC); |
|
75 |
+ x(pipe2, &fds[2], O_CLOEXEC); |
|
76 |
+ x(pipe2, &fds[4], O_CLOEXEC); |
|
77 |
+ pid = fork(); |
|
78 |
+ if(pid < 0) { |
|
79 |
+ die("fork"); |
|
80 |
+ } else if(!pid) { |
|
81 |
+ x(dup2, fds[0], 0); |
|
82 |
+ x(dup2, fds[3], 1); |
|
83 |
+ x(dup2, fds[5], 3); |
|
84 |
+ execl("../../bin/uxnemu", "uxnemu", "autotest.rom", NULL); |
|
85 |
+ die("execl"); |
|
86 |
+ } |
|
87 |
+ x(close, fds[0]); |
|
88 |
+ x(close, fds[3]); |
|
89 |
+ x(close, fds[5]); |
|
90 |
+ *write_fd = fds[1]; |
|
91 |
+ *read_fd = fds[2]; |
|
92 |
+ *sound_fd = fds[4]; |
|
93 |
+ return pid; |
|
94 |
+} |
|
95 |
+ |
|
96 |
+static void |
|
97 |
+terminate(pid_t pid) |
|
98 |
+{ |
|
99 |
+ int signals[] = {SIGINT, SIGTERM, SIGKILL}; |
|
100 |
+ int status; |
|
101 |
+ size_t i; |
|
102 |
+ for(i = 0; i < sizeof(signals) / sizeof(int) * 10; ++i) { |
|
103 |
+ if(kill(pid, signals[i / 10])) { |
|
104 |
+ break; |
|
105 |
+ } |
|
106 |
+ usleep(100000); |
|
107 |
+ if(pid == waitpid(pid, &status, WNOHANG)) { |
|
108 |
+ return; |
|
109 |
+ } |
|
110 |
+ } |
|
111 |
+ waitpid(pid, &status, 0); |
|
112 |
+} |
|
113 |
+ |
|
114 |
+static int |
|
115 |
+open_framebuffer(void) |
|
116 |
+{ |
|
117 |
+ for(;;) { |
|
118 |
+ int fd = open("Xvfb_screen0", O_RDONLY | O_CLOEXEC); |
|
119 |
+ if(fd >= 0) { |
|
120 |
+ return fd; |
|
121 |
+ } |
|
122 |
+ if(errno != ENOENT) { |
|
123 |
+ perror("open"); |
|
124 |
+ return fd; |
|
125 |
+ } |
|
126 |
+ usleep(100000); |
|
127 |
+ } |
|
128 |
+} |
|
129 |
+ |
|
130 |
+#define PPM_HEADER "P6\n" str(WIDTH) " " str(HEIGHT) "\n255\n" |
|
131 |
+ |
|
132 |
+static void |
|
133 |
+save_screenshot(int fb_fd, const char *filename) |
|
134 |
+{ |
|
135 |
+ unsigned char screen[WIDTH * HEIGHT * 4 + 4]; |
|
136 |
+ int fd = open(filename, O_WRONLY | O_CREAT, 0666); |
|
137 |
+ int i; |
|
138 |
+ if(fd < 0) { |
|
139 |
+ die("screenshot open"); |
|
140 |
+ } |
|
141 |
+ x(write, fd, PPM_HEADER, strlen(PPM_HEADER)); |
|
142 |
+ x(lseek, fb_fd, 0xca0, SEEK_SET); |
|
143 |
+ x(read, fb_fd, &screen[4], WIDTH * HEIGHT * 4); |
|
144 |
+ for(i = 0; i < WIDTH * HEIGHT; ++i) { |
|
145 |
+ screen[i * 3 + 2] = screen[i * 4 + 4]; |
|
146 |
+ screen[i * 3 + 1] = screen[i * 4 + 5]; |
|
147 |
+ screen[i * 3 + 0] = screen[i * 4 + 6]; |
|
148 |
+ } |
|
149 |
+ x(write, fd, screen, WIDTH * HEIGHT * 3); |
|
150 |
+ x(close, fd); |
|
151 |
+} |
|
152 |
+ |
|
153 |
+static void |
|
154 |
+systemf(char *format, ...) |
|
155 |
+{ |
|
156 |
+ char *command; |
|
157 |
+ va_list ap; |
|
158 |
+ va_start(ap, format); |
|
159 |
+ x(vasprintf, &command, format, ap); |
|
160 |
+ system(command); |
|
161 |
+ free(command); |
|
162 |
+} |
|
163 |
+ |
|
164 |
+int uxn_read_fd, sound_fd; |
|
165 |
+ |
|
166 |
+static int |
|
167 |
+byte(void) |
|
168 |
+{ |
|
169 |
+ char c; |
|
170 |
+ if(read(uxn_read_fd, &c, 1) != 1) { |
|
171 |
+ return 0; |
|
172 |
+ } |
|
173 |
+ return (unsigned char)c; |
|
174 |
+} |
|
175 |
+ |
|
176 |
+#define NEW_FFT_SIZE_POW2 10 |
|
177 |
+#define NEW_FFT_SIZE (1 << NEW_FFT_SIZE_POW2) |
|
178 |
+#define NEW_FFT_USEC (5000 * NEW_FFT_SIZE / 441) |
|
179 |
+ |
|
180 |
+unsigned char left_peak, right_peak; |
|
181 |
+ |
|
182 |
+static int |
|
183 |
+detect_peak(short *real, short *imag) |
|
184 |
+{ |
|
185 |
+ int i, peak = 0, peak_i; |
|
186 |
+ for(i = 0; i < NEW_FFT_SIZE; ++i) { |
|
187 |
+ int v = real[i] * real[i] + imag[i] * imag[i]; |
|
188 |
+ if(peak < v) { |
|
189 |
+ peak = v; |
|
190 |
+ peak_i = i; |
|
191 |
+ } else if(peak > v * 10) { |
|
192 |
+ return peak_i; |
|
193 |
+ } |
|
194 |
+ } |
|
195 |
+ return 0; |
|
196 |
+} |
|
197 |
+ |
|
198 |
+static int |
|
199 |
+analyse_sound(short *samples) |
|
200 |
+{ |
|
201 |
+ short real[NEW_FFT_SIZE], imag[NEW_FFT_SIZE]; |
|
202 |
+ int i; |
|
203 |
+ for(i = 0; i < NEW_FFT_SIZE * 2; ++i) { |
|
204 |
+ if(samples[i * 2]) break; |
|
205 |
+ } |
|
206 |
+ if(i == NEW_FFT_SIZE * 2) return 0; |
|
207 |
+ for(i = 0; i < NEW_FFT_SIZE; ++i) { |
|
208 |
+ real[i] = samples[i * 4]; |
|
209 |
+ imag[i] = samples[i * 4 + 2]; |
|
210 |
+ } |
|
211 |
+ fix_fft(real, imag, NEW_FFT_SIZE_POW2, 0); |
|
212 |
+ return detect_peak(real, imag); |
|
213 |
+} |
|
214 |
+ |
|
215 |
+static int |
|
216 |
+read_sound(void) |
|
217 |
+{ |
|
218 |
+ static short samples[NEW_FFT_SIZE * 4]; |
|
219 |
+ static size_t len = 0; |
|
220 |
+ int r = read(sound_fd, ((char *)samples) + len, sizeof(samples) - len); |
|
221 |
+ if(r > 0) { |
|
222 |
+ len += r; |
|
223 |
+ if(len == sizeof(samples)) { |
|
224 |
+ left_peak = analyse_sound(&samples[0]); |
|
225 |
+ right_peak = analyse_sound(&samples[1]); |
|
226 |
+ len = 0; |
|
227 |
+ return 1; |
|
228 |
+ } |
|
229 |
+ } |
|
230 |
+ return 0; |
|
231 |
+} |
|
232 |
+ |
|
233 |
+static void |
|
234 |
+main_loop(int uxn_write_fd, int fb_fd) |
|
235 |
+{ |
|
236 |
+ struct timeval next_sound = {0, 0}; |
|
237 |
+ for(;;) { |
|
238 |
+ struct timeval now; |
|
239 |
+ struct timeval *timeout; |
|
240 |
+ fd_set fds; |
|
241 |
+ FD_ZERO(&fds); |
|
242 |
+ FD_SET(uxn_read_fd, &fds); |
|
243 |
+ x(gettimeofday, &now, NULL); |
|
244 |
+ if(now.tv_sec > next_sound.tv_sec || (now.tv_sec == next_sound.tv_sec && now.tv_usec > next_sound.tv_usec)) { |
|
245 |
+ FD_SET(sound_fd, &fds); |
|
246 |
+ timeout = NULL; |
|
247 |
+ } else { |
|
248 |
+ now.tv_sec = 0; |
|
249 |
+ now.tv_usec = NEW_FFT_USEC; |
|
250 |
+ timeout = &now; |
|
251 |
+ } |
|
252 |
+ x(select, uxn_read_fd > sound_fd ? uxn_read_fd + 1 : sound_fd + 1, &fds, NULL, NULL, timeout); |
|
253 |
+ if(FD_ISSET(uxn_read_fd, &fds)) { |
|
254 |
+ int c, x, y; |
|
255 |
+ unsigned char blue; |
|
256 |
+ switch(c = byte()) { |
|
257 |
+ case 0x00: /* also used for EOF */ |
|
258 |
+ printf("exiting\n"); |
|
259 |
+ return; |
|
260 |
+ /* 01-06 mouse */ |
|
261 |
+ case 0x01 ... 0x05: |
|
262 |
+ systemf("xdotool click %d", c); |
|
263 |
+ break; |
|
264 |
+ case 0x06: |
|
265 |
+ x = (byte() << 8) | byte(); |
|
266 |
+ y = (byte() << 8) | byte(); |
|
267 |
+ systemf("xdotool mousemove %d %d", x, y); |
|
268 |
+ break; |
|
269 |
+ /* 07-08 Screen */ |
|
270 |
+ case 0x07: |
|
271 |
+ x = (byte() << 8) | byte(); |
|
272 |
+ y = (byte() << 8) | byte(); |
|
273 |
+ lseek(fb_fd, 0xca0 + (x + y * WIDTH) * 4, SEEK_SET); |
|
274 |
+ read(fb_fd, &blue, 1); |
|
275 |
+ blue = blue / 0x11; |
|
276 |
+ write(uxn_write_fd, &blue, 1); |
|
277 |
+ break; |
|
278 |
+ case 0x08: |
|
279 |
+ save_screenshot(fb_fd, "test.ppm"); |
|
280 |
+ break; |
|
281 |
+ /* 09-0a Audio */ |
|
282 |
+ case 0x09: |
|
283 |
+ write(uxn_write_fd, &left_peak, 1); |
|
284 |
+ break; |
|
285 |
+ case 0x0a: |
|
286 |
+ write(uxn_write_fd, &right_peak, 1); |
|
287 |
+ break; |
|
288 |
+ /* 11-7e Controller/key */ |
|
289 |
+ case 0x11 ... 0x1c: |
|
290 |
+ systemf("xdotool key F%d", c - 0x10); |
|
291 |
+ break; |
|
292 |
+ case '0' ... '9': |
|
293 |
+ case 'A' ... 'Z': |
|
294 |
+ case 'a' ... 'z': |
|
295 |
+ systemf("xdotool key %c", c); |
|
296 |
+ break; |
|
297 |
+ default: |
|
298 |
+ printf("unhandled command 0x%02x\n", c); |
|
299 |
+ break; |
|
300 |
+ } |
|
301 |
+ } |
|
302 |
+ if(FD_ISSET(sound_fd, &fds)) { |
|
303 |
+ if(!next_sound.tv_sec) { |
|
304 |
+ x(gettimeofday, &next_sound, NULL); |
|
305 |
+ } |
|
306 |
+ next_sound.tv_usec += NEW_FFT_USEC * read_sound(); |
|
307 |
+ if(next_sound.tv_usec > 1000000) { |
|
308 |
+ next_sound.tv_usec -= 1000000; |
|
309 |
+ ++next_sound.tv_sec; |
|
310 |
+ } |
|
311 |
+ } |
|
312 |
+ } |
|
313 |
+} |
|
314 |
+ |
|
315 |
+int |
|
316 |
+main(void) |
|
317 |
+{ |
|
318 |
+ pid_t xvfb_pid = launch_xvfb(); |
|
319 |
+ int fb_fd = open_framebuffer(); |
|
320 |
+ if(fb_fd >= 0) { |
|
321 |
+ int uxn_write_fd; |
|
322 |
+ pid_t uxnemu_pid = launch_uxnemu(&uxn_write_fd, &uxn_read_fd, &sound_fd); |
|
323 |
+ main_loop(uxn_write_fd, fb_fd); |
|
324 |
+ terminate(uxnemu_pid); |
|
325 |
+ x(close, uxn_write_fd); |
|
326 |
+ x(close, uxn_read_fd); |
|
327 |
+ x(close, sound_fd); |
|
328 |
+ x(close, fb_fd); |
|
329 |
+ } |
|
330 |
+ terminate(xvfb_pid); |
|
331 |
+ return 0; |
|
332 |
+} |
0 | 333 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,20 @@ |
1 |
+#!/bin/sh -e |
|
2 |
+cd "$(dirname "${0}")" |
|
3 |
+if ! which Xvfb 2>/dev/null; then |
|
4 |
+ echo "error: ${0} depends on Xvfb" |
|
5 |
+ exit 1 |
|
6 |
+fi |
|
7 |
+if ! which xdotool 2>/dev/null; then |
|
8 |
+ echo "error: ${0} depends on xdotool" |
|
9 |
+ exit 1 |
|
10 |
+fi |
|
11 |
+if [ ! -e fix_fft.c ]; then |
|
12 |
+ wget https://gist.githubusercontent.com/Tomwi/3842231/raw/67149b6ec81cfb6ac1056fd23a3bb6ce1f0a5188/fix_fft.c |
|
13 |
+fi |
|
14 |
+if which clang-format 2>/dev/null; then |
|
15 |
+ ( cd ../.. && clang-format -i etc/autotest/main.c ) |
|
16 |
+fi |
|
17 |
+../../bin/uxnasm autotest.tal autotest.rom |
|
18 |
+gcc -std=gnu89 -Wall -Wextra -o autotest main.c fix_fft.c -lm |
|
19 |
+./autotest |
|
20 |
+ |