OH4mono

aputoy.c at tip
Login

File pending/sandbox/gameboy/research/audio/aputoy.c from the latest check-in


/* aputoy.c - GUI for playing with the GameBoy APU.
**
** This tool allows for configuring all the options for each channel and
** playing back samples. Channels where the length or sweep is active in a way
** that would cause the channel to automatically shut off can activate a lock
** which immediately reactivates the channel when it is shut off, allowing for
** the channel to be replayed easily. Register values can be easily copied to
** the clipboard if you find a sound you like.
*/
#include <errno.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

/* Internal */
#include "apu.h"

/* SDL */
#include <SDL.h>

/* stb */
#if 0
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"
#endif

/* Common */
#define COMMON_ARENA_IMPLEMENTATION
#include "arena.h"
#include "sdl_helpers.h"
#include "fixed_font.h"

/* Emscripten */
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

#define PERM_ARENA_SIZE 1024*1024*1024
#define WINDOW_WIDTH 1024
#define WINDOW_HEIGHT 768

#define min(A, B) (((A) < (B)) ? (A) : (B))
#define max(A, B) (((A) > (B)) ? (A) : (B))

typedef enum Text_Alignment {
  TEXTALIGNMENT_left,
  TEXTALIGNMENT_center,
  TEXTALIGNMENT_right
} Text_Alignment;

typedef enum Pan {
  PAN_left,
  PAN_center,
  PAN_right
} Pan;

typedef enum Sprite_Type {
  SPRITETYPE_power,
  SPRITETYPE_square_wave,
  SPRITETYPE_waveform,
  SPRITETYPE_noise,
  SPRITETYPE_up,
  SPRITETYPE_down,
  SPRITETYPE_play,
  SPRITETYPE_locked,
  SPRITETYPE_unlocked,
  SPRITETYPE_tap,
  SPRITETYPE_clipboard,
  SPRITETYPE_COUNT
} Sprite_Type;

typedef enum RectCut_Side {
  RECTCUTSIDE_left,
  RECTCUTSIDE_right,
  RECTCUTSIDE_top,
  RECTCUTSIDE_bottom
} RectCut_Side;

typedef struct RectCut {
  SDL_Rect *rect;
  RectCut_Side side;
} RectCut;

typedef struct String {
  char     *data;
  uint32_t size; /* Total size of data */
  uint32_t len;  /* Size of data used */
} String;

typedef struct Button_State {
  bool just_pressed;
  bool just_released;
  bool down;
} Button_State;

typedef struct Input_State {
  char     text[5];
  uint32_t integer;
  bool     is_editing;
} Input_State;

typedef struct List_Option {
  char   *name;
  int32_t value;
} List_Option;

typedef struct Sprite_List_Option {
  Sprite_Type sprite;
  int32_t     value;
} Sprite_List_Option;

typedef struct Editor_State {
  bool is_running;
  Arena        *a;
  SDL_Renderer *r;
  SDL_Window   *w;
  SDL_Texture  *sprites;
  SDL_AudioDeviceID dev;

  struct {
    float size;
    int32_t ascent;
    int32_t descent;
    int32_t line_gap;
    SDL_Texture *tex;
#if 0
    stbtt_bakedchar *cdata;
#else
    Fixed_Font_Kerning *cdata;
#endif
  } font;

  struct {
    SDL_Point pos;
    SDL_Point prev_pos;
    Button_State left;
  } mouse;

  struct {
    bool has_input;
    char text[32];
  } text_input;

  struct {
    int32_t l_volume;
    int32_t r_volume;

    struct {
      bool    is_locked;
      int32_t wavelength;
      int32_t duty_cycle;
      int32_t env_dir;
      int32_t env_pace;
      int32_t env_start_vol;
      int32_t sweep_dir;
      int32_t sweep_pace;
      int32_t sweep_slope;
      int32_t length;
    } ch1;

    struct {
      bool    is_locked;
      int32_t wavelength;
      int32_t duty_cycle;
      int32_t env_dir;
      int32_t env_pace;
      int32_t env_start_vol;
      int32_t length;
    } ch2;

    struct {
      bool    is_locked;
      int32_t wavelength;
      int32_t length;
      int32_t volume;
      Input_State samples[16];
    } ch3;

    struct {
      bool    is_locked;
      int32_t shift;
      int32_t divisor;
      int32_t width;
      int32_t length;
      int32_t env_dir;
      int32_t env_pace;
      int32_t env_start_vol;
    } ch4;

    APU apu;
  } internal;
} Editor_State;

static SDL_Rect sprite_table[SPRITETYPE_COUNT] = {
  [SPRITETYPE_power]       = {0, 0, 16, 16},
  [SPRITETYPE_square_wave] = {16, 0, 64, 16},
  [SPRITETYPE_waveform]    = {16, 16, 64, 16},
  [SPRITETYPE_noise]       = {16, 32, 64, 16},
  [SPRITETYPE_up]          = {80, 0, 16, 16},
  [SPRITETYPE_down]        = {96, 0, 16, 16},
  [SPRITETYPE_play]        = {80, 16, 16, 16},
  [SPRITETYPE_locked]      = {80, 32, 16, 16},
  [SPRITETYPE_unlocked]    = {96, 32, 16, 16},
  [SPRITETYPE_tap]         = {0, 16, 16, 16},
  [SPRITETYPE_clipboard]   = {112, 16, 16, 16},
};

static List_Option duty_cycle_options[4] = {
  [0] = {"12.5%", DUTYCYCLE_12_5},
  [1] = {"25%", DUTYCYCLE_25},
  [2] = {"50%", DUTYCYCLE_50},
  [3] = {"75%", DUTYCYCLE_75}
};

static List_Option lfsr_width_options[2] = {
  [0] = {"15-bit", LFSRWIDTH_15},
  [1] = {"7-bit", LFSRWIDTH_7},
};

static List_Option waveform_volume_options[4] = {
  [0] = {"0%", WAVEFORMVOLUME_0},
  [1] = {"25%", WAVEFORMVOLUME_25},
  [2] = {"50%", WAVEFORMVOLUME_50},
  [3] = {"100%", WAVEFORMVOLUME_100},
};

static Sprite_List_Option direction_options[2] = {
  [0] = {SPRITETYPE_up, DIRECTION_up},
  [1] = {SPRITETYPE_down, DIRECTION_down},
};

SDL_Rect
expand(SDL_Rect *rect, int32_t a)
{
  return (SDL_Rect){
    rect->x - a,
    rect->y - a,
    rect->w + 2*a,
    rect->h + 2*a,
  };
}

SDL_Rect
contract(SDL_Rect *rect, int32_t a)
{
  return (SDL_Rect){
    rect->x + a,
    rect->y + a,
    rect->w - 2*a,
    rect->h - 2*a
  };
}

SDL_Rect
cut_left(SDL_Rect *rect, int32_t a)
{
  int32_t old_x = rect->x;
  rect->x = min(rect->x + a, rect->x + rect->w);
  rect->w = max(0, rect->w - a);
  return (SDL_Rect){ old_x, rect->y, a, rect->h };
}

SDL_Rect
cut_right(SDL_Rect *rect, int32_t a)
{
  rect->w = max(0, rect->w - a);
  return (SDL_Rect){ rect->x + rect->w, rect->y, a, rect->h };
}

SDL_Rect
cut_top(SDL_Rect *rect, int32_t a)
{
  int32_t old_y = rect->y;
  rect->y = min(rect->y + a, rect->y + rect->h);
  rect->h = max(0, rect->h - a);
  return (SDL_Rect){ rect->x, old_y, rect->w, a };
}

SDL_Rect
cut_bottom(SDL_Rect *rect, int32_t a)
{
  rect->h = max(0, rect->h - a);
  return (SDL_Rect){ rect->x, rect->y + rect->h, rect->w, a };
}

#define cut_left_percent(RECT, PCT) cut_left(RECT, (RECT)->w * (PCT))
#define cut_right_percent(RECT, PCT) cut_right(RECT, (RECT)->w * (PCT))
#define cut_top_percent(RECT, PCT) cut_top(RECT, (RECT)->h * (PCT))
#define cut_bottom_percent(RECT, PCT) cut_bottom(RECT, (RECT)->h * (PCT))

RectCut
rectcut(SDL_Rect *rect, RectCut_Side side)
{
  return (RectCut){ .rect = rect, .side = side };
}

SDL_Rect
rectcut_cut(RectCut rectcut, int32_t a)
{
  switch (rectcut.side)
  {
  case RECTCUTSIDE_left: return cut_left(rectcut.rect, a);
  case RECTCUTSIDE_right: return cut_right(rectcut.rect, a);
  case RECTCUTSIDE_top: return cut_top(rectcut.rect, a);
  case RECTCUTSIDE_bottom: return cut_bottom(rectcut.rect, a);
  default: abort();
  }
}

void
draw_line(Editor_State *editor, SDL_Point start, SDL_Point stop, uint8_t r, uint8_t g, uint8_t b)
{
  SDL_SetRenderDrawColor(editor->r, r, g, b, SDL_ALPHA_OPAQUE);
  SDL_RenderDrawLine(editor->r, start.x, start.y, stop.x, stop.y);
}

void
draw_rect_unfilled(Editor_State *editor, SDL_Rect *rect, uint8_t r, uint8_t g, uint8_t b)
{
  SDL_SetRenderDrawColor(editor->r, r, g, b, SDL_ALPHA_OPAQUE);
  SDL_RenderDrawRect(editor->r, rect);
}

