From 06c55ae15d5b0ad96dd175677403c38d23f1f66b Mon Sep 17 00:00:00 2001
From: Mitya Selivanov <automainint@guattari.tech>
Date: Wed, 14 Feb 2024 20:26:49 +0100
Subject: Choose instrument UI

---
 source/saw/main.c | 745 +++++++++++++++++++++++++++++++-----------------------
 1 file changed, 434 insertions(+), 311 deletions(-)

(limited to 'source')

diff --git a/source/saw/main.c b/source/saw/main.c
index 12b271e..d139773 100644
--- a/source/saw/main.c
+++ b/source/saw/main.c
@@ -52,8 +52,11 @@
 
 enum {
   //  TODO
+  //
   //  Use 28224000 for time rate, divisible by common sample rates
   //  like 192000, 44100 etc.
+  //
+  //  Dynamic buffer size.
 
   CHANNEL_COUNT = 2,
   SAMPLE_RATE   = 44100,
@@ -82,26 +85,30 @@ enum {
   ROLL_DEFAULT_RATE        = 8,
   ROLL_DEFAULT_UI_OFFSET_Y = 710,
 
-  INSTRUMENT_SINE = 0,
-  INSTRUMENT_SAW_UP,
-  INSTRUMENT_SAW_DOWN,
-  INSTRUMENT_SQUARE_UP,
-  INSTRUMENT_SQUARE_DOWN,
-  INSTRUMENT_KICK,
-  INSTRUMENT_COUNT,
-
-  TRACK_INPUT_NONE = 0,
-  TRACK_INPUT_INSTRUMENT,
-  TRACK_INPUT_WARP,
-  TRACK_INPUT_PHASE,
-  TRACK_INPUT_UNISON,
-  TRACK_INPUT_SPREAD,
-  TRACK_INPUT_STEREO_WIDTH,
-  TRACK_INPUT_VOLUME,
-  TRACK_INPUT_SUSTAIN,
-  TRACK_INPUT_ATTACK,
-  TRACK_INPUT_DECAY,
-  TRACK_INPUT_RELEASE,
+  WAVE_SINE = 0,
+  WAVE_SAW_UP,
+  WAVE_SAW_DOWN,
+  WAVE_SQUARE_UP,
+  WAVE_SQUARE_DOWN,
+  WAVE_KICK,
+  WAVE_COUNT,
+
+  INSTRUMENT_NONE = 0,
+  INSTRUMENT_OSCILLATOR,
+  INSTRUMENT_SAMPLER,
+
+  OSCILLATOR_INPUT_NONE = 0,
+  OSCILLATOR_INPUT_WAVE,
+  OSCILLATOR_INPUT_WARP,
+  OSCILLATOR_INPUT_PHASE,
+  OSCILLATOR_INPUT_UNISON,
+  OSCILLATOR_INPUT_SPREAD,
+  OSCILLATOR_INPUT_STEREO_WIDTH,
+  OSCILLATOR_INPUT_VOLUME,
+  OSCILLATOR_INPUT_SUSTAIN,
+  OSCILLATOR_INPUT_ATTACK,
+  OSCILLATOR_INPUT_DECAY,
+  OSCILLATOR_INPUT_RELEASE,
 
   EDIT_MODE_HAND = 0,
   EDIT_MODE_ERASE,
@@ -160,11 +167,9 @@ typedef struct {
 } saw_envelope_t;
 
 typedef struct {
-  i32            instrument;
+  i32            wave;
   f64            warp;
   f64            phase;
-  i32            unison;
-  f64            spread;
   f64            stereo_width;
   f64            volume;
   saw_envelope_t envelope;
@@ -173,6 +178,19 @@ typedef struct {
   //
   i32 value_input;
   f64 value_buffer;
+} saw_oscillator_t;
+
+typedef struct {
+  //  TODO
+  int _;
+} saw_sampler_t;
+
+typedef struct {
+  i32 instrument;
+  union {
+    saw_oscillator_t oscillator;
+    saw_sampler_t    sampler;
+  };
 } saw_track_t;
 
 typedef struct {
@@ -303,25 +321,33 @@ static void saw_play_voice(saw_track_t *track, saw_roll_t *roll,
   for (i32 n = VOICE_COUNT - 1; n > 0; --n)
     saw_voices[n] = saw_voices[n - 1];
 
-  f64 s = track->stereo_width / 8;
-
-  saw_voices[0] = (saw_voice_t) {
-    .enabled   = 1,
-    .time      = 0,
-    .duration  = (f64) duration / (f64) SAMPLE_RATE,
-    .frequency = saw_pitch_frequency(pitch),
-    .amplitude = saw_pitch_amplitude(pitch) * track->volume,
-    .phase = {
-      saw_random(-s, s),
-      saw_random(-s, s),
-    },
-    .track     = roll->track,
-  };
+  switch (track->instrument) {
+    case INSTRUMENT_OSCILLATOR: {
+      saw_oscillator_t *osc = &track->oscillator;
+
+      f64 s = osc->stereo_width / 8;
+
+      saw_voices[0] = (saw_voice_t) {
+        .enabled   = 1,
+        .time      = 0,
+        .duration  = (f64) duration / (f64) SAMPLE_RATE,
+        .frequency = saw_pitch_frequency(pitch),
+        .amplitude = saw_pitch_amplitude(pitch) * osc->volume,
+        .phase = {
+          saw_random(-s, s),
+          saw_random(-s, s),
+        },
+        .track     = roll->track,
+      };
+    } break;
+
+    default:;
+  }
 }
 
 static f64 saw_oscillator(i32 type, f64 frequency, f64 phase,
                           f64 warp, f64 t) {
-  if (type == INSTRUMENT_KICK) {
+  if (type == WAVE_KICK) {
     frequency /= 8.;
     f64 q = t * frequency + phase;
     frequency /= 8.;
@@ -333,11 +359,11 @@ static f64 saw_oscillator(i32 type, f64 frequency, f64 phase,
   t = t - floor(t);
 
   switch (type) {
-    case INSTRUMENT_SINE: return sin(t * (M_PI * 2));
-    case INSTRUMENT_SAW_UP: return -1. + t * 2.;
-    case INSTRUMENT_SAW_DOWN: return 1. - t * 2.;
-    case INSTRUMENT_SQUARE_UP: return t < .5 + warp / 2. ? -1. : 1.;
-    case INSTRUMENT_SQUARE_DOWN: return t < .5 + warp / 2. ? 1. : -1.;
+    case WAVE_SINE: return sin(t * (M_PI * 2));
+    case WAVE_SAW_UP: return -1. + t * 2.;
+    case WAVE_SAW_DOWN: return 1. - t * 2.;
+    case WAVE_SQUARE_UP: return t < .5 + warp / 2. ? -1. : 1.;
+    case WAVE_SQUARE_DOWN: return t < .5 + warp / 2. ? 1. : -1.;
     default:;
   }
 
@@ -382,8 +408,14 @@ static void saw_audio_render(void) {
             if (!note->enabled || note->time != roll_frame)
               continue;
 
-            saw_play_voice(saw_tracks + roll->track, roll,
-                           note->pitch, note->duration);
+            switch (saw_tracks[roll->track].instrument) {
+              case INSTRUMENT_OSCILLATOR:
+                saw_play_voice(saw_tracks + roll->track, roll,
+                               note->pitch, note->duration);
+                break;
+
+              default:;
+            }
           }
         }
 
@@ -399,36 +431,45 @@ static void saw_audio_render(void) {
 
           saw_track_t *track = saw_tracks + saw_voices[n].track;
 
-          i32 wave_type = track->instrument;
-          f64 warp      = track->warp;
-          f64 frequency = saw_voices[n].frequency;
-          f64 amplitude = saw_voices[n].amplitude;
-          f64 phase_l   = track->phase + saw_voices[n].phase[0];
-          f64 phase_r   = track->phase + saw_voices[n].phase[1];
-
-          f64 attack   = track->envelope.attack;
-          f64 decay    = track->envelope.decay;
-          f64 sustain  = track->envelope.sustain;
-          f64 duration = saw_voices[n].duration;
-          f64 release  = track->envelope.release;
-
-          f64 t = (f64) saw_voices[n].time / (f64) SAMPLE_RATE;
-          f64 a = amplitude * saw_envelope(t, attack, decay, sustain,
-                                           duration, release);
-
-          saw_playback_temp[i * CHANNEL_COUNT] +=
-              (f32) (saw_oscillator(wave_type, frequency, phase_l,
-                                    warp, t) *
-                     a);
-          saw_playback_temp[i * CHANNEL_COUNT + 1] +=
-              (f32) (saw_oscillator(wave_type, frequency, phase_r,
-                                    warp, t) *
-                     a);
-
-          saw_voices[n].time++;
-
-          if (t > duration + release)
-            saw_voices[n].enabled = 0;
+          switch (track->instrument) {
+            case INSTRUMENT_OSCILLATOR: {
+              saw_oscillator_t *osc = &track->oscillator;
+
+              i32 wave_type = osc->wave;
+              f64 warp      = osc->warp;
+              f64 frequency = saw_voices[n].frequency;
+              f64 amplitude = saw_voices[n].amplitude;
+              f64 phase_l   = osc->phase + saw_voices[n].phase[0];
+              f64 phase_r   = osc->phase + saw_voices[n].phase[1];
+
+              f64 attack   = osc->envelope.attack;
+              f64 decay    = osc->envelope.decay;
+              f64 sustain  = osc->envelope.sustain;
+              f64 duration = saw_voices[n].duration;
+              f64 release  = osc->envelope.release;
+
+              f64 t = (f64) saw_voices[n].time / (f64) SAMPLE_RATE;
+              f64 a = amplitude * saw_envelope(t, attack, decay,
+                                               sustain, duration,
+                                               release);
+
+              saw_playback_temp[i * CHANNEL_COUNT] +=
+                  (f32) (saw_oscillator(wave_type, frequency, phase_l,
+                                        warp, t) *
+                         a);
+              saw_playback_temp[i * CHANNEL_COUNT + 1] +=
+                  (f32) (saw_oscillator(wave_type, frequency, phase_r,
+                                        warp, t) *
+                         a);
+
+              saw_voices[n].time++;
+
+              if (t > duration + release)
+                saw_voices[n].enabled = 0;
+            } break;
+
+            default:;
+          }
         }
       }
     }
@@ -541,9 +582,12 @@ static void saw_reset_ui_offset(void) {
 }
 
 static void saw_ui_header(i64 x0, u64 y0, u64 width, i64 height) {
+  if (height > (2 * width) / 15)
+    height = (2 * width) / 15;
+
   i64 frame_height = sapp_height();
 
-  i64 border = 14;
+  i64 border = height > 14 * 4 ? 14 : height / 4;
 
   nvgFontSize(saw_nvg, height - border * 2);
   nvgFontFaceId(saw_nvg, saw_font_icons);
@@ -1033,8 +1077,53 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
   }
 }
 
-static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
-                         i64 width, i64 height, str_t title) {
+static void saw_ui_choose_instrument(saw_track_t *track, i64 x0,
+                                     i64 y0, i64 width, i64 height) {
+  i64 frame_height = sapp_height();
+
+  i64 text_height = 40;
+  i64 border      = 4;
+
+  nvgFontSize(saw_nvg, text_height - border);
+  nvgFontFaceId(saw_nvg, saw_font_text);
+
+  i64 y = frame_height - y0 - height;
+
+  {
+    b8 has_cursor = saw_mouse_x >= x0 && saw_mouse_x < x0 + width &&
+                    saw_mouse_y >= y && saw_mouse_y < y + text_height;
+
+    if (has_cursor)
+      nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255));
+    else
+      nvgFillColor(saw_nvg, nvgRGBA(200, 120, 80, 255));
+    nvgText(saw_nvg, x0 + border * 2, y + text_height - border * 2,
+            "Oscillator", NULL);
+    y += text_height;
+
+    if (has_cursor && saw_lbutton_down)
+      track->instrument = INSTRUMENT_OSCILLATOR;
+  }
+
+  {
+    b8 has_cursor = saw_mouse_x >= x0 && saw_mouse_x < x0 + width &&
+                    saw_mouse_y >= y && saw_mouse_y < y + text_height;
+
+    if (has_cursor)
+      nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255));
+    else
+      nvgFillColor(saw_nvg, nvgRGBA(200, 120, 80, 255));
+    nvgText(saw_nvg, x0 + border * 2, y + text_height - border * 2,
+            "Sampler", NULL);
+    y += text_height;
+
+    if (has_cursor && saw_lbutton_down)
+      track->instrument = INSTRUMENT_SAMPLER;
+  }
+}
+
+static void saw_ui_oscillator(saw_oscillator_t *osc, i64 x0, i64 y0,
+                              i64 width, i64 height) {
   i64 frame_height = sapp_height();
 
   i64 text_height   = 33;
@@ -1042,31 +1131,32 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
   i64 border        = 2;
   i64 column_width  = 200;
 
-  i64 next_y = header_offset;
-
   //  Values input and highlight
   //
 
-  if (!saw_lbutton_down && track->value_input != TRACK_INPUT_NONE) {
-    track->value_input  = TRACK_INPUT_NONE;
-    track->value_buffer = 0;
+  if (!saw_lbutton_down &&
+      osc->value_input != OSCILLATOR_INPUT_NONE) {
+    osc->value_input  = OSCILLATOR_INPUT_NONE;
+    osc->value_buffer = 0;
 
 #ifndef __EMSCRIPTEN__
     sapp_lock_mouse(0);
 #endif
   }
 
-  for (i64 input_index = TRACK_INPUT_INSTRUMENT;
-       input_index <= TRACK_INPUT_RELEASE; ++input_index) {
+  i64 next_y = 0;
+
+  for (i64 input_index = OSCILLATOR_INPUT_WAVE;
+       input_index <= OSCILLATOR_INPUT_RELEASE; ++input_index) {
     //  TODO
     //  Implement Unison and Spread.
 
-    if (input_index == TRACK_INPUT_UNISON ||
-        input_index == TRACK_INPUT_SPREAD)
+    if (input_index == OSCILLATOR_INPUT_UNISON ||
+        input_index == OSCILLATOR_INPUT_SPREAD)
       continue;
 
-    if ((track->value_input == TRACK_INPUT_NONE ||
-         track->value_input == input_index) &&
+    if ((osc->value_input == OSCILLATOR_INPUT_NONE ||
+         osc->value_input == input_index) &&
         saw_mouse_x >= x0 && saw_mouse_x < x0 + width &&
         saw_mouse_y >= frame_height - y0 - height + next_y &&
         saw_mouse_y <
@@ -1084,57 +1174,49 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
       }
 
       if (saw_lbutton_click &&
-          track->value_input == TRACK_INPUT_NONE) {
+          osc->value_input == OSCILLATOR_INPUT_NONE) {
 
 #ifndef __EMSCRIPTEN__
         sapp_lock_mouse(1);
 #endif
 
-        track->value_input = input_index;
+        osc->value_input = input_index;
 
         switch (input_index) {
-          case TRACK_INPUT_INSTRUMENT:
-            track->value_buffer = track->instrument * 500;
+          case OSCILLATOR_INPUT_WAVE:
+            osc->value_buffer = osc->wave * 500;
             break;
 
-          case TRACK_INPUT_UNISON:
-            track->value_buffer = track->unison * 100;
+          case OSCILLATOR_INPUT_WARP:
+            osc->value_buffer = osc->warp * 10000;
             break;
 
-          case TRACK_INPUT_WARP:
-            track->value_buffer = track->warp * 10000;
+          case OSCILLATOR_INPUT_PHASE:
+            osc->value_buffer = osc->phase * 10000;
             break;
 
-          case TRACK_INPUT_PHASE:
-            track->value_buffer = track->phase * 10000;
+          case OSCILLATOR_INPUT_STEREO_WIDTH:
+            osc->value_buffer = osc->stereo_width * 10000;
             break;
 
-          case TRACK_INPUT_SPREAD:
-            track->value_buffer = track->spread * 10000;
+          case OSCILLATOR_INPUT_VOLUME:
+            osc->value_buffer = osc->volume * 10000;
             break;
 
-          case TRACK_INPUT_STEREO_WIDTH:
-            track->value_buffer = track->stereo_width * 10000;
+          case OSCILLATOR_INPUT_SUSTAIN:
+            osc->value_buffer = osc->envelope.sustain * 10000;
             break;
 
-          case TRACK_INPUT_VOLUME:
-            track->value_buffer = track->volume * 10000;
+          case OSCILLATOR_INPUT_ATTACK:
+            osc->value_buffer = osc->envelope.attack * 100000;
             break;
 
-          case TRACK_INPUT_SUSTAIN:
-            track->value_buffer = track->envelope.sustain * 10000;
+          case OSCILLATOR_INPUT_DECAY:
+            osc->value_buffer = osc->envelope.decay * 100000;
             break;
 
-          case TRACK_INPUT_ATTACK:
-            track->value_buffer = track->envelope.attack * 100000;
-            break;
-
-          case TRACK_INPUT_DECAY:
-            track->value_buffer = track->envelope.decay * 100000;
-            break;
-
-          case TRACK_INPUT_RELEASE:
-            track->value_buffer = track->envelope.release * 100000;
+          case OSCILLATOR_INPUT_RELEASE:
+            osc->value_buffer = osc->envelope.release * 100000;
             break;
 
           default:;
@@ -1142,7 +1224,7 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
       }
     }
 
-    if (input_index == TRACK_INPUT_VOLUME)
+    if (input_index == OSCILLATOR_INPUT_VOLUME)
       next_y += header_offset;
     next_y += text_height;
   }
@@ -1150,29 +1232,25 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
   //  Draw text
   //
 
-  nvgBeginPath(saw_nvg);
+  next_y = text_height;
+
   nvgFontSize(saw_nvg, text_height - border);
   nvgFontFaceId(saw_nvg, saw_font_text);
   nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255));
 
+  char buf_wave[][100] = { "Sine",   "Saw up",   "Saw down",
+                           "Sqr up", "Sqr down", "Kick" };
   nvgText(saw_nvg, x0 + border * 2,
-          frame_height - y0 - height + text_height - border * 2,
-          title.values, title.values + title.size);
-  next_y = header_offset + text_height;
-
-  char buf_instr[][100] = { "Sine",   "Saw up",   "Saw down",
-                            "Sqr up", "Sqr down", "Kick" };
-  nvgText(saw_nvg, x0 + border * 2,
-          frame_height - y0 - height + next_y - border * 2, "Instr.",
+          frame_height - y0 - height + next_y - border * 2, "Wave",
           0);
-  if (track->instrument >= 0 && track->instrument < INSTRUMENT_COUNT)
+  if (osc->wave >= 0 && osc->wave < WAVE_COUNT)
     nvgText(saw_nvg, x0 + column_width + border * 2,
             frame_height - y0 - height + next_y - border * 2,
-            buf_instr[track->instrument], 0);
+            buf_wave[osc->wave], 0);
   next_y += text_height;
 
   char buf[100];
-  sprintf(buf, "%.3f", (f32) track->warp);
+  sprintf(buf, "%.3f", (f32) osc->warp);
   nvgText(saw_nvg, x0 + border * 2,
           frame_height - y0 - height + next_y - border * 2, "Warp",
           0);
@@ -1180,7 +1258,7 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
           frame_height - y0 - height + next_y - border * 2, buf, 0);
   next_y += text_height;
 
