uxn

Varvara Ordinator, written in ANSI C(SDL2)
git clone https://git.eamoncaddigan.net/uxn.git
Log | Files | Refs | README | LICENSE

main.c (6965B)


      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 }