| 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 |
+ |