shithub: orca

Download patch

ref: 18e8675003170f4a2251872b3f6188c2a7c2e9da
parent: 070d9a990d8249ae105f838f4ab03427aefd2e9b
author: cancel <cancel@cancel.fm>
date: Sat Dec 8 13:17:24 EST 2018

Add start of OSC output

--- /dev/null
+++ b/osc_out.c
@@ -1,0 +1,103 @@
+#include "osc_out.h"
+
+//#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+
+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);
+}
--- /dev/null
+++ b/osc_out.h
@@ -1,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);
--- a/tool
+++ b/tool
@@ -226,7 +226,7 @@
       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
--- 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 <getopt.h>
 #include <locale.h>
@@ -18,10 +19,14 @@
   // clang-format off
   fprintf(stderr,
       "Usage: orca [options] [file]\n\n"
-      "Options:\n"
+      "General options:\n"
       "    --margins <number> Set cosmetic margins.\n"
       "                       Default: 2\n"
       "    -h or --help       Print this message and exit.\n"
+      "\n"
+      "OSC options:\n"
+      "    --osc-port <number>  UDP Port to send OSC messages to\n"
+      "                         Default: none\n"
       );
   // clang-format on
 }
@@ -471,6 +476,7 @@
   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 @@
   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 @@
   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 @@
   }
 }
 
+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 @@
   }
 }
 
-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 @@
     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 @@
                 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;
@@ -791,6 +829,13 @@
 
   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);