Monorepo for Aesthetic.Computer aesthetic.computer
at main 314 lines 9.2 kB view raw
1#include "udp-client.h" 2 3#include <stdint.h> 4#include <stdio.h> 5#include <stdlib.h> 6#include <string.h> 7#include <unistd.h> 8#include <errno.h> 9#include <netdb.h> 10#include <sys/socket.h> 11#include <arpa/inet.h> 12#include <fcntl.h> 13#include <poll.h> 14#include <time.h> 15 16extern void ac_log(const char *fmt, ...); 17 18// Packet format (binary, little-endian): 19// [1 byte type] [payload...] 20// Type 0x01 = fairy point: 21// [1] [4 float x] [4 float y] [1 handle_len] [N handle_bytes] 22// Type 0x02 = fairy broadcast (from server): 23// [1] [4 float x] [4 float y] [1 handle_len] [N handle_bytes] 24 25#define PKT_FAIRY_SEND 0x01 26#define PKT_FAIRY_RECV 0x02 27 28static uint64_t udp_now_ms(void) { 29 struct timespec ts; 30 clock_gettime(CLOCK_REALTIME, &ts); 31 return (uint64_t)ts.tv_sec * 1000ULL + (uint64_t)ts.tv_nsec / 1000000ULL; 32} 33 34static void json_escape_copy(char *dst, size_t dst_size, const char *src) { 35 size_t out = 0; 36 37 if (!dst || dst_size == 0) return; 38 if (!src) src = ""; 39 40 while (*src && out + 1 < dst_size) { 41 unsigned char c = (unsigned char)*src++; 42 if (c == '"' || c == '\\') { 43 if (out + 2 >= dst_size) break; 44 dst[out++] = '\\'; 45 dst[out++] = (char)c; 46 } else if (c >= 32 && c < 127) { 47 dst[out++] = (char)c; 48 } 49 } 50 51 dst[out] = '\0'; 52} 53 54static int udp_snapshot_identity( 55 ACUdp *udp, 56 int *sock_out, 57 struct sockaddr_in *addr_out, 58 char *handle_out, 59 size_t handle_out_size, 60 char *machine_out, 61 size_t machine_out_size 62) { 63 int sock = -1; 64 int connected = 0; 65 66 if (!udp) return 0; 67 68 pthread_mutex_lock(&udp->mu); 69 sock = udp->sock; 70 connected = udp->connected; 71 if (addr_out) *addr_out = udp->server_addr; 72 if (handle_out && handle_out_size > 0) { 73 strncpy(handle_out, udp->handle, handle_out_size - 1); 74 handle_out[handle_out_size - 1] = 0; 75 } 76 if (machine_out && machine_out_size > 0) { 77 strncpy(machine_out, udp->machine_id, machine_out_size - 1); 78 machine_out[machine_out_size - 1] = 0; 79 } 80 pthread_mutex_unlock(&udp->mu); 81 82 if (sock_out) *sock_out = sock; 83 return connected && sock >= 0; 84} 85 86static void udp_send_json(ACUdp *udp, const char *json) { 87 int sock = -1; 88 struct sockaddr_in server_addr; 89 90 if (!udp || !json || !json[0]) return; 91 if (!udp_snapshot_identity(udp, &sock, &server_addr, NULL, 0, NULL, 0)) return; 92 93 ssize_t sent = sendto( 94 sock, 95 json, 96 strlen(json), 97 0, 98 (struct sockaddr *)&server_addr, 99 sizeof(server_addr) 100 ); 101 102 if (sent < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { 103 ac_log("[udp] json send failed: %s\n", strerror(errno)); 104 } 105} 106 107static void *udp_thread(void *arg) { 108 ACUdp *udp = (ACUdp *)arg; 109 unsigned char buf[256]; 110 111 while (udp->thread_running) { 112 if (udp->sock < 0) { 113 usleep(100000); // 100ms 114 continue; 115 } 116 117 // Send pending fairy point 118 pthread_mutex_lock(&udp->mu); 119 int do_send = udp->send_pending; 120 float sx = udp->send_x, sy = udp->send_y; 121 char handle[64]; 122 strncpy(handle, udp->handle, sizeof(handle) - 1); 123 handle[sizeof(handle) - 1] = 0; 124 udp->send_pending = 0; 125 pthread_mutex_unlock(&udp->mu); 126 127 if (do_send && udp->connected) { 128 int hlen = (int)strlen(handle); 129 unsigned char pkt[128]; 130 pkt[0] = PKT_FAIRY_SEND; 131 memcpy(pkt + 1, &sx, 4); 132 memcpy(pkt + 5, &sy, 4); 133 pkt[9] = (unsigned char)hlen; 134 memcpy(pkt + 10, handle, hlen); 135 int pkt_len = 10 + hlen; 136 sendto(udp->sock, pkt, pkt_len, 0, 137 (struct sockaddr *)&udp->server_addr, 138 sizeof(udp->server_addr)); 139 } 140 141 // Recv with short timeout 142 struct pollfd pfd = { .fd = udp->sock, .events = POLLIN }; 143 int ret = poll(&pfd, 1, 16); // 16ms = ~60Hz 144 if (ret > 0 && (pfd.revents & POLLIN)) { 145 ssize_t n = recvfrom(udp->sock, buf, sizeof(buf), 0, NULL, NULL); 146 if (n > 0 && buf[0] == PKT_FAIRY_RECV && n >= 10) { 147 float fx, fy; 148 memcpy(&fx, buf + 1, 4); 149 memcpy(&fy, buf + 5, 4); 150 151 pthread_mutex_lock(&udp->mu); 152 if (udp->fairy_count < UDP_MAX_FAIRIES) { 153 udp->fairies[udp->fairy_count].x = fx; 154 udp->fairies[udp->fairy_count].y = fy; 155 udp->fairy_count++; 156 } 157 pthread_mutex_unlock(&udp->mu); 158 } 159 } 160 } 161 return NULL; 162} 163 164ACUdp *udp_create(void) { 165 ACUdp *udp = calloc(1, sizeof(ACUdp)); 166 udp->sock = -1; 167 pthread_mutex_init(&udp->mu, NULL); 168 udp->thread_running = 1; 169 pthread_create(&udp->thread, NULL, udp_thread, udp); 170 return udp; 171} 172 173void udp_destroy(ACUdp *udp) { 174 if (!udp) return; 175 udp->thread_running = 0; 176 pthread_join(udp->thread, NULL); 177 if (udp->sock >= 0) close(udp->sock); 178 pthread_mutex_destroy(&udp->mu); 179 free(udp); 180} 181 182void udp_connect(ACUdp *udp, const char *host, int port) { 183 if (!udp) return; 184 185 // Resolve hostname 186 struct hostent *he = gethostbyname(host); 187 if (!he) { 188 ac_log("[udp] DNS resolve failed for %s\n", host); 189 return; 190 } 191 192 memset(&udp->server_addr, 0, sizeof(udp->server_addr)); 193 udp->server_addr.sin_family = AF_INET; 194 udp->server_addr.sin_port = htons(port); 195 memcpy(&udp->server_addr.sin_addr, he->h_addr_list[0], he->h_length); 196 197 // Create socket 198 int fd = socket(AF_INET, SOCK_DGRAM, 0); 199 if (fd < 0) { 200 ac_log("[udp] socket() failed: %s\n", strerror(errno)); 201 return; 202 } 203 204 // Non-blocking 205 int flags = fcntl(fd, F_GETFL, 0); 206 fcntl(fd, F_SETFL, flags | O_NONBLOCK); 207 208 pthread_mutex_lock(&udp->mu); 209 udp->sock = fd; 210 udp->connected = 1; 211 pthread_mutex_unlock(&udp->mu); 212 213 ac_log("[udp] connected to %s:%d\n", host, port); 214} 215 216void udp_send_fairy(ACUdp *udp, float x, float y) { 217 if (!udp) return; 218 pthread_mutex_lock(&udp->mu); 219 udp->send_x = x; 220 udp->send_y = y; 221 udp->send_pending = 1; 222 pthread_mutex_unlock(&udp->mu); 223} 224 225void udp_set_identity(ACUdp *udp, const char *handle, const char *machine_id) { 226 if (!udp) return; 227 pthread_mutex_lock(&udp->mu); 228 if (handle) { 229 strncpy(udp->handle, handle, sizeof(udp->handle) - 1); 230 udp->handle[sizeof(udp->handle) - 1] = 0; 231 } 232 if (machine_id) { 233 strncpy(udp->machine_id, machine_id, sizeof(udp->machine_id) - 1); 234 udp->machine_id[sizeof(udp->machine_id) - 1] = 0; 235 } 236 pthread_mutex_unlock(&udp->mu); 237} 238 239void udp_send_midi(ACUdp *udp, const char *event, int note, int velocity, int channel, const char *piece) { 240 char handle[64] = ""; 241 char machine_id[64] = ""; 242 char handle_json[128]; 243 char machine_json[128]; 244 char piece_json[64]; 245 char event_json[32]; 246 char json[512]; 247 248 if (!udp || !event || !piece) return; 249 if (!udp_snapshot_identity(udp, NULL, NULL, handle, sizeof(handle), machine_id, sizeof(machine_id))) return; 250 251 json_escape_copy(handle_json, sizeof(handle_json), handle); 252 json_escape_copy(machine_json, sizeof(machine_json), machine_id[0] ? machine_id : "unknown"); 253 json_escape_copy(piece_json, sizeof(piece_json), piece); 254 json_escape_copy(event_json, sizeof(event_json), event); 255 256 snprintf( 257 json, 258 sizeof(json), 259 "{\"type\":\"notepat:midi\",\"event\":\"%s\",\"note\":%d,\"velocity\":%d," 260 "\"channel\":%d,\"handle\":\"%s\",\"machineId\":\"%s\",\"piece\":\"%s\",\"ts\":%llu}", 261 event_json, 262 note, 263 velocity, 264 channel, 265 handle_json, 266 machine_json, 267 piece_json, 268 (unsigned long long)udp_now_ms() 269 ); 270 271 udp_send_json(udp, json); 272} 273 274void udp_send_midi_heartbeat(ACUdp *udp, const char *piece) { 275 char handle[64] = ""; 276 char machine_id[64] = ""; 277 char handle_json[128]; 278 char machine_json[128]; 279 char piece_json[64]; 280 char json[512]; 281 282 if (!udp || !piece) return; 283 if (!udp_snapshot_identity(udp, NULL, NULL, handle, sizeof(handle), machine_id, sizeof(machine_id))) return; 284 285 json_escape_copy(handle_json, sizeof(handle_json), handle); 286 json_escape_copy(machine_json, sizeof(machine_json), machine_id[0] ? machine_id : "unknown"); 287 json_escape_copy(piece_json, sizeof(piece_json), piece); 288 289 snprintf( 290 json, 291 sizeof(json), 292 "{\"type\":\"notepat:midi:heartbeat\",\"handle\":\"%s\",\"machineId\":\"%s\"," 293 "\"piece\":\"%s\",\"broadcast\":true,\"ts\":%llu}", 294 handle_json, 295 machine_json, 296 piece_json, 297 (unsigned long long)udp_now_ms() 298 ); 299 300 udp_send_json(udp, json); 301} 302 303int udp_poll_fairies(ACUdp *udp, UDPFairy *out, int max) { 304 if (!udp) return 0; 305 pthread_mutex_lock(&udp->mu); 306 int count = udp->fairy_count; 307 if (count > max) count = max; 308 if (count > 0) { 309 memcpy(out, udp->fairies, count * sizeof(UDPFairy)); 310 udp->fairy_count = 0; 311 } 312 pthread_mutex_unlock(&udp->mu); 313 return count; 314}