-  sprintf(buf, "%.3f", (f32) track->phase);
+  sprintf(buf, "%.3f", (f32) osc->phase);
   nvgText(saw_nvg, x0 + border * 2,
           frame_height - y0 - height + next_y - border * 2, "Phase",
           0);
@@ -1188,25 +1266,7 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
           frame_height - y0 - height + next_y - border * 2, buf, 0);
   next_y += text_height;
 
-  /*
-  sprintf(buf, "%d", (i32) track->unison);
-  nvgText(saw_nvg, x0 + border * 2,
-          frame_height - y0 - height + next_y - border * 2, "Unison",
-          0);
-  nvgText(saw_nvg, x0 + column_width + border * 2,
-          frame_height - y0 - height + next_y - border * 2, buf, 0);
-  next_y += text_height;
-
-  sprintf(buf, "%.3f", (f32) track->spread);
-  nvgText(saw_nvg, x0 + border * 2,
-          frame_height - y0 - height + next_y - border * 2, "Spread",
-          0);
-  nvgText(saw_nvg, x0 + column_width + border * 2,
-          frame_height - y0 - height + next_y - border * 2, buf, 0);
-  next_y += text_height;
-  */
-
-  sprintf(buf, "%.3f", (f32) track->stereo_width);
+  sprintf(buf, "%.3f", (f32) osc->stereo_width);
   nvgText(saw_nvg, x0 + border * 2,
           frame_height - y0 - height + next_y - border * 2, "Stereo",
           0);