void
draw_rect_filled(Editor_State *editor, SDL_Rect *rect, uint8_t r, uint8_t g, uint8_t b)
{
  SDL_SetRenderDrawColor(editor->r, r, g, b, SDL_ALPHA_OPAQUE);
  SDL_RenderFillRect(editor->r, rect);
}

SDL_Rect
rectcut_cut_percent(RectCut rectcut, float percent)
{
  switch (rectcut.side)
  {
  case RECTCUTSIDE_left: return cut_left_percent(rectcut.rect, percent);
  case RECTCUTSIDE_right: return cut_right_percent(rectcut.rect, percent);
  case RECTCUTSIDE_top: return cut_top_percent(rectcut.rect, percent);
  case RECTCUTSIDE_bottom: return cut_bottom_percent(rectcut.rect, percent);
  default: abort();
  }
}

int32_t
text_width(Editor_State *editor, char *text)
{
  int32_t result = 0;
  while (*text != 0)
  {
    if ((uint8_t)*text >= 32 && (uint8_t)*text <= 128)
    {
#if 0
      stbtt_bakedchar *ch = editor->font.cdata + ((uint8_t)*text - 32);
#else
      Fixed_Font_Kerning *ch = editor->font.cdata + ((uint8_t)*text - 32);
#endif
      result += ch->xadvance;
    }
    ++text;
  }
  return(result);
}

void
draw_sprite(Editor_State *editor, SDL_Texture *texture, SDL_Rect *src, SDL_Rect *dst)
{
  SDL_RenderCopy(editor->r, texture, src, dst);
}

void
draw_text(Editor_State *editor, char *text, int32_t x, int32_t y)
{
  SDL_Rect tracking_rect = {
    .x = x,
    .y = y + editor->font.size,
    .w = 0,
    .h = 0
  };
  SDL_Rect dest_rect = {
    .x = tracking_rect.x,
    .y = tracking_rect.y,
    .w = 0,
    .h = 0
  };
  SDL_Rect src_rect = {0};

  while (*text != 0)
  {
    if ((uint8_t)*text >= 32 && (uint8_t)*text <= 128)
    {
#if 0
      stbtt_bakedchar *ch = editor->font.cdata + ((uint8_t)*text - 32);
#else
      Fixed_Font_Kerning *ch = editor->font.cdata + ((uint8_t)*text - 32);
#endif
      src_rect.x = ch->x0;
      src_rect.y = ch->y0;
      src_rect.w = (ch->x1 - ch->x0);
      src_rect.h = (ch->y1 - ch->y0);
      dest_rect.x = tracking_rect.x + ch->xoff;
      dest_rect.y = tracking_rect.y + ch->yoff;
      dest_rect.w = src_rect.w;
      dest_rect.h = src_rect.h;
      SDL_RenderCopy(editor->r, editor->font.tex, &src_rect, &dest_rect);
      tracking_rect.x += ch->xadvance;
    }
    ++text;
  }
}

void
draw_label(Editor_State *editor, SDL_Rect *rect, char *text, Text_Alignment alignment, float padding_ems)
{
  int32_t text_w = text_width(editor, text);
  int32_t padding_px = padding_ems * editor->font.size;

  /* TODO: Move this into a method for calculating rectangles for a given label
  ** text. Figure out how to work this into an overall layout system.
  */
#if 0
  /* Prevent funkiness with oversmall rectangles by enforcing the minimum size
  ** to contain the text using the given font.
  **
  ** NOTE: If you set width and height in rect to 0 they will be automatically
  ** set to the minimum size rect for you.
  */
  rect->w = max(text_w + 2*padding_px, rect->w);
  rect->h = max(editor->font.size + 2*padding_px, rect->h);
#endif

  SDL_Point pos = {
    .x = 0,
    /* Center on Y by going to the rect center then subtracting off 1/2 the
    ** font height and 1/2 the descent (which is negative).
    */
    .y = rect->y + (rect->h / 2) - (editor->font.size / 2) + (editor->font.descent / 2),
  };

  switch (alignment)
  {
  case TEXTALIGNMENT_left:
    pos.x = rect->x + padding_px;
    break;
  case TEXTALIGNMENT_center:
    pos.x = rect->x + (rect->w / 2) - (text_w / 2);
    break;
  case TEXTALIGNMENT_right:
    pos.x = rect->x + rect->w - padding_px - text_w;
    break;
  default: abort();
  }

  SDL_SetClipRect(SDL_GetWindowSurface(editor->w), rect);
  draw_text(editor, text, pos.x, pos.y);
  SDL_SetClipRect(SDL_GetWindowSurface(editor->w), rect);
}

bool
button(Editor_State *editor, SDL_Rect *rect)
{
  bool is_pressed = false;

  /* Draw border and contract the rectangle to produce the inside. */
  draw_rect_unfilled(editor, rect, 0, 0, 0);
  SDL_Rect inner_rect = contract(rect, 1);

  if (SDL_PointInRect(&editor->mouse.pos, &inner_rect))
  {
    if (editor->mouse.left.down)
    {
      draw_rect_filled(editor, &inner_rect, 99, 155, 255);
    }
    else
    {
      if(editor->mouse.left.just_released)
      {
	is_pressed = true;
      }

      draw_rect_filled(editor, &inner_rect, 91, 110, 225);
    }
  }
  else
  {
    draw_rect_filled(editor, &inner_rect, 63, 63, 116);
  }

  return(is_pressed);
}

bool
selectable_button(Editor_State *editor, SDL_Rect *rect, bool is_selected)
{
  bool is_pressed = false;

  /* Draw border and contract the rectangle to produce the inside. */
  draw_rect_unfilled(editor, rect, 0, 0, 0);
  SDL_Rect inner_rect = contract(rect, 1);

  if (SDL_PointInRect(&editor->mouse.pos, &inner_rect))
  {
    if (editor->mouse.left.down)
    {
      draw_rect_filled(editor, &inner_rect, 99, 155, 255);
    }
    else
    {
      if(editor->mouse.left.just_released)
      {
	is_pressed = true;
      }

      draw_rect_filled(editor, &inner_rect, 91, 110, 225);
    }
  }
  else
  {
    if (is_selected)
    {
      draw_rect_filled(editor, &inner_rect, 91, 110, 225);
    }
    else
    {
      draw_rect_filled(editor, &inner_rect, 63, 63, 116);
    }
  }

  return(is_pressed);
}

bool
text_button(Editor_State *editor, SDL_Rect *rect, char *text, Text_Alignment alignment, float padding_ems)
{
  bool is_pressed = button(editor, rect);
  draw_label(editor, rect, text, alignment, padding_ems);
  return(is_pressed);
}

bool
selectable_text_button(Editor_State *editor, SDL_Rect *rect, char *text, Text_Alignment alignment, float padding_ems, bool is_selected)
{
  bool is_pressed = selectable_button(editor, rect, is_selected);
  draw_label(editor, rect, text, alignment, padding_ems);
  return(is_pressed);
}

bool
sprite_button(Editor_State *editor, SDL_Rect *rect, Sprite_Type sprite)
{
  bool is_pressed = button(editor, rect);

  /* Center the sprite in the button */
  SDL_Rect *sprite_rect = sprite_table + sprite;
  SDL_Rect dest_rect = {
    rect->x + (rect->w / 2) - (sprite_rect->w / 2),
    rect->y + (rect->h / 2) - (sprite_rect->h / 2),
    sprite_rect->w,
    sprite_rect->h
  };
  draw_sprite(editor, editor->sprites, sprite_rect, &dest_rect);

  return(is_pressed);
}

bool
selectable_sprite_button(Editor_State *editor, SDL_Rect *rect, Sprite_Type sprite, bool is_selected)
{
  bool is_pressed = selectable_button(editor, rect, is_selected);

  /* Center the sprite in the button */
  SDL_Rect *sprite_rect = sprite_table + sprite;
  SDL_Rect dest_rect = {
    rect->x + (rect->w / 2) - (sprite_rect->w / 2),
    rect->y + (rect->h / 2) - (sprite_rect->h / 2),
    sprite_rect->w,
    sprite_rect->h
  };
  draw_sprite(editor, editor->sprites, sprite_rect, &dest_rect);

  return(is_pressed);
}

void
header(Editor_State *editor, SDL_Rect *rect, char* text)
{
  draw_label(editor, rect, text, TEXTALIGNMENT_left, 0.25f);
}

bool
toggle_sprite_button(Editor_State *editor, SDL_Rect *rect, Sprite_Type on_spr, Sprite_Type off_spr, bool *value)
{
  bool is_changed = false;

  draw_rect_unfilled(editor, rect, 0, 0, 0);
  SDL_Rect inner_rect = contract(rect, 1);
  SDL_Rect *sprite_rect = NULL;

  if (SDL_PointInRect(&editor->mouse.pos, rect))
  {
    if (*value)
    {
      draw_rect_filled(editor, &inner_rect, 217, 160, 102);
    }
    else
    {
      draw_rect_filled(editor, &inner_rect, 91, 110, 225);
    }

    if (editor->mouse.left.just_pressed)
    {
      *value = !*value;
      is_changed = true;
    }
  }
  else
  {
    if (*value)
    {
      draw_rect_filled(editor, &inner_rect, 223, 113, 38);
    }
    else
    {
      draw_rect_filled(editor, &inner_rect, 50, 60, 57);
    }
  }

  if (*value)
  {
    sprite_rect = sprite_table + on_spr;
  }
  else
  {
    sprite_rect = sprite_table + off_spr;
  }

  SDL_Rect dest_rect = {
    rect->x + (rect->w / 2) - (sprite_rect->w / 2),
    rect->y + (rect->h / 2) - (sprite_rect->h / 2),
    sprite_rect->w,
    sprite_rect->h
  };
  draw_sprite(editor, editor->sprites, sprite_rect, &dest_rect);

  return(is_changed);
}

