From 18e8675003170f4a2251872b3f6188c2a7c2e9da Mon Sep 17 00:00:00 2001 From: cancel Date: Sat, 8 Dec 2018 18:17:24 +0900 Subject: [PATCH] Add start of OSC output --- osc_out.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++ osc_out.h | 15 ++++++++ tool | 2 +- tui_main.c | 53 ++++++++++++++++++++++++--- 4 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 osc_out.c create mode 100644 osc_out.h diff --git a/osc_out.c b/osc_out.c new file mode 100644 index 0000000..0cf47a9 --- /dev/null +++ b/osc_out.c @@ -0,0 +1,103 @@ +#include "osc_out.h" + +//#include +#include +#include +#include + +struct Oosc_dev { + int fd; + struct sockaddr_in addr; +}; + +Oosc_udp_create_error oosc_dev_create_udp(Oosc_dev** out_ptr, U16 port) { + int udpfd = socket(AF_INET, SOCK_DGRAM, 0); + if (udpfd < 0) { + fprintf(stderr, "Failed to open UDP socket, error number: %d\n", errno); + return Oosc_udp_create_error_couldnt_open_socket; + } + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons((U16)port); + Oosc_dev* dev = malloc(sizeof(Oosc_dev)); + dev->fd = udpfd; + dev->addr = addr; + *out_ptr = dev; + return Oosc_udp_create_error_ok; +} + +void oosc_dev_destroy(Oosc_dev* dev) { + close(dev->fd); + free(dev); +} + +void oosc_send_datagram(Oosc_dev* dev, char const* data, Usz size) { + ssize_t res = sendto(dev->fd, data, size, 0, (struct sockaddr*)&dev->addr, + sizeof(dev->addr)); + 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 = in_plus_null % 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_str_len; + 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 = typetag_str_size % 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 += count; + oosc_send_datagram(dev, buffer, buf_pos); +} diff --git a/osc_out.h b/osc_out.h new file mode 100644 index 0000000..0c8ba9a --- /dev/null +++ b/osc_out.h @@ -0,0 +1,15 @@ +#include "base.h" + +typedef struct Oosc_dev Oosc_dev; + +typedef enum { + Oosc_udp_create_error_ok = 0, + Oosc_udp_create_error_couldnt_open_socket = 1, +} Oosc_udp_create_error; + +Oosc_udp_create_error oosc_dev_create_udp(Oosc_dev** out_dev_ptr, U16 port); +void oosc_dev_destroy(Oosc_dev* dev); +// raw UDP datagram +void oosc_send_datagram(Oosc_dev* dev, char const* data, Usz size); +void oosc_send_int32s(Oosc_dev* dev, char const* osc_address, I32 const* vals, + Usz count); diff --git a/tool b/tool index 429f7d5..665730a 100755 --- a/tool +++ b/tool @@ -226,7 +226,7 @@ build_target() { out_exe=cli ;; orca|tui) - add source_files tui_main.c + add source_files osc_out.c tui_main.c add cc_flags -D_XOPEN_SOURCE_EXTENDED=1 # thirdparty headers (like sokol_time.h) should get -isystem for their # include dir so that any warnings they generate with our warning flags diff --git a/tui_main.c b/tui_main.c index d41cbc2..c687292 100644 --- a/tui_main.c +++ b/tui_main.c @@ -3,6 +3,7 @@ #include "field.h" #include "gbuffer.h" #include "mark.h" +#include "osc_out.h" #include "sim.h" #include #include @@ -18,10 +19,14 @@ static void usage() { // clang-format off fprintf(stderr, "Usage: orca [options] [file]\n\n" - "Options:\n" + "General options:\n" " --margins Set cosmetic margins.\n" " Default: 2\n" " -h or --help Print this message and exit.\n" + "\n" + "OSC options:\n" + " --osc-port UDP Port to send OSC messages to\n" + " Default: none\n" ); // clang-format on } @@ -471,6 +476,7 @@ typedef struct { Usz bpm; double accum_secs; char const* filename; + Oosc_dev* oosc_dev; bool needs_remarking; bool is_draw_dirty; bool is_playing; @@ -494,6 +500,7 @@ void app_init(App_state* a) { a->bpm = 120; a->accum_secs = 0.0; a->filename = NULL; + a->oosc_dev = NULL; a->needs_remarking = true; a->is_draw_dirty = false; a->is_playing = false; @@ -508,6 +515,9 @@ void app_deinit(App_state* a) { undo_history_deinit(&a->undo_hist); oevent_list_deinit(&a->oevent_list); oevent_list_deinit(&a->scratch_oevent_list); + if (a->oosc_dev) { + oosc_dev_destroy(a->oosc_dev); + } } bool app_is_draw_dirty(App_state* a) { @@ -532,6 +542,18 @@ void app_apply_delta_secs(App_state* a, double secs) { } } +bool app_set_osc_udp_port(App_state* a, U16 port) { + if (a->oosc_dev) { + oosc_dev_destroy(a->oosc_dev); + a->oosc_dev = NULL; + } + Oosc_udp_create_error err = oosc_dev_create_udp(&a->oosc_dev, port); + if (err) { + return false; + } + return true; +} + void app_do_stuff(App_state* a) { double secs_span = 60.0 / (double)a->bpm / 4.0; while (a->accum_secs > secs_span) { @@ -743,15 +765,20 @@ void app_input_cmd(App_state* a, App_input_cmd ev) { } } -enum { Argopt_margins = UCHAR_MAX + 1 }; +enum { + Argopt_margins = UCHAR_MAX + 1, + Argopt_osc_port, +}; int main(int argc, char** argv) { static struct option tui_options[] = { {"margins", required_argument, 0, Argopt_margins}, {"help", no_argument, 0, 'h'}, + {"osc-port", required_argument, 0, Argopt_osc_port}, {NULL, 0, NULL, 0}}; char* input_file = NULL; int margin_thickness = 2; + U16 osc_port = 0; for (;;) { int c = getopt_long(argc, argv, "h", tui_options, NULL); if (c == -1) @@ -760,7 +787,7 @@ int main(int argc, char** argv) { case 'h': usage(); return 0; - case Argopt_margins: + case Argopt_margins: { margin_thickness = atoi(optarg); if (margin_thickness == 0 && strcmp(optarg, "0")) { fprintf(stderr, @@ -769,7 +796,18 @@ int main(int argc, char** argv) { optarg); return 1; } - break; + } break; + case Argopt_osc_port: { + int osc_port0 = atoi(optarg); + if (osc_port0 <= 0) { + fprintf(stderr, "OSC port must be greater than 0.\n"); + return 1; + } else if (osc_port0 > UINT16_MAX) { + fprintf(stderr, "OSC port must be <= %d\n", (int)UINT16_MAX); + return 1; + } + osc_port = (U16)osc_port0; + } break; case '?': usage(); return 1; @@ -792,6 +830,13 @@ int main(int argc, char** argv) { App_state app_state; app_init(&app_state); + if (osc_port != 0) { + if (!app_set_osc_udp_port(&app_state, osc_port)) { + fprintf(stderr, "Failed to open OSC UDP port %d\n", (int)osc_port); + exit(1); + } + } + if (input_file) { Field_load_error fle = field_load_file(input_file, &app_state.field); if (fle != Field_load_error_ok) {