@@ -1214,7 +1274,7 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
           frame_height - y0 - height + next_y - border * 2, buf, 0);
   next_y += text_height;
 
-  sprintf(buf, "%.3f", (f32) track->volume);
+  sprintf(buf, "%.3f", (f32) osc->volume);
   nvgText(saw_nvg, x0 + border * 2,
           frame_height - y0 - height + next_y - border * 2, "Volume",
           0);
@@ -1227,7 +1287,7 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
           "Envelope", 0);
   next_y += text_height;
 
-  sprintf(buf, "%.3f", (f32) track->envelope.sustain);
+  sprintf(buf, "%.3f", (f32) osc->envelope.sustain);
   nvgText(saw_nvg, x0 + column_width / 4 + border * 2,
           frame_height - y0 - height + next_y - border * 2, "Sustain",
           0);
@@ -1235,7 +1295,7 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
           frame_height - y0 - height + next_y - border * 2, buf, 0);
   next_y += text_height;
 
-  sprintf(buf, "%.1f ms", (f32) (track->envelope.attack * 1000));
+  sprintf(buf, "%.1f ms", (f32) (osc->envelope.attack * 1000));
   nvgText(saw_nvg, x0 + column_width / 4 + border * 2,
           frame_height - y0 - height + next_y - border * 2, "Attack",
           0);