bool
range_selector(Editor_State *editor, SDL_Rect *rect, int32_t *value, int32_t min, int32_t max, char *fmt)
{
  static char numtxt[128];
  bool is_changed = false;

  draw_rect_unfilled(editor, rect, 0, 0, 0);
  *rect = contract(rect, 1);

  draw_rect_filled(editor, rect, 50, 60, 57);

  float v = *value;
  int32_t last = *value;

  SDL_Rect knob_rect = contract(rect, 3);
  rect->w = knob_rect.w;
  knob_rect.w = 20;
  knob_rect.x += (v - min) * (rect->w - knob_rect.w) / (max - min);

  if (SDL_PointInRect(&editor->mouse.pos, rect))
  {
    if (editor->mouse.left.down)
    {
      v = ((float)min + ((float)(editor->mouse.pos.x - rect->x) * (max - min + 1)) / (float)rect->w);
    }
  }

  button(editor, &knob_rect);

  v = max(min, v);
  v = min(max, v);
  *value = v;

  if (last != *value)
  {
    is_changed = true;
  }

  snprintf(numtxt, 128, fmt, *value);
  draw_label(editor, rect, numtxt, TEXTALIGNMENT_center, 0);

  return(is_changed);
}

bool
pan_selector(Editor_State *editor, SDL_Rect *rect, int32_t *value)
{
  bool is_changed = false;
  int32_t min = PAN_left;
  int32_t max = PAN_right;

  {
    int32_t l_width = text_width(editor, "L");
    cut_left(rect, l_width);
    SDL_Rect l_rect = cut_left(rect, l_width);
    draw_label(editor, &l_rect, "L", TEXTALIGNMENT_center, 0);
  }

  {
    int32_t r_width = text_width(editor, "R");
    cut_right(rect, r_width);
    SDL_Rect r_rect = cut_right(rect, r_width);
    draw_label(editor, &r_rect, "R", TEXTALIGNMENT_center, 0);
  }

  draw_line(editor, (SDL_Point){rect->x + 3, rect->y + rect->h / 2}, (SDL_Point){rect->x + rect->w - 3, rect->y + rect->h / 2}, 50, 60, 57);

  draw_line(editor, (SDL_Point){rect->x + (rect->w / 2), rect->y + (rect->h / 2) - 11}, (SDL_Point){rect->x + (rect->w / 2), rect->y + (rect->h / 2) - 2}, 50, 60, 57);

  float v = *value;
  int32_t last = *value;

  SDL_Rect knob_rect = contract(rect, 3);
  rect->w = knob_rect.w;
  knob_rect.w = 10;
  knob_rect.x += (v - min) * (rect->w - knob_rect.w) / (max - min);

  if (SDL_PointInRect(&editor->mouse.pos, rect))
  {
    if (editor->mouse.left.down)
    {
      v = ((float)min + ((float)(editor->mouse.pos.x - rect->x) * (max - min + 1)) / (float)rect->w);
    }
  }

  button(editor, &knob_rect);

  v = max(min, v);
  v = min(max, v);
  *value = v;

  if (last != *value)
  {
    is_changed = true;
  }

  return(is_changed);
}

bool
options(Editor_State *editor, SDL_Rect *rect, List_Option *options, int32_t n_options, int32_t *value)
{
  bool item_changed = false;
  int32_t item_btn_w = ((float)rect->w / n_options);

  SDL_Rect btn_rect = {rect->x, rect->y, item_btn_w, rect->h};

  for (int32_t i = 0; i < n_options; ++i)
  {
    bool is_selected = (*value == options[i].value);
    if (selectable_text_button(editor, &btn_rect, options[i].name, TEXTALIGNMENT_center, 0.25f, is_selected))
    {
      *value = options[i].value;
      item_changed = true;
    }

    btn_rect.x += btn_rect.w;
  }

  return(item_changed);
}

bool
sprite_options(Editor_State *editor, SDL_Rect *rect, Sprite_List_Option *options, int32_t n_options, int32_t *value)
{
   bool item_changed = false;
  int32_t item_btn_w = ((float)rect->w / n_options);

  SDL_Rect btn_rect = {rect->x, rect->y, item_btn_w, rect->h};

  for (int32_t i = 0; i < n_options; ++i)
  {
    bool is_selected = (*value == options[i].value);
    if (selectable_sprite_button(editor, &btn_rect, options[i].sprite, is_selected))
    {
      *value = options[i].value;
      item_changed = true;
    }

    btn_rect.x += btn_rect.w;
  }

  return(item_changed);
}

bool
text_input(Editor_State *editor, SDL_Rect *rect, Input_State *input)
{
  bool was_edited = false;

  if (SDL_PointInRect(&editor->mouse.pos, rect))
  {
    /* Trigger editing by clicking in the text field. */
    if (editor->mouse.left.just_pressed)
    {
      input->is_editing = true;
      input->text[0] = '\0';
    }
  }
  else
  {
    /* Take focus by clicking outside the text field. */
    if (editor->mouse.left.just_pressed)
    {
      input->is_editing = false;
    }
  }

  draw_rect_unfilled(editor, rect, 0, 0, 0);
  SDL_Rect inner_rect = contract(rect, 1);

  if (input->is_editing)
  {
    if (editor->text_input.has_input)
    {
      /* Reject the input if its not full of digits. */
      bool is_numeric = true;
      char *ch = editor->text_input.text;
      while (*ch)
      {
	if (!(*ch >= '0' && *ch <= '9') &&
	    !(*ch >= 'a' && *ch <= 'f') &&
	    !(*ch >= 'A' && *ch <= 'F'))
	{
	  SDL_Log("%c", *ch);
	  is_numeric = false;
	  break;
	}
	++ch;
      }

      if (is_numeric)
      {
	strncat(input->text, editor->text_input.text, 5);
	was_edited = true;
      }
    }
    
    draw_rect_filled(editor, &inner_rect, 155, 173, 183);
  }
  else
  {
    draw_rect_filled(editor, &inner_rect, 132, 126, 135);
  }

  draw_label(editor, &inner_rect, input->text, TEXTALIGNMENT_right, 0.25f);
  
  return(was_edited);
}

bool
hex_input(Editor_State *editor, SDL_Rect *rect, Input_State *input)
{
  bool was_edited = text_input(editor, rect, input);

  if (was_edited)
  {
    /* Parse input as hex */
    errno = 0;
    input->integer = strtol(input->text, NULL, 16);
    if (errno == EINVAL || errno == ERANGE)
    {
      SDL_Log("error: invalid integer value %s", input->text);
      input->text[0] = '\0';
      input->integer = 0;
    }
  }

  return(was_edited);
}

bool
hex_input_u8(Editor_State *editor, SDL_Rect *rect, Input_State *input)
{
  bool was_edited = hex_input(editor, rect, input);

  if (was_edited)
  {
    if (strlen(input->text) > 2)
    {
      input->text[2] = '\0';
    }

    input->integer &= 0xFF;
  }

  return(was_edited);
}

void
draw_reg_value(Editor_State *editor, SDL_Rect *rect, uint8_t value)
{
  static char buf[128];

  snprintf(buf, 128, "%02X", value);

  draw_rect_unfilled(editor, rect, 0, 0, 0);

  SDL_Rect inner_rect = contract(rect, 1);
  draw_rect_filled(editor, &inner_rect, 132, 126, 135);

  SDL_Rect clipboard_rect = cut_right(&inner_rect, 16 + 6);
  if (sprite_button(editor, &clipboard_rect, SPRITETYPE_clipboard))
  {
    SDL_SetClipboardText(buf);
  }

  draw_label(editor, &inner_rect, buf, TEXTALIGNMENT_left, 0.25f);
}

void
reg_value(Editor_State *editor, SDL_Rect *rect, char *regname, uint8_t value)
{
  SDL_Rect reg_rect = cut_left(rect, text_width(editor, regname) + 6);
  draw_label(editor, &reg_rect, regname, TEXTALIGNMENT_center, 0.25f);

  SDL_Rect val_rect = cut_left(rect, 3 * editor->font.size + 6);
  draw_reg_value(editor, &val_rect, value);
}

