Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315#include "udp-client.h"
#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <netdb.h>#include <sys/socket.h>#include <arpa/inet.h>#include <fcntl.h>#include <poll.h>#include <time.h>
extern void ac_log(const char *fmt, ...);
// Packet format (binary, little-endian):// [1 byte type] [payload...]// Type 0x01 = fairy point:// [1] [4 float x] [4 float y] [1 handle_len] [N handle_bytes]// Type 0x02 = fairy broadcast (from server):// [1] [4 float x] [4 float y] [1 handle_len] [N handle_bytes]
#define PKT_FAIRY_SEND 0x01#define PKT_FAIRY_RECV 0x02
static uint64_t udp_now_ms(void) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); return (uint64_t)ts.tv_sec * 1000ULL + (uint64_t)ts.tv_nsec / 1000000ULL;}
static void json_escape_copy(char *dst, size_t dst_size, const char *src) { size_t out = 0;
if (!dst || dst_size == 0) return; if (!src) src = "";
while (*src && out + 1 < dst_size) { unsigned char c = (unsigned char)*src++; if (c == '"' || c == '\\') { if (out + 2 >= dst_size) break; dst[out++] = '\\'; dst[out++] = (char)c; } else if (c >= 32 && c < 127) { dst[out++] = (char)c; } }
dst[out] = '\0';}
static int udp_snapshot_identity( ACUdp *udp, int *sock_out, struct sockaddr_in *addr_out, char *handle_out, size_t handle_out_size, char *machine_out, size_t machine_out_size) { int sock = -1; int connected = 0;
if (!udp) return 0;
pthread_mutex_lock(&udp->mu); sock = udp->sock; connected = udp->connected; if (addr_out) *addr_out = udp->server_addr; if (handle_out && handle_out_size > 0) { strncpy(handle_out, udp->handle, handle_out_size - 1); handle_out[handle_out_size - 1] = 0; } if (machine_out && machine_out_size > 0) { strncpy(machine_out, udp->machine_id, machine_out_size - 1); machine_out[machine_out_size - 1] = 0; } pthread_mutex_unlock(&udp->mu);
if (sock_out) *sock_out = sock; return connected && sock >= 0;}
static void udp_send_json(ACUdp *udp, const char *json) { int sock = -1; struct sockaddr_in server_addr;
if (!udp || !json || !json[0]) return; if (!udp_snapshot_identity(udp, &sock, &server_addr, NULL, 0, NULL, 0)) return;
ssize_t sent = sendto( sock, json, strlen(json), 0, (struct sockaddr *)&server_addr, sizeof(server_addr) );
if (sent < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { ac_log("[udp] json send failed: %s\n", strerror(errno)); }}
static void *udp_thread(void *arg) { ACUdp *udp = (ACUdp *)arg; unsigned char buf[256];
while (udp->thread_running) { if (udp->sock < 0) { usleep(100000); // 100ms continue; }
// Send pending fairy point pthread_mutex_lock(&udp->mu); int do_send = udp->send_pending; float sx = udp->send_x, sy = udp->send_y; char handle[64]; strncpy(handle, udp->handle, sizeof(handle) - 1); handle[sizeof(handle) - 1] = 0; udp->send_pending = 0; pthread_mutex_unlock(&udp->mu);
if (do_send && udp->connected) { int hlen = (int)strlen(handle); unsigned char pkt[128]; pkt[0] = PKT_FAIRY_SEND; memcpy(pkt + 1, &sx, 4); memcpy(pkt + 5, &sy, 4); pkt[9] = (unsigned char)hlen; memcpy(pkt + 10, handle, hlen); int pkt_len = 10 + hlen; sendto(udp->sock, pkt, pkt_len, 0, (struct sockaddr *)&udp->server_addr, sizeof(udp->server_addr)); }
// Recv with short timeout struct pollfd pfd = { .fd = udp->sock, .events = POLLIN }; int ret = poll(&pfd, 1, 16); // 16ms = ~60Hz if (ret > 0 && (pfd.revents & POLLIN)) { ssize_t n = recvfrom(udp->sock, buf, sizeof(buf), 0, NULL, NULL); if (n > 0 && buf[0] == PKT_FAIRY_RECV && n >= 10) { float fx, fy; memcpy(&fx, buf + 1, 4); memcpy(&fy, buf + 5, 4);
pthread_mutex_lock(&udp->mu); if (udp->fairy_count < UDP_MAX_FAIRIES) { udp->fairies[udp->fairy_count].x = fx; udp->fairies[udp->fairy_count].y = fy; udp->fairy_count++; } pthread_mutex_unlock(&udp->mu); } } } return NULL;}
ACUdp *udp_create(void) { ACUdp *udp = calloc(1, sizeof(ACUdp)); udp->sock = -1; pthread_mutex_init(&udp->mu, NULL); udp->thread_running = 1; pthread_create(&udp->thread, NULL, udp_thread, udp); return udp;}
void udp_destroy(ACUdp *udp) { if (!udp) return; udp->thread_running = 0; pthread_join(udp->thread, NULL); if (udp->sock >= 0) close(udp->sock); pthread_mutex_destroy(&udp->mu); free(udp);}
void udp_connect(ACUdp *udp, const char *host, int port) { if (!udp) return;
// Resolve hostname struct hostent *he = gethostbyname(host); if (!he) { ac_log("[udp] DNS resolve failed for %s\n", host); return; }
memset(&udp->server_addr, 0, sizeof(udp->server_addr)); udp->server_addr.sin_family = AF_INET; udp->server_addr.sin_port = htons(port); memcpy(&udp->server_addr.sin_addr, he->h_addr_list[0], he->h_length);
// Create socket int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { ac_log("[udp] socket() failed: %s\n", strerror(errno)); return; }
// Non-blocking int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK);
pthread_mutex_lock(&udp->mu); udp->sock = fd; udp->connected = 1; pthread_mutex_unlock(&udp->mu);
ac_log("[udp] connected to %s:%d\n", host, port);}
void udp_send_fairy(ACUdp *udp, float x, float y) { if (!udp) return; pthread_mutex_lock(&udp->mu); udp->send_x = x; udp->send_y = y; udp->send_pending = 1; pthread_mutex_unlock(&udp->mu);}
void udp_set_identity(ACUdp *udp, const char *handle, const char *machine_id) { if (!udp) return; pthread_mutex_lock(&udp->mu); if (handle) { strncpy(udp->handle, handle, sizeof(udp->handle) - 1); udp->handle[sizeof(udp->handle) - 1] = 0; } if (machine_id) { strncpy(udp->machine_id, machine_id, sizeof(udp->machine_id) - 1); udp->machine_id[sizeof(udp->machine_id) - 1] = 0; } pthread_mutex_unlock(&udp->mu);}
void udp_send_midi(ACUdp *udp, const char *event, int note, int velocity, int channel, const char *piece) { char handle[64] = ""; char machine_id[64] = ""; char handle_json[128]; char machine_json[128]; char piece_json[64]; char event_json[32]; char json[512];
if (!udp || !event || !piece) return; if (!udp_snapshot_identity(udp, NULL, NULL, handle, sizeof(handle), machine_id, sizeof(machine_id))) return;
json_escape_copy(handle_json, sizeof(handle_json), handle); json_escape_copy(machine_json, sizeof(machine_json), machine_id[0] ? machine_id : "unknown"); json_escape_copy(piece_json, sizeof(piece_json), piece); json_escape_copy(event_json, sizeof(event_json), event);
snprintf( json, sizeof(json), "{\"type\":\"notepat:midi\",\"event\":\"%s\",\"note\":%d,\"velocity\":%d," "\"channel\":%d,\"handle\":\"%s\",\"machineId\":\"%s\",\"piece\":\"%s\",\"ts\":%llu}", event_json, note, velocity, channel, handle_json, machine_json, piece_json, (unsigned long long)udp_now_ms() );
udp_send_json(udp, json);}
void udp_send_midi_heartbeat(ACUdp *udp, const char *piece) { char handle[64] = ""; char machine_id[64] = ""; char handle_json[128]; char machine_json[128]; char piece_json[64]; char json[512];
if (!udp || !piece) return; if (!udp_snapshot_identity(udp, NULL, NULL, handle, sizeof(handle), machine_id, sizeof(machine_id))) return;
json_escape_copy(handle_json, sizeof(handle_json), handle); json_escape_copy(machine_json, sizeof(machine_json), machine_id[0] ? machine_id : "unknown"); json_escape_copy(piece_json, sizeof(piece_json), piece);
snprintf( json, sizeof(json), "{\"type\":\"notepat:midi:heartbeat\",\"handle\":\"%s\",\"machineId\":\"%s\"," "\"piece\":\"%s\",\"broadcast\":true,\"ts\":%llu}", handle_json, machine_json, piece_json, (unsigned long long)udp_now_ms() );
udp_send_json(udp, json);}
int udp_poll_fairies(ACUdp *udp, UDPFairy *out, int max) { if (!udp) return 0; pthread_mutex_lock(&udp->mu); int count = udp->fairy_count; if (count > max) count = max; if (count > 0) { memcpy(out, udp->fairies, count * sizeof(UDPFairy)); udp->fairy_count = 0; } pthread_mutex_unlock(&udp->mu); return count;}