@@ -1243,7 +1303,7 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
           frame_height - y0 - height + next_y - border * 2, buf, 0);
   next_y += text_height;
 
-  sprintf(buf, "%.1f ms", (f32) (track->envelope.decay * 1000));
+  sprintf(buf, "%.1f ms", (f32) (osc->envelope.decay * 1000));
   nvgText(saw_nvg, x0 + column_width / 4 + border * 2,
           frame_height - y0 - height + next_y - border * 2, "Decay",
           0);
@@ -1251,15 +1311,66 @@ static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
           frame_height - y0 - height + next_y - border * 2, buf, 0);
   next_y += text_height;
 
-  sprintf(buf, "%.1f ms", (f32) (track->envelope.release * 1000));
+  sprintf(buf, "%.1f ms", (f32) (osc->envelope.release * 1000));
   nvgText(saw_nvg, x0 + column_width / 4 + border * 2,
           frame_height - y0 - height + next_y - border * 2, "Release",
           0);
   nvgText(saw_nvg, x0 + column_width + border * 2,
           frame_height - y0 - height + next_y - border * 2, buf, 0);
   next_y += text_height;
+}
 
-  nvgFill(saw_nvg);
+static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
+                         i64 width, i64 height, str_t title) {
+  i64 frame_height = sapp_height();
+
+  i64 text_height   = 33;
+  i64 header_offset = 60;
+  i64 border        = 2;
+
+  nvgFontSize(saw_nvg, text_height - border);
+  nvgFontFaceId(saw_nvg, saw_font_text);
+  nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255));
+
+  nvgText(saw_nvg, x0 + border * 2,
+          frame_height - y0 - height + text_height - border * 2,
+          title.values, title.values + title.size);
+
+  if (track->instrument != INSTRUMENT_NONE) {
+    i64 x = x0 + width - (text_height * 3) / 2;
+    i64 y = frame_height - y0 - height;
+    i64 s = text_height;
+
+    b8 has_cursor = saw_mouse_x >= x && saw_mouse_y >= y &&
+                    saw_mouse_x < x + s && saw_mouse_y < y + s;
+
+    char xmark[] = "\uf00d";
+
+    nvgFontSize(saw_nvg, text_height);
+    nvgFontFaceId(saw_nvg, saw_font_icons);
+
+    if (has_cursor)
+      nvgFillColor(saw_nvg, nvgRGBA(240, 200, 100, 255));
+    else
+      nvgFillColor(saw_nvg, nvgRGBA(240, 200, 100, 160));
+
+    nvgText(saw_nvg, x + border, y + s - border, xmark,
+            xmark + (sizeof xmark - 1));
+
+    if (has_cursor && saw_lbutton_click)
+      track->instrument = INSTRUMENT_NONE;
+  }
+
+  switch (track->instrument) {
+    case INSTRUMENT_OSCILLATOR:
+      saw_ui_oscillator(&track->oscillator, x0, y0, width,
+                        height - header_offset - text_height);
+      break;
+
+    default:
+      saw_ui_choose_instrument(track, x0, y0, width,
+                               height - header_offset - text_height);
+  }
 }
 
 static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