void
editor_init(Editor_State *editor)
{
  /* Load font and create simple texture atlas. */
#if 0
  /* TODO: Make this an includable fixed font as binary data. */
  FILE* font_file = fopen("../../ext/PixelCarnageMonoNull.ttf", "rb");
  if (font_file)
  {
    uint32_t tex_x = 128;
    uint32_t tex_y = 100;
    editor->font.size = 16.0f;
    editor->font.cdata = push_array(editor->a, stbtt_bakedchar, 96);
    editor->font.tex = SDL_CreateTexture(editor->r, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, tex_x, tex_y);
    SDL_SetTextureBlendMode(editor->font.tex, SDL_BLENDMODE_BLEND); /* Do alpha blending. */

    uint64_t old_pos = arena_get_pos(editor->a);
    {
      fseek(font_file, 0, SEEK_END);
      uint64_t font_file_size = ftell(font_file);
      rewind(font_file);
      uint8_t *font_data = push_array(editor->a, uint8_t, font_file_size);
      fread(font_data, font_file_size, 1, font_file);
      fclose(font_file);

      stbtt_fontinfo info;
      stbtt_InitFont(&info, font_data, 0);
      stbtt_GetFontVMetrics(&info, &editor->font.ascent, &editor->font.descent, &editor->font.line_gap);

      float scalar = stbtt_ScaleForPixelHeight(&info, editor->font.size);
      editor->font.ascent *= scalar;
      editor->font.descent *= scalar;
      editor->font.line_gap *= scalar;

      uint8_t *tmp_bitmap = push_array(editor->a, uint8_t, tex_x*tex_y);
      stbtt_BakeFontBitmap(font_data, 0, editor->font.size, tmp_bitmap, tex_x, tex_y, 32, 96, editor->font.cdata);

      uint8_t *pix = 0;
      int32_t pitch = 0;
      
      SDL_LockTexture(editor->font.tex, NULL, (void*)&pix, &pitch);
      for (uint32_t y = 0; y < tex_y; ++y)
      {
	uint32_t *line = (uint32_t*)(pix + (y * pitch));
	for (uint32_t x = 0; x < tex_x; ++x)
	{
	  uint8_t val = tmp_bitmap[x + (y * tex_x)];
	  *line = 0xFFFFFF00 | val;
	  ++line;
	}
      }
      SDL_UnlockTexture(editor->font.tex);
    }
    arena_set_pos_back(editor->a, old_pos);
  }
  else
  {
    fprintf(stderr, "error: font open failed: %s\n", strerror(errno));
  }
#else
  uint32_t tex_x = FIXED_FONT_BITMAP_W;
  uint32_t tex_y = FIXED_FONT_BITMAP_H;
  uint8_t *pix = 0;
  int32_t pitch = 0;

  editor->font.size = FIXED_FONT_HEIGHT_PIX;
  editor->font.ascent = FIXED_FONT_ASCENT;
  editor->font.descent = FIXED_FONT_DESCENT;
  editor->font.line_gap = FIXED_FONT_LINE_GAP;
  editor->font.cdata = fixed_font_kerning;

  editor->font.tex = SDL_CreateTexture(editor->r, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, tex_x, tex_y);
  SDL_SetTextureBlendMode(editor->font.tex, SDL_BLENDMODE_BLEND); /* Do alpha blending. */

  SDL_LockTexture(editor->font.tex, NULL, (void*)&pix, &pitch);
  for (uint32_t y = 0; y < tex_y; ++y)
  {
    uint32_t *line = (uint32_t*)(pix + (y * pitch));
    for (uint32_t x = 0; x < tex_x; ++x)
    {
      uint8_t val = fixed_font_bitmap[x + (y * tex_x)];
      *line = 0xFFFFFF00 | val;
      ++line;
    }
  }
  SDL_UnlockTexture(editor->font.tex);
#endif

  /* Load sprites */
#ifdef __EMSCRIPTEN__
  editor->sprites = load_png(editor->r, "assets/aputoy-sprites.png");
#else
  editor->sprites = load_png(editor->r, "../../misc/aputoy-sprites.png");
#endif

  /* Initialize values */
  editor->internal.l_volume = 7;
  editor->internal.r_volume = 7;

  editor->internal.apu.is_on = true;
  editor->internal.apu.vol = 0xFF;
  editor->internal.apu.pan = 0xFF;
  memset(&editor->internal.ch1, 0, sizeof(editor->internal.ch1));
  apu_reset(&editor->internal.apu);

  for (uint32_t i = 0; i < 16; ++i)
  {
    editor->internal.ch3.samples[i].text[0] = '0';
    editor->internal.ch3.samples[i].text[1] = '0';
  }
}

void
editor_shutdown(Editor_State *editor)
{
  SDL_DestroyTexture(editor->sprites);
  SDL_DestroyTexture(editor->font.tex);
}

void
editor_ch1_update(Editor_State *editor, SDL_Rect ch_rect, int32_t padding)
{
  /* Draw border on RHS */
  draw_line(editor, (SDL_Point){ch_rect.x + ch_rect.w, ch_rect.y}, (SDL_Point){ch_rect.x + ch_rect.w, ch_rect.y + ch_rect.h}, 0, 0, 0);

  /* Top section */
  {
    /* CH text */
    SDL_Rect ch_top_rect = cut_top(&ch_rect, 2*editor->font.size);
    int32_t ch_text_width = text_width(editor, "CH1") + 2 * padding;
    SDL_Rect ch_label = cut_left(&ch_top_rect, ch_text_width);
    draw_label(editor, &ch_label, "CH1", TEXTALIGNMENT_left, 0.25f);

    SDL_Rect toggle_rect = cut_right_percent(&ch_top_rect, 0.20f);
    toggle_rect = contract(&toggle_rect, padding);
    if (toggle_sprite_button(editor, &toggle_rect, SPRITETYPE_power, SPRITETYPE_power, &editor->internal.apu.ch1.is_on))
    {
      if (editor->internal.ch1.is_locked)
      {
	editor->internal.ch1.is_locked = false;
      }

      square_wave_channel_reset(&editor->internal.apu.ch1);
    }

    SDL_Rect lock_rect = cut_right(&ch_top_rect, toggle_rect.w);
    lock_rect = contract(&lock_rect, padding);
    lock_rect.x += padding;
    toggle_sprite_button(editor, &lock_rect, SPRITETYPE_locked, SPRITETYPE_unlocked, &editor->internal.ch1.is_locked);

    /* CH wave sprite */
    SDL_Rect sq_wave_src_rect = sprite_table[SPRITETYPE_square_wave];
    SDL_Rect sq_wave_rect = cut_left(&ch_top_rect, sq_wave_src_rect.w);
    sq_wave_rect.y += (sq_wave_rect.h - sq_wave_src_rect.h) / 2;
    sq_wave_rect.h = sq_wave_src_rect.h;
    draw_sprite(editor, editor->sprites, &sq_wave_src_rect, &sq_wave_rect);

    /* Pan slider */
    static int32_t pan_value = PAN_center;
    if (pan_selector(editor, &ch_top_rect, &pan_value))
    {
      /* Update CH1 panning */
      uint8_t lpan = (pan_value == PAN_center || pan_value == PAN_left) ? 1 : 0;
      uint8_t rpan = (pan_value == PAN_center || pan_value == PAN_right) ? 1 : 0;
      editor->internal.apu.pan &= ~0x11;
      editor->internal.apu.pan |= ((lpan & 1) << 4) | (rpan & 1);
    }
  }

  /* Separate CH header from body */
  cut_top(&ch_rect, editor->font.size);

  /* Initial Volume */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Initial Volume");

    SDL_Rect volume_rect = cut_top(&ch_rect, 2*editor->font.size + 2 * padding);
    volume_rect = contract(&volume_rect, padding);
    if (range_selector(editor, &volume_rect, &editor->internal.ch1.env_start_vol, 0, 0xF, "%d"))
    {
      editor->internal.apu.ch1.envelope.start_volume = editor->internal.ch1.env_start_vol & 0xF;
      square_wave_channel_reset(&editor->internal.apu.ch1);
    }
  }

  /* Wavelength */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Wavelength");

    SDL_Rect wavelen_ctrl_rect = cut_top(&ch_rect, 2*editor->font.size + 2 * padding);
    wavelen_ctrl_rect = contract(&wavelen_ctrl_rect, padding);
    if (range_selector(editor, &wavelen_ctrl_rect, (int32_t*)&editor->internal.ch1.wavelength, 0, 0x7FF, "%03X"))
    {
      editor->internal.apu.ch1.wavelength = (uint16_t)(editor->internal.ch1.wavelength & 0x7FF);
      square_wave_channel_reset(&editor->internal.apu.ch1);
    }
  }

  /* Duty Cycle */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Duty Cycle");

    SDL_Rect duty_cycle_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
    duty_cycle_rect = contract(&duty_cycle_rect, padding);
    if (options(editor, &duty_cycle_rect, duty_cycle_options, 4, &editor->internal.ch1.duty_cycle))
    {
      editor->internal.apu.ch1.duty_cycle = (Duty_Cycle)editor->internal.ch1.duty_cycle;
      square_wave_channel_reset(&editor->internal.apu.ch1);
    }
  }

  /* Envelope */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Envelope");

    /* Direction */
    {
      SDL_Rect dir_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
      dir_rect = contract(&dir_rect, padding);
      if (sprite_options(editor, &dir_rect, direction_options, 2, &editor->internal.ch1.env_dir))
      {
	editor->internal.apu.ch1.envelope.direction = (Direction)editor->internal.ch1.env_dir;
	square_wave_channel_reset(&editor->internal.apu.ch1);
      }
    }

    /* Sweep pace */
    {
      SDL_Rect pace_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
      pace_rect = contract(&pace_rect, padding);

      SDL_Rect txt_rect = cut_left_percent(&pace_rect, 0.5f);
      draw_label(editor, &txt_rect, "Pace", TEXTALIGNMENT_right, 0.25f);

      if (range_selector(editor, &pace_rect, &editor->internal.ch1.env_pace, 0, 7, "%d"))
      {
	editor->internal.apu.ch1.envelope.sweep_pace = editor->internal.ch1.env_pace & 0x7;
	square_wave_channel_reset(&editor->internal.apu.ch1);
      }
    }
  }

  /* Length */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Length");

    SDL_Rect body_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
    body_rect = contract(&body_rect, padding);

    SDL_Rect toggle_rect = cut_left_percent(&body_rect, 0.25f);
    if (toggle_sprite_button(editor, &toggle_rect, SPRITETYPE_power, SPRITETYPE_power, &editor->internal.apu.ch1.length.is_on))
    {
      square_wave_channel_reset(&editor->internal.apu.ch1);
    }

    cut_left(&body_rect, 2 * padding);

    if (range_selector(editor, &body_rect, &editor->internal.ch1.length, 0, 63, "%02X"))
    {
      editor->internal.apu.ch1.length.length = (uint8_t)(editor->internal.ch1.length & 0x3F);
      square_wave_channel_reset(&editor->internal.apu.ch1);
    }
  }

  /* Sweep */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Sweep");

    /* Direction */
    {
      SDL_Rect dir_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
      dir_rect = contract(&dir_rect, padding);
      if (sprite_options(editor, &dir_rect, direction_options, 2, &editor->internal.ch1.sweep_dir))
      {
	editor->internal.apu.ch1.sweep.direction = (Direction)editor->internal.ch1.sweep_dir;
	square_wave_channel_reset(&editor->internal.apu.ch1);
      }
    }

    /* Slope */
    {
      SDL_Rect slope_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
      slope_rect = contract(&slope_rect, padding);

      SDL_Rect txt_rect = cut_left_percent(&slope_rect, 0.5f);
      draw_label(editor, &txt_rect, "Slope", TEXTALIGNMENT_right, 0.25f);

      if (range_selector(editor, &slope_rect, &editor->internal.ch1.sweep_slope, 0, 7, "%d"))
      {
	editor->internal.apu.ch1.sweep.slope = editor->internal.ch1.sweep_slope & 0x7;
	square_wave_channel_reset(&editor->internal.apu.ch1);
      }
    }

    /* Pace */
    {
      SDL_Rect pace_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
      pace_rect = contract(&pace_rect, padding);

      SDL_Rect txt_rect = cut_left_percent(&pace_rect, 0.5f);
      draw_label(editor, &txt_rect, "Pace", TEXTALIGNMENT_right, 0.25f);

      if (range_selector(editor, &pace_rect, &editor->internal.ch1.sweep_pace, 0, 7, "%d"))
      {
	editor->internal.apu.ch1.sweep.pace = editor->internal.ch1.sweep_pace & 0x7;
	square_wave_channel_reset(&editor->internal.apu.ch1);
      }
    }
  }

  /* Register values */
  {
    SDL_Rect reg_val_rect = cut_bottom(&ch_rect, 4 * 2 * editor->font.size + 2 * padding);

    SDL_Rect row1 = cut_top(&reg_val_rect, 2 * editor->font.size + 2 * padding);
    SDL_Rect row2 = cut_top(&reg_val_rect, 2 * editor->font.size + 2 * padding);
    SDL_Rect row3 = cut_top(&reg_val_rect, 2 * editor->font.size + 2 * padding);

    /* NR10 */
    uint8_t nr10 = (
      ((editor->internal.apu.ch1.sweep.pace & 7) << 4) |
      ((editor->internal.apu.ch1.sweep.direction & 1) << 3) |
      ((editor->internal.apu.ch1.sweep.slope & 7)));

    reg_value(editor, &row1, "NR10", nr10);

    cut_left(&row1, 16);

    /* NR11 */
    uint8_t nr11 = (
      ((editor->internal.apu.ch1.duty_cycle & 3) << 6) |
      ((editor->internal.apu.ch1.length.length & 0x3F)));

    reg_value(editor, &row1, "NR11", nr11);

    /* NR12 */
    uint8_t nr12 = (
      ((editor->internal.apu.ch1.envelope.start_volume & 0xF) << 4) |
      ((editor->internal.apu.ch1.envelope.direction & 1) << 3) |
      ((editor->internal.apu.ch1.envelope.sweep_pace & 0x7)));

    reg_value(editor, &row2, "NR12", nr12);

    cut_left(&row2, 16);

    /* NR13 */
    uint8_t nr13 = editor->internal.apu.ch1.wavelength & 0xFF;
    reg_value(editor, &row2, "NR13", nr13);

    /* NR14 */
    uint8_t nr14 = (
      ((editor->internal.apu.ch1.length.is_on & 1) << 6) |
      ((editor->internal.apu.ch1.wavelength >> 8) & 0x7));
    reg_value(editor, &row3, "NR14", nr14);
  }
}

