You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
7.2 KiB
245 lines
7.2 KiB
#include "osc_out.h"
|
|
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
|
|
struct Oosc_dev {
|
|
int fd;
|
|
// Just keep the whole list around, since juggling the strict-aliasing
|
|
// problems with sockaddr_storage is not worth it.
|
|
struct addrinfo* chosen;
|
|
struct addrinfo* head;
|
|
};
|
|
|
|
Oosc_udp_create_error oosc_dev_create_udp(Oosc_dev** out_ptr,
|
|
char const* dest_addr,
|
|
char const* dest_port) {
|
|
struct addrinfo hints;
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
hints.ai_protocol = 0;
|
|
hints.ai_flags = AI_ADDRCONFIG;
|
|
struct addrinfo* chosen = NULL;
|
|
struct addrinfo* head = NULL;
|
|
int err = getaddrinfo(dest_addr, dest_port, &hints, &head);
|
|
if (err != 0) {
|
|
fprintf(stderr, "Failed to get address info, error: %d\n", errno);
|
|
return Oosc_udp_create_error_getaddrinfo_failed;
|
|
}
|
|
// Special behavior: if no hostname was provided, we'll get loopback(s) from
|
|
// getaddrinfo. Which is good. But on systems with both an ipv4 and ipv6
|
|
// address, we might get the ipv6 address listed first. And the OSC server,
|
|
// for example Plogue Bidule, might not support ipv6. And defaulting to the
|
|
// ipv6 loopback wouldn't work, in that case. So if there's no hostname, and
|
|
// we find an ipv4 address in the results, prefer to use that.
|
|
//
|
|
// Actually let's just prefer ipv4 completely for now
|
|
#if 0
|
|
if (!dest_addr) {
|
|
#endif
|
|
{
|
|
for (struct addrinfo* a = head; a; a = a->ai_next) {
|
|
if (a->ai_family != AF_INET)
|
|
continue;
|
|
chosen = a;
|
|
break;
|
|
}
|
|
}
|
|
if (!chosen)
|
|
chosen = head;
|
|
#if 0
|
|
for (struct addrinfo* a = head; a; a = a->ai_next) {
|
|
char buff[INET6_ADDRSTRLEN];
|
|
char const* str = NULL;
|
|
if (a->ai_family == AF_INET) {
|
|
str = inet_ntop(AF_INET, &((struct sockaddr_in*)a->ai_addr)->sin_addr,
|
|
buff, sizeof(buff));
|
|
} else if (a->ai_family == AF_INET6) {
|
|
str = inet_ntop(AF_INET6, &((struct sockaddr_in6*)a->ai_addr)->sin6_addr,
|
|
buff, sizeof(buff));
|
|
}
|
|
if (str) {
|
|
fprintf(stderr, "ntop name: %s\n", str);
|
|
} else {
|
|
fprintf(stderr, "inet_ntop error\n");
|
|
}
|
|
}
|
|
#endif
|
|
int udpfd =
|
|
socket(chosen->ai_family, chosen->ai_socktype, chosen->ai_protocol);
|
|
if (udpfd < 0) {
|
|
fprintf(stderr, "Failed to open UDP socket, error number: %d\n", errno);
|
|
freeaddrinfo(head);
|
|
return Oosc_udp_create_error_couldnt_open_socket;
|
|
}
|
|
Oosc_dev* dev = malloc(sizeof(Oosc_dev));
|
|
dev->fd = udpfd;
|
|
dev->chosen = chosen;
|
|
dev->head = head;
|
|
*out_ptr = dev;
|
|
return Oosc_udp_create_error_ok;
|
|
}
|
|
|
|
void oosc_dev_destroy(Oosc_dev* dev) {
|
|
close(dev->fd);
|
|
freeaddrinfo(dev->head);
|
|
free(dev);
|
|
}
|
|
|
|
void oosc_send_datagram(Oosc_dev* dev, char const* data, Usz size) {
|
|
ssize_t res = sendto(dev->fd, data, size, 0, dev->chosen->ai_addr,
|
|
dev->chosen->ai_addrlen);
|
|
if (res < 0) {
|
|
fprintf(stderr, "UDP message send failed\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static bool oosc_write_strn(char* restrict buffer, Usz buffer_size,
|
|
Usz* buffer_pos, char const* restrict in_str,
|
|
Usz in_str_len) {
|
|
// no overflow check, should be fine
|
|
Usz in_plus_null = in_str_len + 1;
|
|
Usz null_pad = (4 - in_plus_null % 4) % 4;
|
|
Usz needed = in_plus_null + null_pad;
|
|
Usz cur_pos = *buffer_pos;
|
|
if (cur_pos + needed >= buffer_size)
|
|
return false;
|
|
for (Usz i = 0; i < in_str_len; ++i) {
|
|
buffer[cur_pos + i] = in_str[i];
|
|
}
|
|
buffer[cur_pos + in_str_len] = 0;
|
|
cur_pos += in_plus_null;
|
|
for (Usz i = 0; i < null_pad; ++i) {
|
|
buffer[cur_pos + i] = 0;
|
|
}
|
|
*buffer_pos = cur_pos + null_pad;
|
|
return true;
|
|
}
|
|
|
|
void oosc_send_int32s(Oosc_dev* dev, char const* osc_address, I32 const* vals,
|
|
Usz count) {
|
|
char buffer[2048];
|
|
Usz buf_pos = 0;
|
|
if (!oosc_write_strn(buffer, sizeof(buffer), &buf_pos, osc_address,
|
|
strlen(osc_address)))
|
|
return;
|
|
Usz typetag_str_size = 1 + count + 1; // comma, 'i'... , null
|
|
Usz typetag_str_null_pad = (4 - typetag_str_size % 4) % 4;
|
|
if (buf_pos + typetag_str_size + typetag_str_null_pad > sizeof(buffer))
|
|
return;
|
|
buffer[buf_pos] = ',';
|
|
++buf_pos;
|
|
for (Usz i = 0; i < count; ++i) {
|
|
buffer[buf_pos + i] = 'i';
|
|
}
|
|
buffer[buf_pos + count] = 0;
|
|
buf_pos += count + 1;
|
|
for (Usz i = 0; i < typetag_str_null_pad; ++i) {
|
|
buffer[buf_pos + i] = 0;
|
|
}
|
|
buf_pos += typetag_str_null_pad;
|
|
Usz ints_size = count * sizeof(I32);
|
|
if (buf_pos + ints_size > sizeof(buffer))
|
|
return;
|
|
for (Usz i = 0; i < count; ++i) {
|
|
union {
|
|
I32 i;
|
|
U32 u;
|
|
} pun;
|
|
pun.i = vals[i];
|
|
U32 u_ne = htonl(pun.u);
|
|
memcpy(buffer + buf_pos, &u_ne, sizeof(u_ne));
|
|
buf_pos += sizeof(u_ne);
|
|
}
|
|
oosc_send_datagram(dev, buffer, buf_pos);
|
|
}
|
|
|
|
void susnote_list_init(Susnote_list* sl) {
|
|
sl->buffer = NULL;
|
|
sl->count = 0;
|
|
sl->capacity = 0;
|
|
}
|
|
|
|
void susnote_list_deinit(Susnote_list* sl) { free(sl->buffer); }
|
|
|
|
void susnote_list_clear(Susnote_list* sl) { sl->count = 0; }
|
|
|
|
void susnote_list_add_notes(Susnote_list* sl, Susnote const* restrict notes,
|
|
Usz added_count, Usz* restrict start_removed,
|
|
Usz* restrict end_removed) {
|
|
Susnote* buffer = sl->buffer;
|
|
Usz count = sl->count;
|
|
Usz cap = sl->capacity;
|
|
Usz rem = count + added_count;
|
|
Usz needed_cap = rem + added_count;
|
|
if (cap < needed_cap) {
|
|
cap = needed_cap < 16 ? 16 : orca_round_up_power2(needed_cap);
|
|
buffer = realloc(buffer, cap * sizeof(Susnote));
|
|
sl->capacity = cap;
|
|
sl->buffer = buffer;
|
|
}
|
|
*start_removed = rem;
|
|
Usz i_in = 0;
|
|
for (; i_in < added_count; ++i_in) {
|
|
Susnote this_in = notes[i_in];
|
|
for (Usz i_old = 0; i_old < count; ++i_old) {
|
|
Susnote this_old = buffer[i_old];
|
|
if (this_old.chan_note == this_in.chan_note) {
|
|
buffer[i_old] = this_in;
|
|
buffer[rem] = this_old;
|
|
++rem;
|
|
goto next_in;
|
|
}
|
|
}
|
|
buffer[count] = this_in;
|
|
++count;
|
|
next_in:;
|
|
}
|
|
sl->count = count;
|
|
*end_removed = rem;
|
|
}
|
|
|
|
void susnote_list_advance_time(Susnote_list* sl, double delta_time,
|
|
Usz* restrict start_removed,
|
|
Usz* restrict end_removed,
|
|
double* soonest_deadline) {
|
|
Susnote* restrict buffer = sl->buffer;
|
|
Usz count = sl->count;
|
|
*end_removed = count;
|
|
float delta_float = (float)delta_time;
|
|
float soonest = 1.0f;
|
|
for (Usz i = 0; i < count;) {
|
|
Susnote sn = buffer[i];
|
|
sn.remaining -= delta_float;
|
|
if (sn.remaining > 0.001) {
|
|
if (sn.remaining < soonest)
|
|
soonest = sn.remaining;
|
|
buffer[i].remaining = sn.remaining;
|
|
++i;
|
|
} else {
|
|
buffer[i] = buffer[count - 1];
|
|
buffer[count - 1] = sn;
|
|
--count;
|
|
}
|
|
}
|
|
*start_removed = count;
|
|
*soonest_deadline = (double)soonest;
|
|
sl->count = count;
|
|
}
|
|
|
|
double susnote_list_soonest_deadline(Susnote_list const* sl) {
|
|
float soonest = 1.0f;
|
|
Susnote const* buffer = sl->buffer;
|
|
for (Usz i = 0, n = sl->count; i < n; ++i) {
|
|
float rem = buffer[i].remaining;
|
|
if (rem < soonest)
|
|
soonest = rem;
|
|
}
|
|
return (double)soonest;
|
|
}
|
|
|