@@ -1816,20 +1927,21 @@ static void saw_init(void) {
 
   for (i32 i = 0; i < TRACK_COUNT; i++)
     saw_tracks[i] = (saw_track_t) {
-      .instrument   = INSTRUMENT_SINE,
-      .warp         = .0,
-      .phase        = .0,
-      .unison       = 1,
-      .spread       = .1,
-      .stereo_width = .2,
-      .volume       = 1.,
-      .envelope     = {
-        .sustain    = .15,
-        .attack     = .007,
-        .decay      = .3,
-        .release    = .4,
-      },
-      .value_input  = TRACK_INPUT_NONE,
+      .instrument = INSTRUMENT_OSCILLATOR,
+      .oscillator  = {
+        .wave         = WAVE_SINE,
+        .warp         = .0,
+        .phase        = .0,
+        .stereo_width = .2,
+        .volume       = 1.,
+        .envelope     = {
+          .sustain    = .15,
+          .attack     = .007,
+          .decay      = .3,
+          .release    = .4,
+        },
+        .value_input  = OSCILLATOR_INPUT_NONE,
+      }
     };
 
   //  Determine the project file name
@@ -1957,30 +2069,38 @@ static void saw_init(void) {
     for (i64 i = 0; i < total_tracks; i++) {
       saw_track_t *track = saw_tracks + i;
 
-      i64 warp, phase, spread, stereo_width, volume, sustain, attack,
-          decay, release;
-
       SCAN_(" instrument %d", 1, &track->instrument);
-      SCAN_(" warp %lld", 1, &warp);
-      SCAN_(" phase %lld", 1, &phase);
-      SCAN_(" unison %d", 1, &track->unison);
-      SCAN_(" spread %lld", 1, &spread);
-      SCAN_(" stereo_width %lld", 1, &stereo_width);
-      SCAN_(" volume %lld", 1, &volume);
-      SCAN_(" sustain %lld", 1, &sustain);
-      SCAN_(" attack %lld", 1, &attack);
-      SCAN_(" decay %lld", 1, &decay);
-      SCAN_(" release %lld", 1, &release);
-
-      track->warp             = warp * 0.0001 - 1.;
-      track->phase            = phase * 0.0001;
-      track->spread           = spread * 0.0001;
-      track->stereo_width     = stereo_width * 0.0001;
-      track->volume           = volume * 0.0001;
-      track->envelope.sustain = sustain * 0.0001;
-      track->envelope.attack  = attack * 0.0001;
-      track->envelope.decay   = decay * 0.0001;
-      track->envelope.release = release * 0.0001;
+
+      switch (track->instrument) {
+        case INSTRUMENT_OSCILLATOR: {
+          saw_oscillator_t *osc = &track->oscillator;
+
+          i64 warp, phase, stereo_width, volume, sustain, attack,
+              decay, release;
+
+          SCAN_(" wave %d", 1, &osc->wave);
+          SCAN_(" warp %lld", 1, &warp);
+          SCAN_(" phase %lld", 1, &phase);
+
+          SCAN_(" stereo_width %lld", 1, &stereo_width);
+          SCAN_(" volume %lld", 1, &volume);
+          SCAN_(" sustain %lld", 1, &sustain);
+          SCAN_(" attack %lld", 1, &attack);
+          SCAN_(" decay %lld", 1, &decay);
+          SCAN_(" release %lld", 1, &release);
+
+          osc->warp             = warp * 0.0001 - 1.;
+          osc->phase            = phase * 0.0001;
+          osc->stereo_width     = stereo_width * 0.0001;
+          osc->volume           = volume * 0.0001;
+          osc->envelope.sustain = sustain * 0.0001;
+          osc->envelope.attack  = attack * 0.0001;
+          osc->envelope.decay   = decay * 0.0001;
+          osc->envelope.release = release * 0.0001;
+        } break;
+
+        default:;
+      }
     }
 
 #undef SCAN_
@@ -1990,6 +2110,10 @@ static void saw_init(void) {
 }
 
 static void saw_frame(void) {
+  //  TODO
+  //  Check how much time passed to see if we need to adjust the
+  //  buffer size.
+
   saw_audio_render();
 
   i64 frame_width  = sapp_width();
@@ -2031,112 +2155,104 @@ static void saw_frame(void) {
       saw_compose.ui_offset_y += saw_mouse_dy;
     }
 
-    if (saw_current_track != -1 &&
-        saw_tracks[saw_current_track].value_input !=
-            TRACK_INPUT_NONE) {
+    if (saw_current_track != -1) {
       //  Value input
       //
 
       saw_track_t *track = saw_tracks + saw_current_track;
 
-      track->value_buffer -= saw_shift_on  ? saw_mouse_dy * 300
-                             : saw_ctrl_on ? saw_mouse_dy
-                                           : saw_mouse_dy * 20;
-
-      //  TODO
-      //  Unify value input logic.
-
-      //  Change input value buffer for selected value.
-      //
-      switch (track->value_input) {
-        case TRACK_INPUT_INSTRUMENT:
-          track->instrument = (i64) (track->value_buffer * .002 +
-                                     0.5);
-          if (track->instrument < 0)
-            track->instrument = 0;
-          if (track->instrument >= INSTRUMENT_COUNT)
-            track->instrument = INSTRUMENT_COUNT - 1;
-          break;
-
-        case TRACK_INPUT_WARP:
-          track->warp = track->value_buffer * .0001;
-          if (track->warp < -1.)
-            track->warp = -1.;
-          if (track->warp > 1.)
-            track->warp = 1.;
-          break;
-
-        case TRACK_INPUT_PHASE:
-          track->value_buffer = (f64) ((i64) track->value_buffer %
-                                       10000);
-          while (track->value_buffer < 0)
-            track->value_buffer += 10000;
-          track->phase = track->value_buffer * .0001;
-          break;
-
-        case TRACK_INPUT_UNISON:
-          track->unison = (i64) (track->value_buffer * .01 + 0.5);
-          if (track->unison < 1)
-            track->unison = 1;
-          if (track->unison > UNISON_COUNT)
-            track->unison = UNISON_COUNT;
-          break;
-
-        case TRACK_INPUT_SPREAD:
-          track->spread = track->value_buffer * .0001;
-          if (track->spread < 0.)
-            track->spread = 0.;
-          if (track->spread > 1.)
-            track->spread = 1.;
-          break;
-
-        case TRACK_INPUT_STEREO_WIDTH:
-          track->stereo_width = track->value_buffer * .0001;
-          if (track->stereo_width < 0.)
-            track->stereo_width = 0.;
-          if (track->stereo_width > 2.)
-            track->stereo_width = 2.;
-          break;
-
-        case TRACK_INPUT_VOLUME:
-          track->volume = track->value_buffer * .0001;
-          if (track->volume < 0.)
-            track->volume = 0.;
-          if (track->volume > 2.)
-            track->volume = 2.;
-          break;
-
-        case TRACK_INPUT_SUSTAIN:
-          track->envelope.sustain = track->value_buffer * .0001;
-          if (track->envelope.sustain < 0.)
-            track->envelope.sustain = 0.;
-          if (track->envelope.sustain > 1.)
-            track->envelope.sustain = 1.;
-          break;
-
-        case TRACK_INPUT_ATTACK:
-          track->envelope.attack = track->value_buffer * .00001;
-          if (track->envelope.attack < 0.)
-            track->envelope.attack = 0.;
-          if (track->envelope.attack > 60.)
-            track->envelope.attack = 60.;
-          break;
+      switch (saw_tracks[saw_current_track].instrument) {
+        case INSTRUMENT_OSCILLATOR: {
+          saw_oscillator_t *osc = &track->oscillator;
 
-        case TRACK_INPUT_DECAY:
-          track->envelope.decay = track->value_buffer * .00001;
-          if (track->envelope.decay < 0.)
-            track->envelope.decay = 0.;
-          if (track->envelope.decay > 60.)
-            track->envelope.decay = 60.;
-          break;
+          if (osc->value_input == OSCILLATOR_INPUT_NONE)
+            break;
 
-        case TRACK_INPUT_RELEASE:
-          track->envelope.release = track->value_buffer * .00001;
-          if (track->envelope.release < 0.)
-            track->envelope.release = 0.;
-          if (track->envelope.release > 60.)
-            track->envelope.release = 60.;
-          break;
+          osc->value_buffer -= saw_shift_on  ? saw_mouse_dy * 300
+                               : saw_ctrl_on ? saw_mouse_dy
+                                             : saw_mouse_dy * 20;
+
+          //  TODO
+          //  Unify value input logic.
+
+          //  Change input value buffer for selected value.
+          //
+          switch (osc->value_input) {
+            case OSCILLATOR_INPUT_WAVE:
+              osc->wave = (i64) (osc->value_buffer * .002 + 0.5);
+              if (osc->wave < 0)
+                osc->wave = 0;
+              if (osc->wave >= WAVE_COUNT)
+                osc->wave = WAVE_COUNT - 1;
+              break;
+
+            case OSCILLATOR_INPUT_WARP:
+              osc->warp = osc->value_buffer * .0001;
+              if (osc->warp < -1.)
+                osc->warp = -1.;
+              if (osc->warp > 1.)
+                osc->warp = 1.;
+              break;
+
+            case OSCILLATOR_INPUT_PHASE:
+              osc->value_buffer = (f64) ((i64) osc->value_buffer %
+                                         10000);
+              while (osc->value_buffer < 0)
+                osc->value_buffer += 10000;
+              osc->phase = osc->value_buffer * .0001;
+              break;
+
+            case OSCILLATOR_INPUT_STEREO_WIDTH:
+              osc->stereo_width = osc->value_buffer * .0001;
+              if (osc->stereo_width < 0.)
+                osc->stereo_width = 0.;
+              if (osc->stereo_width > 2.)
+                osc->stereo_width = 2.;
+              break;
+
+            case OSCILLATOR_INPUT_VOLUME:
+              osc->volume = osc->value_buffer * .0001;
+              if (osc->volume < 0.)
+                osc->volume = 0.;
+              if (osc->volume > 2.)
+                osc->volume = 2.;
+              break;
+
+            case OSCILLATOR_INPUT_SUSTAIN:
+              osc->envelope.sustain = osc->value_buffer * .0001;
+              if (osc->envelope.sustain < 0.)
+                osc->envelope.sustain = 0.;
+              if (osc->envelope.sustain > 1.)
+                osc->envelope.sustain = 1.;
+              break;
+
+            case OSCILLATOR_INPUT_ATTACK:
+              osc->envelope.attack = osc->value_buffer * .00001;
+              if (osc->envelope.attack < 0.)
+                osc->envelope.attack = 0.;
+              if (osc->envelope.attack > 60.)
+                osc->envelope.attack = 60.;
+              break;
+
+            case OSCILLATOR_INPUT_DECAY:
+              osc->envelope.decay = osc->value_buffer * .00001;
+              if (osc->envelope.decay < 0.)
+                osc->envelope.decay = 0.;
+              if (osc->envelope.decay > 60.)
+                osc->envelope.decay = 60.;
+              break;
+
+            case OSCILLATOR_INPUT_RELEASE:
+              osc->envelope.release = osc->value_buffer * .00001;
+              if (osc->envelope.release < 0.)
+                osc->envelope.release = 0.;
+              if (osc->envelope.release > 60.)
+                osc->envelope.release = 60.;
+              break;
+
+            default:;
+          }
+        } break;
 
         default:;
       }
@@ -2314,25 +2430,32 @@ static void saw_cleanup(void) {
     saw_track_t *track = saw_tracks + i;
 
     fprintf(f, "instrument    %d\n", track->instrument);
-    fprintf(f, "warp          %lld\n",
-            (i64) ((1. + track->warp) * 10000 + .5));
-    fprintf(f, "phase         %lld\n",
-            (i64) (track->phase * 10000 + .5));
-    fprintf(f, "unison        %d\n", track->unison);
-    fprintf(f, "spread        %lld\n",
-            (i64) (track->spread * 10000 + .5));
-    fprintf(f, "stereo_width  %lld\n",
-            (i64) (track->stereo_width * 10000 + .5));
-    fprintf(f, "volume        %lld\n",
-            (i64) (track->volume * 10000 + .5));
-    fprintf(f, "sustain       %lld\n",
-            (i64) (track->envelope.sustain * 10000 + .5));
-    fprintf(f, "attack        %lld\n",
-            (i64) (track->envelope.attack * 10000 + .5));
-    fprintf(f, "decay         %lld\n",
-            (i64) (track->envelope.decay * 10000 + .5));
-    fprintf(f, "release       %lld\n\n",
-            (i64) (track->envelope.release * 10000 + .5));
+
+    switch (track->instrument) {
+      case INSTRUMENT_OSCILLATOR: {
+        saw_oscillator_t *osc = &track->oscillator;
+
+        fprintf(f, "wave          %d\n", osc->wave);
+        fprintf(f, "warp          %lld\n",
+                (i64) ((1. + osc->warp) * 10000 + .5));
+        fprintf(f, "phase         %lld\n",
+                (i64) (osc->phase * 10000 + .5));
+        fprintf(f, "stereo_width  %lld\n",
+                (i64) (osc->stereo_width * 10000 + .5));
+        fprintf(f, "volume        %lld\n",
+                (i64) (osc->volume * 10000 + .5));
+        fprintf(f, "sustain       %lld\n",
+                (i64) (osc->envelope.sustain * 10000 + .5));
+        fprintf(f, "attack        %lld\n",
+                (i64) (osc->envelope.attack * 10000 + .5));
+        fprintf(f, "decay         %lld\n",
+                (i64) (osc->envelope.decay * 10000 + .5));
+        fprintf(f, "release       %lld\n\n",
+                (i64) (osc->envelope.release * 10000 + .5));
+      } break;
+
+      default:;
+    }
   }
 
   fclose(f);
-- 
cgit v1.2.3