void
editor_ch2_update(Editor_State *editor, SDL_Rect ch_rect, int32_t padding)
{
  /* Draw border on RHS */
  draw_line(editor, (SDL_Point){ch_rect.x + ch_rect.w, ch_rect.y}, (SDL_Point){ch_rect.x + ch_rect.w, ch_rect.y + ch_rect.h}, 0, 0, 0);

  /* Top section */
  {
    /* CH text */
    SDL_Rect ch_top_rect = cut_top(&ch_rect, 2*editor->font.size);
    int32_t ch_text_width = text_width(editor, "CH2") + 2 * padding;
    SDL_Rect ch_label = cut_left(&ch_top_rect, ch_text_width);
    draw_label(editor, &ch_label, "CH2", TEXTALIGNMENT_left, 0.25f);

    SDL_Rect toggle_rect = cut_right_percent(&ch_top_rect, 0.20f);
    toggle_rect = contract(&toggle_rect, padding);
    if (toggle_sprite_button(editor, &toggle_rect, SPRITETYPE_power, SPRITETYPE_power, &editor->internal.apu.ch2.is_on))
    {
      if (editor->internal.ch2.is_locked)
      {
	editor->internal.ch2.is_locked = false;
      }

      square_wave_channel_reset(&editor->internal.apu.ch2);
    }

    SDL_Rect lock_rect = cut_right(&ch_top_rect, toggle_rect.w);
    lock_rect = contract(&lock_rect, padding);
    lock_rect.x += padding;
    toggle_sprite_button(editor, &lock_rect, SPRITETYPE_locked, SPRITETYPE_unlocked, &editor->internal.ch2.is_locked);

    /* CH wave sprite */
    SDL_Rect sq_wave_src_rect = sprite_table[SPRITETYPE_square_wave];
    SDL_Rect sq_wave_rect = cut_left(&ch_top_rect, sq_wave_src_rect.w);
    sq_wave_rect.y += (sq_wave_rect.h - sq_wave_src_rect.h) / 2;
    sq_wave_rect.h = sq_wave_src_rect.h;
    draw_sprite(editor, editor->sprites, &sq_wave_src_rect, &sq_wave_rect);

    /* Pan slider */
    static int32_t pan_value = PAN_center;
    if (pan_selector(editor, &ch_top_rect, &pan_value))
    {
      /* Update CH2 panning */
      uint8_t lpan = (pan_value == PAN_center || pan_value == PAN_left) ? 1 : 0;
      uint8_t rpan = (pan_value == PAN_center || pan_value == PAN_right) ? 1 : 0;
      editor->internal.apu.pan &= ~0x22;
      editor->internal.apu.pan |= ((lpan & 1) << 5) | ((rpan & 1) << 1);
    }
  }

  /* Separate CH header from body */
  cut_top(&ch_rect, editor->font.size);

  /* Initial Volume */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Initial Volume");

    SDL_Rect volume_rect = cut_top(&ch_rect, 2*editor->font.size + 2 * padding);
    volume_rect = contract(&volume_rect, padding);
    if (range_selector(editor, &volume_rect, &editor->internal.ch2.env_start_vol, 0, 0xF, "%d"))
    {
      editor->internal.apu.ch2.envelope.start_volume = editor->internal.ch2.env_start_vol & 0xF;
      square_wave_channel_reset(&editor->internal.apu.ch2);
    }
  }

  /* Wavelength */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Wavelength");

    SDL_Rect wavelen_ctrl_rect = cut_top(&ch_rect, 2*editor->font.size + 2 * padding);
    wavelen_ctrl_rect = contract(&wavelen_ctrl_rect, padding);
    if (range_selector(editor, &wavelen_ctrl_rect, (int32_t*)&editor->internal.ch2.wavelength, 0, 0x7FF, "%03X"))
    {
      editor->internal.apu.ch2.wavelength = (uint16_t)(editor->internal.ch2.wavelength & 0x7FF);
      square_wave_channel_reset(&editor->internal.apu.ch2);
    }
  }

  /* Duty Cycle */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Duty Cycle");

    SDL_Rect duty_cycle_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
    duty_cycle_rect = contract(&duty_cycle_rect, padding);
    if (options(editor, &duty_cycle_rect, duty_cycle_options, 4, &editor->internal.ch2.duty_cycle))
    {
      editor->internal.apu.ch2.duty_cycle = (Duty_Cycle)editor->internal.ch2.duty_cycle;
      square_wave_channel_reset(&editor->internal.apu.ch2);
    }
  }

  /* Envelope */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Envelope");

    /* Direction */
    {
      SDL_Rect dir_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
      dir_rect = contract(&dir_rect, padding);
      if (sprite_options(editor, &dir_rect, direction_options, 2, &editor->internal.ch2.env_dir))
      {
	editor->internal.apu.ch2.envelope.direction = (Direction)editor->internal.ch2.env_dir;
	square_wave_channel_reset(&editor->internal.apu.ch2);
      }
    }

    /* Sweep pace */
    {
      SDL_Rect pace_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
      pace_rect = contract(&pace_rect, padding);

      SDL_Rect txt_rect = cut_left_percent(&pace_rect, 0.5f);
      draw_label(editor, &txt_rect, "Pace", TEXTALIGNMENT_right, 0.25f);

      if (range_selector(editor, &pace_rect, &editor->internal.ch2.env_pace, 0, 7, "%d"))
      {
	editor->internal.apu.ch2.envelope.sweep_pace = editor->internal.ch2.env_pace & 0x7;
	square_wave_channel_reset(&editor->internal.apu.ch2);
      }
    }
  }

  /* Length */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Length");

    SDL_Rect body_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
    body_rect = contract(&body_rect, padding);

    SDL_Rect toggle_rect = cut_left_percent(&body_rect, 0.25f);
    if (toggle_sprite_button(editor, &toggle_rect, SPRITETYPE_power, SPRITETYPE_power, &editor->internal.apu.ch2.length.is_on))
    {
      square_wave_channel_reset(&editor->internal.apu.ch2);
    }

    cut_left(&body_rect, 2 * padding);

    if (range_selector(editor, &body_rect, &editor->internal.ch2.length, 0, 63, "%02X"))
    {
      editor->internal.apu.ch2.length.length = (uint8_t)(editor->internal.ch2.length & 0x3F);
      square_wave_channel_reset(&editor->internal.apu.ch2);
    }
  }

  /* Register values */
  {
    SDL_Rect reg_val_rect = cut_bottom(&ch_rect, 4 * 2 * editor->font.size + 2 * padding);

    SDL_Rect row1 = cut_top(&reg_val_rect, 2 * editor->font.size + 2 * padding);
    SDL_Rect row2 = cut_top(&reg_val_rect, 2 * editor->font.size + 2 * padding);

    /* NR21 */
    uint8_t nr21 = (
      ((editor->internal.apu.ch2.duty_cycle & 3) << 6) |
      ((editor->internal.apu.ch2.length.length & 0x3F)));

    reg_value(editor, &row1, "NR21", nr21);

    cut_left(&row1, 16);

    /* NR22 */
    uint8_t nr22 = (
      ((editor->internal.apu.ch2.envelope.start_volume & 0xF) << 4) |
      ((editor->internal.apu.ch2.envelope.direction & 1) << 3) |
      ((editor->internal.apu.ch2.envelope.sweep_pace & 0x7)));

    reg_value(editor, &row1, "NR22", nr22);

    /* NR13 */
    uint8_t nr23 = editor->internal.apu.ch2.wavelength & 0xFF;
    reg_value(editor, &row2, "NR23", nr23);

    cut_left(&row2, 16);

    /* NR14 */
    uint8_t nr24 = (
      ((editor->internal.apu.ch2.length.is_on & 1) << 6) |
      ((editor->internal.apu.ch2.wavelength >> 8) & 0x7));
    reg_value(editor, &row2, "NR24", nr24);
  }
}

