shithub: orca

Download patch

ref: 0f21b338f0dc0ee59937f61d0dcd4b0b000bf564
parent: 8f824ed84aea2dbcf0617770a3dc28dfb7ee60f7
author: cancel <cancel@cancel.fm>
date: Sun Dec 9 13:33:20 EST 2018

Add basic note-off/sustain handling

--- a/osc_out.c
+++ b/osc_out.c
@@ -102,3 +102,69 @@
   }
   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;
+  if (cap < rem) {
+    cap = rem < 16 ? 16 : orca_round_up_power2(rem);
+    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, float delta_time,
+                               Usz* restrict start_removed,
+                               Usz* restrict end_removed) {
+  Susnote* restrict buffer = sl->buffer;
+  Usz count = sl->count;
+  *end_removed = count;
+  for (Usz i = 0; i < count;) {
+    Susnote sn = buffer[i];
+    sn.remaining -= delta_time;
+    if (sn.remaining > 0) {
+      buffer[i].remaining = sn.remaining;
+      ++i;
+    } else {
+      buffer[i] = buffer[count - 1];
+      buffer[count - 1] = sn;
+      --count;
+    }
+  }
+  *start_removed = count;
+  sl->count = count;
+}
--- a/osc_out.h
+++ b/osc_out.h
@@ -14,3 +14,24 @@
 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);
+
+typedef struct {
+  float remaining;
+  U16 chan_note;
+} Susnote;
+
+typedef struct {
+  Susnote* buffer;
+  Usz count;
+  Usz capacity;
+} Susnote_list;
+
+void susnote_list_init(Susnote_list* sl);
+void susnote_list_deinit(Susnote_list* sl);
+void susnote_list_clear(Susnote_list* sl);
+void susnote_list_add_notes(Susnote_list* sl, Susnote const* restrict notes,
+                            Usz count, Usz* restrict start_removed,
+                            Usz* restrict end_removed);
+void susnote_list_advance_time(Susnote_list* sl, float delta_time,
+                               Usz* restrict start_removed,
+                               Usz* restrict end_removed);
--- a/tui_main.c
+++ b/tui_main.c
@@ -503,6 +503,7 @@
   Undo_history undo_hist;
   Oevent_list oevent_list;
   Oevent_list scratch_oevent_list;
+  Susnote_list susnote_list;
   Tui_cursor tui_cursor;
   Piano_bits piano_bits;
   Usz tick_num;
@@ -528,6 +529,7 @@
   tui_cursor_init(&a->tui_cursor);
   oevent_list_init(&a->oevent_list);
   oevent_list_init(&a->scratch_oevent_list);
+  susnote_list_init(&a->susnote_list);
   a->piano_bits = ORCA_PIANO_BITS_NONE;
   a->tick_num = 0;
   a->ruler_spacing_y = 8;
@@ -552,6 +554,7 @@
   undo_history_deinit(&a->undo_hist);
   oevent_list_deinit(&a->oevent_list);
   oevent_list_deinit(&a->scratch_oevent_list);
+  susnote_list_deinit(&a->susnote_list);
   if (a->oosc_dev) {
     oosc_dev_destroy(a->oosc_dev);
   }
@@ -561,24 +564,6 @@
   return a->is_draw_dirty || a->needs_remarking;
 }
 