void
editor_ch3_update(Editor_State *editor, SDL_Rect ch_rect, int32_t padding)
{
  /* Draw border on RHS */
  draw_line(editor, (SDL_Point){ch_rect.x + ch_rect.w, ch_rect.y}, (SDL_Point){ch_rect.x + ch_rect.w, ch_rect.y + ch_rect.h}, 0, 0, 0);

  /* Top section */
  {
    /* CH text */
    SDL_Rect ch_top_rect = cut_top(&ch_rect, 2*editor->font.size);
    int32_t ch_text_width = text_width(editor, "CH3") + 2 * padding;
    SDL_Rect ch_label = cut_left(&ch_top_rect, ch_text_width);
    draw_label(editor, &ch_label, "CH3", TEXTALIGNMENT_left, 0.25f);

    SDL_Rect toggle_rect = cut_right_percent(&ch_top_rect, 0.20f);
    toggle_rect = contract(&toggle_rect, padding);
    if (toggle_sprite_button(editor, &toggle_rect, SPRITETYPE_power, SPRITETYPE_power, &editor->internal.apu.ch3.is_on))
    {
      if (editor->internal.ch3.is_locked)
      {
	editor->internal.ch3.is_locked = false;
      }

      waveform_channel_reset(&editor->internal.apu.ch3);
    }

    SDL_Rect lock_rect = cut_right(&ch_top_rect, toggle_rect.w);
    lock_rect = contract(&lock_rect, padding);
    lock_rect.x += padding;
    toggle_sprite_button(editor, &lock_rect, SPRITETYPE_locked, SPRITETYPE_unlocked, &editor->internal.ch3.is_locked);

    /* CH wave sprite */
    SDL_Rect wave_src_rect = sprite_table[SPRITETYPE_waveform];
    SDL_Rect wave_rect = cut_left(&ch_top_rect, wave_src_rect.w);
    wave_rect.y += (wave_rect.h - wave_src_rect.h) / 2;
    wave_rect.h = wave_src_rect.h;
    draw_sprite(editor, editor->sprites, &wave_src_rect, &wave_rect);

    /* Pan slider */
    static int32_t pan_value = PAN_center;
    if (pan_selector(editor, &ch_top_rect, &pan_value))
    {
      /* Update CH3 panning */
      uint8_t lpan = (pan_value == PAN_center || pan_value == PAN_left) ? 1 : 0;
      uint8_t rpan = (pan_value == PAN_center || pan_value == PAN_right) ? 1 : 0;
      editor->internal.apu.pan &= ~0x44;
      editor->internal.apu.pan |= ((lpan & 1) << 6) | ((rpan & 1) << 2);
    }
  }

  /* Separate CH header from body */
  cut_top(&ch_rect, editor->font.size);

  /* Waveform Volume */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Waveform Volume");

    SDL_Rect wave_vol_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
    wave_vol_rect = contract(&wave_vol_rect, padding);
    if (options(editor, &wave_vol_rect, waveform_volume_options, 4, &editor->internal.ch3.volume))
    {
      editor->internal.apu.ch3.volume = (Waveform_Volume)editor->internal.ch3.volume;
      waveform_channel_reset(&editor->internal.apu.ch3);
    }
  }

  /* Wavelength */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Wavelength");

    SDL_Rect wavelen_ctrl_rect = cut_top(&ch_rect, 2*editor->font.size + 2 * padding);
    wavelen_ctrl_rect = contract(&wavelen_ctrl_rect, padding);
    if (range_selector(editor, &wavelen_ctrl_rect, (int32_t*)&editor->internal.ch3.wavelength, 0, 0x7FF, "%03X"))
    {
      editor->internal.apu.ch3.wavelength = (uint16_t)(editor->internal.ch3.wavelength & 0x7FF);
      waveform_channel_reset(&editor->internal.apu.ch3);
    }
  }

  /* Length */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Length");

    SDL_Rect body_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
    body_rect = contract(&body_rect, padding);

    SDL_Rect toggle_rect = cut_left_percent(&body_rect, 0.25f);
    if (toggle_sprite_button(editor, &toggle_rect, SPRITETYPE_power, SPRITETYPE_power, &editor->internal.apu.ch3.length.is_on))
    {
      waveform_channel_reset(&editor->internal.apu.ch3);
    }

    cut_left(&body_rect, 2 * padding);

    if (range_selector(editor, &body_rect, &editor->internal.ch3.length, 0, 63, "%02X"))
    {
      editor->internal.apu.ch3.length.length = (uint8_t)(editor->internal.ch3.length & 0x3F);
      waveform_channel_reset(&editor->internal.apu.ch3);
    }
  }

  /* Waveform Data */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Waveform Data");

    /* Line 1 */
    SDL_Rect body_rect;
    SDL_Rect input_rect;
    int32_t input_width;

    for (uint32_t i = 0; i < 16; ++i)
    {
      if (i % 8 == 0)
      {
	body_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
	body_rect = contract(&body_rect, padding);
	input_width = body_rect.w * 0.125f;
      }

      input_rect = cut_left(&body_rect, input_width);

      if (hex_input_u8(editor, &input_rect, editor->internal.ch3.samples + i))
      {
	editor->internal.apu.ch3.samples[i] = editor->internal.ch3.samples[i].integer;
      }
    }
  }

  /* Register values */
  {
    SDL_Rect reg_val_rect = cut_bottom(&ch_rect, 4 * 2 * editor->font.size + 2 * padding);

    SDL_Rect row1 = cut_top(&reg_val_rect, 2 * editor->font.size + 2 * padding);
    SDL_Rect row2 = cut_top(&reg_val_rect, 2 * editor->font.size + 2 * padding);
    SDL_Rect row3 = cut_top(&reg_val_rect, 2 * editor->font.size + 2 * padding);

    /* NR30 */
    uint8_t nr30 = (editor->internal.apu.ch3.is_on & 1) << 7;
    reg_value(editor, &row1, "NR30", nr30);

    cut_left(&row1, 16);

    /* NR31 */
    uint8_t nr31 = (editor->internal.apu.ch3.length.length & 0xFF);
    reg_value(editor, &row1, "NR31", nr31);

    /* NR32 */
    uint8_t nr32 = (editor->internal.apu.ch3.volume & 3) << 5;
    reg_value(editor, &row2, "NR32", nr32);

    cut_left(&row2, 16);

    /* NR33 */
    uint8_t nr33 = editor->internal.apu.ch3.wavelength & 0xFF;
    reg_value(editor, &row2, "NR33", nr33);

    /* NR34 */
    uint8_t nr34 = (
      ((editor->internal.apu.ch3.length.is_on & 1) << 6) |
      ((editor->internal.apu.ch3.wavelength >> 8) & 0x7));
    reg_value(editor, &row3, "NR34", nr34);
  }
}

void
editor_ch4_update(Editor_State *editor, SDL_Rect ch_rect, int32_t padding)
{
  /* Draw border on RHS */
  draw_line(editor, (SDL_Point){ch_rect.x + ch_rect.w, ch_rect.y}, (SDL_Point){ch_rect.x + ch_rect.w, ch_rect.y + ch_rect.h}, 0, 0, 0);

  /* Top section */
  {
    /* CH text */
    SDL_Rect ch_top_rect = cut_top(&ch_rect, 2*editor->font.size);
    int32_t ch_text_width = text_width(editor, "CH4") + 2 * padding;
    SDL_Rect ch_label = cut_left(&ch_top_rect, ch_text_width);
    draw_label(editor, &ch_label, "CH4", TEXTALIGNMENT_left, 0.25f);

    SDL_Rect toggle_rect = cut_right_percent(&ch_top_rect, 0.20f);
    toggle_rect = contract(&toggle_rect, padding);
    if (toggle_sprite_button(editor, &toggle_rect, SPRITETYPE_power, SPRITETYPE_power, &editor->internal.apu.ch4.is_on))
    {
      if (editor->internal.ch4.is_locked)
      {
	editor->internal.ch4.is_locked = false;
      }

      noise_channel_reset(&editor->internal.apu.ch4);
    }

    SDL_Rect lock_rect = cut_right(&ch_top_rect, toggle_rect.w);
    lock_rect = contract(&lock_rect, padding);
    lock_rect.x += padding;
    toggle_sprite_button(editor, &lock_rect, SPRITETYPE_locked, SPRITETYPE_unlocked, &editor->internal.ch4.is_locked);

    /* CH wave sprite */
    SDL_Rect wave_src_rect = sprite_table[SPRITETYPE_noise];
    SDL_Rect wave_rect = cut_left(&ch_top_rect, wave_src_rect.w);
    wave_rect.y += (wave_rect.h - wave_src_rect.h) / 2;
    wave_rect.h = wave_src_rect.h;
    draw_sprite(editor, editor->sprites, &wave_src_rect, &wave_rect);

    /* Pan slider */
    static int32_t pan_value = PAN_center;
    if (pan_selector(editor, &ch_top_rect, &pan_value))
    {
      /* Update CH4 panning */
      uint8_t lpan = (pan_value == PAN_center || pan_value == PAN_left) ? 1 : 0;
      uint8_t rpan = (pan_value == PAN_center || pan_value == PAN_right) ? 1 : 0;
      editor->internal.apu.pan &= ~0x88;
      editor->internal.apu.pan |= ((lpan & 1) << 7) | ((rpan & 1) << 3);
    }
  }

  /* Separate CH header from body */
  cut_top(&ch_rect, editor->font.size);

  /* Initial Volume */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Initial Volume");

    SDL_Rect volume_rect = cut_top(&ch_rect, 2*editor->font.size + 2 * padding);
    volume_rect = contract(&volume_rect, padding);
    if (range_selector(editor, &volume_rect, &editor->internal.ch4.env_start_vol, 0, 0xF, "%d"))
    {
      editor->internal.apu.ch4.envelope.start_volume = editor->internal.ch4.env_start_vol & 0xF;
      noise_channel_reset(&editor->internal.apu.ch4);
    }
  }

  /* Shift */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Shift");

    SDL_Rect volume_rect = cut_top(&ch_rect, 2*editor->font.size + 2 * padding);
    volume_rect = contract(&volume_rect, padding);
    if (range_selector(editor, &volume_rect, &editor->internal.ch4.shift, 0, 0xF, "%01X"))
    {
      editor->internal.apu.ch4.shift = editor->internal.ch4.shift & 0xF;
      noise_channel_reset(&editor->internal.apu.ch4);
    }
  }

  /* Divisor */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Divisor");

    SDL_Rect volume_rect = cut_top(&ch_rect, 2*editor->font.size + 2 * padding);
    volume_rect = contract(&volume_rect, padding);
    if (range_selector(editor, &volume_rect, &editor->internal.ch4.divisor, 0, 7, "%d"))
    {
      editor->internal.apu.ch4.divisor = editor->internal.ch4.divisor & 0x7;
      noise_channel_reset(&editor->internal.apu.ch4);
    }
  }

  /* LFSR Width */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "LFSR Width");

    SDL_Rect lfsr_w_rect = cut_top(&ch_rect, 2*editor->font.size + 2 * padding);
    lfsr_w_rect = contract(&lfsr_w_rect, padding);
    if (options(editor, &lfsr_w_rect, lfsr_width_options, 2, &editor->internal.ch4.width))
    {
      editor->internal.apu.ch4.width = (LFSR_Width)editor->internal.ch4.width;
      noise_channel_reset(&editor->internal.apu.ch4);
    }
  }

  /* Envelope */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Envelope");

    /* Direction */
    {
      SDL_Rect dir_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
      dir_rect = contract(&dir_rect, padding);
      if (sprite_options(editor, &dir_rect, direction_options, 2, &editor->internal.ch4.env_dir))
      {
	editor->internal.apu.ch4.envelope.direction = (Direction)editor->internal.ch4.env_dir;
	noise_channel_reset(&editor->internal.apu.ch4);
      }
    }

    /* Sweep pace */
    {
      SDL_Rect pace_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
      pace_rect = contract(&pace_rect, padding);

      SDL_Rect txt_rect = cut_left_percent(&pace_rect, 0.5f);
      draw_label(editor, &txt_rect, "Pace", TEXTALIGNMENT_right, 0.25f);

      if (range_selector(editor, &pace_rect, &editor->internal.ch4.env_pace, 0, 7, "%d"))
      {
	editor->internal.apu.ch4.envelope.sweep_pace = editor->internal.ch4.env_pace & 0x7;
	noise_channel_reset(&editor->internal.apu.ch4);
      }
    }
  }

  /* Length */
  {
    SDL_Rect header_rect = cut_top(&ch_rect, editor->font.size + 2 * padding);
    header(editor, &header_rect, "Length");

    SDL_Rect body_rect = cut_top(&ch_rect, 2 * editor->font.size + 2 * padding);
    body_rect = contract(&body_rect, padding);

    SDL_Rect toggle_rect = cut_left_percent(&body_rect, 0.25f);
    if (toggle_sprite_button(editor, &toggle_rect, SPRITETYPE_power, SPRITETYPE_power, &editor->internal.apu.ch4.length.is_on))
    {
      noise_channel_reset(&editor->internal.apu.ch4);
    }

    cut_left(&body_rect, 2 * padding);

    if (range_selector(editor, &body_rect, &editor->internal.ch4.length, 0, 63, "%02X"))
    {
      editor->internal.apu.ch4.length.length = (uint8_t)(editor->internal.ch4.length & 0x3F);
      noise_channel_reset(&editor->internal.apu.ch4);
    }
  }

  /* Register values */
  {
    SDL_Rect reg_val_rect = cut_bottom(&ch_rect, 4 * 2 * editor->font.size + 2 * padding);

    SDL_Rect row1 = cut_top(&reg_val_rect, 2 * editor->font.size + 2 * padding);
    SDL_Rect row2 = cut_top(&reg_val_rect, 2 * editor->font.size + 2 * padding);

    /* NR41 */
    uint8_t nr41 = (editor->internal.apu.ch4.length.length & 0x3F);
    reg_value(editor, &row1, "NR41", nr41);

    cut_left(&row1, 16);

    /* NR42 */
    uint8_t nr42 = (
      ((editor->internal.apu.ch4.envelope.start_volume & 0xF) << 4) |
      ((editor->internal.apu.ch4.envelope.direction & 1) << 3) |
      ((editor->internal.apu.ch4.envelope.sweep_pace & 0x7)));
    reg_value(editor, &row1, "NR42", nr42);

    /* NR43 */
    uint8_t nr43 = (
      ((editor->internal.apu.ch4.shift & 0x7) << 4) |
      ((editor->internal.apu.ch4.width & 1) << 3) |
      ((editor->internal.apu.ch4.divisor & 0x7)));
    reg_value(editor, &row2, "NR43", nr43);

    cut_left(&row2, 16);

    /* NR44 */
    uint8_t nr44 = (editor->internal.apu.ch4.length.is_on & 1) << 6;
    reg_value(editor, &row2, "NR44", nr44);
  }
}