-double app_secs_to_deadline(App_state const* a) {
-  if (a->is_playing) {
-    double secs_span = 60.0 / (double)a->bpm / 4.0;
-    double rem = secs_span - a->accum_secs;
-    if (rem < 0.0)
-      rem = 0.0;
-    return rem;
-  } else {
-    return 1.0;
-  }
-}
-
-void app_apply_delta_secs(App_state* a, double secs) {
-  if (a->is_playing) {
-    a->accum_secs += secs;
-  }
-}
-
 bool app_set_osc_udp(App_state* a, char const* dest_addr, U16 port) {
   if (a->oosc_dev) {
     oosc_dev_destroy(a->oosc_dev);
@@ -596,33 +581,137 @@
   a->midi_mode = midi_mode;
 }
 
-void send_output_events(Oosc_dev* oosc_dev, Midi_mode const* midi_mode,
-                        Oevent const* events, Usz count) {
+void send_midi_note_offs(Oosc_dev* oosc_dev, Midi_mode const* midi_mode,
+                         Susnote const* start, Susnote const* end) {
   Midi_mode_type midi_mode_type = midi_mode->any.type;
+  for (; start != end; ++start) {
+    U16 chan_note = start->chan_note;
+    Usz chan = chan_note >> 8u;
+    Usz note = chan_note & 0xFFu;
+    switch (midi_mode_type) {
+    case Midi_mode_type_null:
+      break;
+    case Midi_mode_type_osc_bidule: {
+      I32 ints[3];
+      ints[0] = (0x8 << 4) | (U8)chan; // status
+      ints[1] = (I32)note;             // note number
+      ints[2] = 0;                     // velocity
+      oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, ints,
+                       ORCA_ARRAY_COUNTOF(ints));
+    } break;
+    }
+  }
+}
+
+void apply_time_to_sustained_notes(Oosc_dev* oosc_dev,
+                                   Midi_mode const* midi_mode,
+                                   double time_elapsed,
+                                   Susnote_list* susnote_list) {
+  Usz start_removed, end_removed;
+  susnote_list_advance_time(susnote_list, (float)time_elapsed, &start_removed,
+                            &end_removed);
+  if (ORCA_UNLIKELY(start_removed != end_removed)) {
+    Susnote const* restrict susnotes_off = susnote_list->buffer;
+    send_midi_note_offs(oosc_dev, midi_mode, susnotes_off + start_removed,
+                        susnotes_off + end_removed);
+  }
+}
+
+void app_stop_all_sustained_notes(App_state* a) {
+  Susnote_list* sl = &a->susnote_list;
+  send_midi_note_offs(a->oosc_dev, a->midi_mode, sl->buffer,
+                      sl->buffer + sl->count);
+  susnote_list_clear(sl);
+}
+
+void send_output_events(Oosc_dev* oosc_dev, Midi_mode const* midi_mode, Usz bpm,
+                        Susnote_list* susnote_list, Oevent const* events,
+                        Usz count) {
+  Midi_mode_type midi_mode_type = midi_mode->any.type;
+  double bar_secs = (double)bpm / 60.0 * 4.0;
+
+  enum { Midi_on_capacity = 512 };
+  typedef struct {
+    U8 channel;
+    U8 note_number;
+    U8 velocity;
+  } Midi_note_on;
+  Midi_note_on midi_note_ons[Midi_on_capacity];
+  Susnote new_susnotes[Midi_on_capacity];
+  Usz midi_note_count = 0;
+
   for (Usz i = 0; i < count; ++i) {
+    if (midi_note_count == Midi_on_capacity)
+      break;
     Oevent const* e = events + i;
     switch ((Oevent_types)e->any.oevent_type) {
     case Oevent_type_midi: {
-      Oevent_midi const* em = (Oevent_midi const*)e;
-      switch (midi_mode_type) {
-      case Midi_mode_type_null:
-        break;
-      case Midi_mode_type_osc_bidule: {
-        I32 ints[3];
-        ints[0] = (0x9 << 4) | em->channel;   // status
-        ints[1] = 12 * em->octave + em->note; // note number
-        // ints[1] = 12 * 4 + em->note; // note number
-        ints[2] = em->velocity; // velocity
-        // ints[2] = 127;          // velocity
-        oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, ints,
-                         ORCA_ARRAY_COUNTOF(ints));
-      } break;
-      }
+      Oevent_midi const* em = (Oevent_midi const*)&e->midi;
+      Usz note_number = (Usz)(12u * em->octave + em->note);
+      Usz channel = em->channel;
+      Usz bar_div = em->bar_divisor;
+      midi_note_ons[midi_note_count] =
+          (Midi_note_on){.channel = (U8)channel,
+                         .note_number = (U8)note_number,
+                         .velocity = em->velocity};
+      new_susnotes[midi_note_count] = (Susnote){
+          .remaining =
+              bar_div == 0 ? 0.0f : (float)(bar_secs / (double)bar_div),
+          .chan_note = (U16)((channel << 8u) | note_number)};
+      ++midi_note_count;
     } break;
     }
   }
+
+  if (midi_note_count == 0)
+    return;
+
+  Usz start_note_offs, end_note_offs;
+  susnote_list_add_notes(susnote_list, new_susnotes, midi_note_count,
+                         &start_note_offs, &end_note_offs);
+  if (start_note_offs != end_note_offs) {
+    Susnote const* restrict susnotes_off = susnote_list->buffer;
+    send_midi_note_offs(oosc_dev, midi_mode, susnotes_off + start_note_offs,
+                        susnotes_off + end_note_offs);
+  }
+  for (Usz i = 0; i < midi_note_count; ++i) {
+    Midi_note_on mno = midi_note_ons[i];
+    switch (midi_mode_type) {
+    case Midi_mode_type_null:
+      break;
+    case Midi_mode_type_osc_bidule: {
+      I32 ints[3];
+      ints[0] = (0x9 << 4) | mno.channel; // status
+      ints[1] = mno.note_number;          // note number
+      ints[2] = mno.velocity;             // velocity
+      oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, ints,
+                       ORCA_ARRAY_COUNTOF(ints));
+    } break;
+    }
+  }
 }
 
+double app_secs_to_deadline(App_state const* a) {
+  if (a->is_playing) {
+    double secs_span = 60.0 / (double)a->bpm / 4.0;
+    double rem = secs_span - a->accum_secs;
+    if (rem < 0.0)
+      rem = 0.0;
+    return rem;
+  } else {
+    return 1.0;
+  }
+}
+
+void app_apply_delta_secs(App_state* a, double secs) {
+  if (a->is_playing) {
+    a->accum_secs += secs;
+    Oosc_dev* oosc_dev = a->oosc_dev;
+    Midi_mode const* midi_mode = a->midi_mode;
+    apply_time_to_sustained_notes(oosc_dev, midi_mode, secs, &a->susnote_list);
+  }
+}
+
 void app_do_stuff(App_state* a) {
   double secs_span = 60.0 / (double)a->bpm / 4.0;
   Oosc_dev* oosc_dev = a->oosc_dev;
@@ -641,7 +730,8 @@
     if (oosc_dev && midi_mode) {
       Usz count = a->oevent_list.count;
       if (count > 0) {
-        send_output_events(oosc_dev, midi_mode, a->oevent_list.buffer, count);
+        send_output_events(oosc_dev, midi_mode, a->bpm, &a->susnote_list,
+                           a->oevent_list.buffer, count);
       }
     }
   }
@@ -827,6 +917,7 @@
     break;
   case App_input_cmd_toggle_play_pause:
     if (a->is_playing) {
+      app_stop_all_sustained_notes(a);
       a->is_playing = false;
       // nodelay(stdscr, FALSE);
     } else {
@@ -1159,6 +1250,7 @@
     key = wgetch(stdscr);
   }
 quit:
+  app_stop_all_sustained_notes(&app_state);
   if (cont_win) {
     delwin(cont_win);
   }