void
editor_update(Editor_State *editor)
{
  SDL_Rect window_rect = {0};
  SDL_GetWindowSize(editor->w, &window_rect.w, &window_rect.h);

  int32_t padding = 3;

  /* Top bar */
  {
    SDL_Rect l_vol_bar = cut_top(&window_rect, 2 * editor->font.size + 2 * padding);
    SDL_Rect r_vol_bar = cut_top(&window_rect, 2 * editor->font.size + 2 * padding);
    int32_t vol_bar_size = 128;

    /* Draw bottom line separating top from channels */
    draw_line(editor, (SDL_Point){r_vol_bar.x, r_vol_bar.y + r_vol_bar.h}, (SDL_Point){r_vol_bar.x + r_vol_bar.w, r_vol_bar.y + r_vol_bar.h}, 0, 0, 0);

    /* L Volume line */
    {
      SDL_Rect body_rect = contract(&l_vol_bar, 3);

      SDL_Rect l_vol_txt_rect = cut_left(&body_rect, text_width(editor, "L Volume") + 2 * padding);
      draw_label(editor, &l_vol_txt_rect, "L Volume", TEXTALIGNMENT_center, 0.25f);

      SDL_Rect l_vol_rect = cut_left(&body_rect, vol_bar_size);
      if (range_selector(editor, &l_vol_rect, &editor->internal.l_volume, 0, 7, "%d"))
      {
	editor->internal.apu.vol &= ~0x70;
	editor->internal.apu.vol |= (editor->internal.l_volume & 0x7) << 4;
      }

      /* Tap to play */
#if 0
      SDL_Rect tap_rect = cut_left(&body_rect, 16 + 4 * padding);
      if (sprite_button(editor, &tap_rect, SPRITETYPE_tap))
      {
	apu_reset(&editor->internal.apu);
      }
#else
      cut_left(&body_rect, 16 + 4 * padding);
#endif

      /* NR50 */
      {
	SDL_Rect reg_rect = cut_left(&body_rect, text_width(editor, "NR50") + 2 * padding);
	draw_label(editor, &reg_rect, "NR50", TEXTALIGNMENT_center, 0.25f);

	SDL_Rect val_rect = cut_left(&body_rect, 3 * editor->font.size + 2 * padding);
	draw_reg_value(editor, &val_rect, editor->internal.apu.vol);
      }
    }

    /* R Volume line */
    {
      SDL_Rect body_rect = contract(&r_vol_bar, 3);

      SDL_Rect r_vol_txt_rect = cut_left(&body_rect, text_width(editor, "R Volume") + 2 * padding);
      draw_label(editor, &r_vol_txt_rect, "R Volume", TEXTALIGNMENT_center, 0.25f);

      SDL_Rect r_vol_rect = cut_left(&body_rect, vol_bar_size);
      if (range_selector(editor, &r_vol_rect, &editor->internal.r_volume, 0, 7, "%d"))
      {
	editor->internal.apu.vol &= ~0x07;
	editor->internal.apu.vol |= (editor->internal.r_volume & 0x7);
      }

      cut_left(&body_rect, 16 + 4 * padding);

      /* NR51 */
      {
	SDL_Rect reg_rect = cut_left(&body_rect, text_width(editor, "NR51") + 2 * padding);
	draw_label(editor, &reg_rect, "NR51", TEXTALIGNMENT_center, 0.25f);

	SDL_Rect val_rect = cut_left(&body_rect, 3 * editor->font.size + 2 * padding);
	draw_reg_value(editor, &val_rect, editor->internal.apu.pan);
      }
    }
  }

  /* CH1 */
  SDL_Rect ch1_rect = cut_left_percent(&window_rect, 0.25f);
  editor_ch1_update(editor, ch1_rect, padding);

  /* CH2 */
  SDL_Rect ch2_rect = cut_left(&window_rect, ch1_rect.w);
  editor_ch2_update(editor, ch2_rect, padding);

  /* CH3 */
  SDL_Rect ch3_rect = cut_left(&window_rect, ch1_rect.w);
  editor_ch3_update(editor, ch3_rect, padding);

  /* CH4 */
  SDL_Rect ch4_rect = cut_left(&window_rect, ch1_rect.w);
  editor_ch4_update(editor, ch4_rect, padding);
}

void
editor_audio_callback(void *userdata, Uint8 *stream, int len)
{
  Editor_State *editor = (Editor_State*)userdata;
  apu_write(&editor->internal.apu, stream, 44100, len / 2);

  if (editor->internal.ch1.is_locked && !editor->internal.apu.ch1.is_on)
  {
    editor->internal.apu.ch1.is_on = true;
    square_wave_channel_reset(&editor->internal.apu.ch1);
  }

  if (editor->internal.ch2.is_locked && !editor->internal.apu.ch2.is_on)
  {
    editor->internal.apu.ch2.is_on = true;
    square_wave_channel_reset(&editor->internal.apu.ch2);
  }

  if (editor->internal.ch3.is_locked && !editor->internal.apu.ch3.is_on)
  {
    editor->internal.apu.ch3.is_on = true;
    waveform_channel_reset(&editor->internal.apu.ch3);
  }

  if (editor->internal.ch4.is_locked && !editor->internal.apu.ch4.is_on)
  {
    editor->internal.apu.ch4.is_on = true;
    noise_channel_reset(&editor->internal.apu.ch4);
  }
}

void
update_loop(void *userdata)
{
  static SDL_Event event = {0};
  Editor_State *editor = (Editor_State*)userdata;

  /* Process queued events. */
  while (SDL_PollEvent(&event))
  {
    switch (event.type)
    {
    case SDL_QUIT:
    {
      editor->is_running = false;
    }
    break;
    case SDL_MOUSEMOTION:
    {
      editor->mouse.prev_pos = editor->mouse.pos;
      editor->mouse.pos.x = event.motion.x;
      editor->mouse.pos.y = event.motion.y;
    }
    break;
    case SDL_MOUSEBUTTONDOWN:
    {
      if (event.button.button == SDL_BUTTON_LEFT)
      {
	editor->mouse.left.just_pressed = true;
	editor->mouse.left.just_released = false;
	editor->mouse.left.down = true;
      }
    }
    break;
    case SDL_MOUSEBUTTONUP:
    {
      if (event.button.button == SDL_BUTTON_LEFT)
      {
	editor->mouse.left.just_pressed = false;
	editor->mouse.left.just_released = true;
	editor->mouse.left.down = false;
      }
    }
    break;
    case SDL_TEXTINPUT:
    {
      strncpy(editor->text_input.text, event.text.text, 32);
      editor->text_input.has_input = true;
    }
    break;
    default: break;
    }
  }

  /* Redraw everything */
  SDL_SetRenderDrawColor(editor->r, 34, 32, 52, SDL_ALPHA_OPAQUE);
  SDL_RenderClear(editor->r);
  editor_update(editor);
  SDL_RenderPresent(editor->r);

  /* Reset button states */
  editor->mouse.left.just_pressed = false;
  editor->mouse.left.just_released = false;
  editor->text_input.has_input = false;
}

int
main(void)
{
#if 0
  {
    SDL_version compiled;
    SDL_version linked;

    SDL_VERSION(&compiled);
    SDL_GetVersion(&linked);
    SDL_Log("We compiled against SDL version %u.%u.%u ...\n",
	    compiled.major, compiled.minor, compiled.patch);
    SDL_Log("But we are linking against SDL version %u.%u.%u.\n",
	    linked.major, linked.minor, linked.patch);
  }
#endif

  int return_code = EXIT_SUCCESS;
  Arena *arena = arena_alloc(PERM_ARENA_SIZE);
  if (arena)
  {
    Editor_State editor = {0};
    editor.a = arena;
    editor.is_running = true;

    if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) == 0)
    {
      editor.w = SDL_CreateWindow(
	"APU Toy",
	SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
	WINDOW_WIDTH, WINDOW_HEIGHT,
	SDL_WINDOW_ALLOW_HIGHDPI);

      if (editor.w)
      {
	/* Disable window resizing */
	SDL_SetWindowResizable(editor.w, SDL_FALSE);

	editor.r = SDL_CreateRenderer(editor.w, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	if (editor.r)
	{
	  /* Initialize editor */
	  editor_init(&editor);

	  /* Initialize audio */
	  SDL_AudioSpec want, have;
	  SDL_AudioDeviceID dev;

	  SDL_memset(&want, 0, sizeof(want));
	  want.freq     = 44100;
	  want.format   = AUDIO_U8;
	  want.channels = 2;
	  want.samples  = 2048;
	  want.callback = editor_audio_callback;
	  want.userdata = &editor;

	  if ((dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0)) > 0)
	  {
	    /* Set up audio device. */
	    editor.dev = dev;
	    SDL_PauseAudioDevice(editor.dev, 0);

#ifdef __EMSCRIPTEN__
	    emscripten_set_main_loop_arg(update_loop, (void*)&editor, 60, 1);
#else
	    while (editor.is_running)
	    {
	      update_loop(&editor);
	    }
#endif

	    SDL_PauseAudioDevice(editor.dev, 1);
	    SDL_CloseAudioDevice(editor.dev);
	  }
	  else
	  {
	    SDL_Log("failed to initialize audio: %s", SDL_GetError());
	    return_code = EXIT_FAILURE;
	  }

	  editor_shutdown(&editor);
	  SDL_DestroyRenderer(editor.r);
	}
	else
	{
	  SDL_Log("failed to create renderer: %s", SDL_GetError());
	  return_code = EXIT_FAILURE;
	}

	SDL_DestroyWindow(editor.w);
      }
      else
      {
	SDL_Log("unable to create window: %s", SDL_GetError());
	return_code = EXIT_FAILURE;
      }

      SDL_Quit();
    }
    else
    {
      SDL_Log("unable to initialize SDL: %s", SDL_GetError());
      return_code = EXIT_FAILURE;
    }

    arena_release(arena);
  }
  else
  {
    return_code = EXIT_FAILURE;
  }

  return(return_code);
}