shithub: m8c

Download patch

ref: 5fcbbc9bee7c8705db1812c3b15ce11298f1b5d6
parent: 328b1c845f21f5b47ba8156399245f2c3b1a4159
author: laamaa <jonne.kokkonen@gmail.com>
date: Tue Apr 15 10:56:58 EDT 2025

start work on event callback

--- /dev/null
+++ b/src/common.h
@@ -1,0 +1,15 @@
+#ifndef COMMON_H_
+#define COMMON_H_
+#include "config.h"
+
+enum app_state { QUIT, WAIT_FOR_DEVICE, RUN };
+
+struct app_context {
+    config_params_s conf;
+    enum app_state app_state;
+    char *preferred_device;
+    unsigned char device_connected;
+    unsigned char app_suspended;
+  };
+  
+#endif
\ No newline at end of file
--- /dev/null
+++ b/src/events.c
@@ -1,0 +1,357 @@
+#include "events.h"
+#include "backends/audio.h"
+#include "backends/m8.h"
+#include "common.h"
+#include "config.h"
+#include "gamecontrollers.h"
+#include "render.h"
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_events.h>
+
+uint8_t keyjazz_enabled = 0;
+uint8_t keyjazz_base_octave = 2;
+uint8_t keyjazz_velocity = 0x64;
+
+static uint8_t keycode = 0; // value of the pressed key
+
+static input_msg_s key = {normal, 0, 0, 0};
+
+static unsigned char toggle_input_keyjazz() {
+  keyjazz_enabled = !keyjazz_enabled;
+  SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, keyjazz_enabled ? "Keyjazz enabled" : "Keyjazz disabled");
+  return keyjazz_enabled;
+}
+
+// Get note value for a scancode, or -1 if not found
+static int get_note_for_scancode(SDL_Scancode scancode) {
+
+  // Map from SDL scancodes to note offsets
+  const struct keyjazz_scancodes_t {
+    SDL_Scancode scancode;
+    uint8_t note_offset;
+  } NOTE_MAP[] = {
+      {SDL_SCANCODE_Z, 0},  {SDL_SCANCODE_S, 1},  {SDL_SCANCODE_X, 2},  {SDL_SCANCODE_D, 3},
+      {SDL_SCANCODE_C, 4},  {SDL_SCANCODE_V, 5},  {SDL_SCANCODE_G, 6},  {SDL_SCANCODE_B, 7},
+      {SDL_SCANCODE_H, 8},  {SDL_SCANCODE_N, 9},  {SDL_SCANCODE_J, 10}, {SDL_SCANCODE_M, 11},
+      {SDL_SCANCODE_Q, 12}, {SDL_SCANCODE_2, 13}, {SDL_SCANCODE_W, 14}, {SDL_SCANCODE_3, 15},
+      {SDL_SCANCODE_E, 16}, {SDL_SCANCODE_R, 17}, {SDL_SCANCODE_5, 18}, {SDL_SCANCODE_T, 19},
+      {SDL_SCANCODE_6, 20}, {SDL_SCANCODE_Y, 21}, {SDL_SCANCODE_7, 22}, {SDL_SCANCODE_U, 23},
+      {SDL_SCANCODE_I, 24}, {SDL_SCANCODE_9, 25}, {SDL_SCANCODE_O, 26}, {SDL_SCANCODE_0, 27},
+      {SDL_SCANCODE_P, 28},
+  };
+
+  const size_t NOTE_MAP_SIZE = (sizeof(NOTE_MAP) / sizeof(NOTE_MAP[0]));
+
+  for (size_t i = 0; i < NOTE_MAP_SIZE; i++) {
+    if (NOTE_MAP[i].scancode == scancode) {
+      return NOTE_MAP[i].note_offset + keyjazz_base_octave * 12;
+    }
+  }
+  return -1; // Not a note key
+}
+
+// Handle octave and velocity changes
+static void handle_keyjazz_settings(const SDL_Event *event, const config_params_s *conf) {
+
+  // Constants for keyjazz limits and adjustments
+  const unsigned char KEYJAZZ_MIN_OCTAVE = 0;
+  const unsigned char KEYJAZZ_MAX_OCTAVE = 8;
+  const unsigned char KEYJAZZ_MIN_VELOCITY = 0;
+  const unsigned char KEYJAZZ_MAX_VELOCITY = 0x7F;
+  const unsigned char KEYJAZZ_FINE_VELOCITY_STEP = 1;
+  const unsigned char KEYJAZZ_COARSE_VELOCITY_STEP = 0x10;
+
+  if (event->key.repeat > 0 || event->key.type == SDL_EVENT_KEY_UP) {
+    return;
+  }
+
+  const SDL_Scancode scancode = event->key.scancode;
+  const bool is_fine_adjustment = (event->key.mod & SDL_KMOD_ALT) > 0;
+
+  if (scancode == conf->key_jazz_dec_octave && keyjazz_base_octave > KEYJAZZ_MIN_OCTAVE) {
+    keyjazz_base_octave--;
+    display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
+  } else if (scancode == conf->key_jazz_inc_octave && keyjazz_base_octave < KEYJAZZ_MAX_OCTAVE) {
+    keyjazz_base_octave++;
+    display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
+  } else if (scancode == conf->key_jazz_dec_velocity) {
+    const int step = is_fine_adjustment ? KEYJAZZ_FINE_VELOCITY_STEP : KEYJAZZ_COARSE_VELOCITY_STEP;
+    if (keyjazz_velocity > (is_fine_adjustment ? KEYJAZZ_MIN_VELOCITY + step : step)) {
+      keyjazz_velocity -= step;
+      display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
+    }
+  } else if (scancode == conf->key_jazz_inc_velocity) {
+    const int step = is_fine_adjustment ? KEYJAZZ_FINE_VELOCITY_STEP : KEYJAZZ_COARSE_VELOCITY_STEP;
+    const int max = is_fine_adjustment ? KEYJAZZ_MAX_VELOCITY : (KEYJAZZ_MAX_VELOCITY - step);
+    if (keyjazz_velocity < max) {
+      keyjazz_velocity += step;
+      display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
+    }
+  }
+}
+
+static input_msg_s handle_keyjazz(SDL_Event *event, uint8_t keyvalue, config_params_s *conf) {
+  input_msg_s key = {keyjazz, keyvalue, keyjazz_velocity, event->type};
+
+  // Check if this is a note key
+  const int note_value = get_note_for_scancode(event->key.scancode);
+  if (note_value >= 0) {
+    key.value = note_value;
+    return key;
+  }
+
+  // Not a note key, handle other settings
+  key.type = normal;
+  handle_keyjazz_settings(event, conf);
+
+  return key;
+}
+
+static input_msg_s handle_m8_buttons(const SDL_Event *event, const config_params_s *conf) {
+  // Default message with normal type and no value
+  input_msg_s key = {normal, 0, 0, 0};
+
+  // Get the current scancode
+  const SDL_Scancode scancode = event->key.scancode;
+
+  // Handle standard keycodes (single key mapping)
+  const struct {
+    SDL_Scancode scancode;
+    uint8_t value;
+  } normal_key_map[] = {
+      {conf->key_up, key_up},
+      {conf->key_left, key_left},
+      {conf->key_down, key_down},
+      {conf->key_right, key_right},
+      {conf->key_select, key_select},
+      {conf->key_select_alt, key_select},
+      {conf->key_start, key_start},
+      {conf->key_start_alt, key_start},
+      {conf->key_opt, key_opt},
+      {conf->key_opt_alt, key_opt},
+      {conf->key_edit, key_edit},
+      {conf->key_edit_alt, key_edit},
+      {conf->key_delete, key_opt | key_edit},
+  };
+
+  // Handle special messages (different message type)
+  const struct {
+    SDL_Scancode scancode;
+    special_messages_t message;
+  } special_key_map[] = {
+      {conf->key_reset, msg_reset_display},
+      {conf->key_toggle_audio, msg_toggle_audio},
+  };
+
+  // Check normal key mappings
+  for (size_t i = 0; i < sizeof(normal_key_map) / sizeof(normal_key_map[0]); i++) {
+    if (scancode == normal_key_map[i].scancode) {
+      key.value = normal_key_map[i].value;
+      return key;
+    }
+  }
+
+  // Check special key mappings
+  for (size_t i = 0; i < sizeof(special_key_map) / sizeof(special_key_map[0]); i++) {
+    if (scancode == special_key_map[i].scancode) {
+      key.type = special;
+      key.value = special_key_map[i].message;
+      return key;
+    }
+  }
+
+  // No matching key found, return default key message
+  return key;
+}
+
+SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
+  struct app_context *ctx = appstate;
+  SDL_AppResult ret_val = SDL_APP_CONTINUE;
+  static int prev_key_analog = 0;
+
+  switch (event->type) {
+
+  // --- System events ---
+  case SDL_EVENT_QUIT:
+  case SDL_EVENT_TERMINATING:
+    ret_val = SDL_APP_SUCCESS;
+    break;
+  case SDL_EVENT_DID_ENTER_BACKGROUND:
+    // iOS: Application entered into background on iOS. About 5 seconds to stop things.
+    SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_DID_ENTER_BACKGROUND");
+    ctx->app_suspended = 1;
+    if (ctx->device_connected)
+      m8_pause_processing();
+    break;
+  case SDL_EVENT_WILL_ENTER_BACKGROUND:
+    // iOS: App about to enter into background
+    break;
+  case SDL_EVENT_WILL_ENTER_FOREGROUND:
+    // iOS: App returning to foreground
+    SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_WILL_ENTER_FOREGROUND");
+    break;
+  case SDL_EVENT_DID_ENTER_FOREGROUND:
+    // iOS: App becomes interactive again
+    SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_DID_ENTER_FOREGROUND");
+    ctx->app_suspended = 0;
+    if (ctx->device_connected) {
+      m8_resume_processing();
+    }
+  case SDL_EVENT_WINDOW_RESIZED:
+  case SDL_EVENT_WINDOW_MOVED:
+    // If window size is changed, some operating systems might need a little nudge to fix scaling
+    renderer_fix_texture_scaling_after_window_resize();
+    break;
+
+  // --- Input events ---
+  case SDL_EVENT_GAMEPAD_ADDED:
+  case SDL_EVENT_GAMEPAD_REMOVED:
+    // Reinitialize game controllers on controller add/remove/remap
+    gamecontrollers_initialize();
+    break;
+
+  case SDL_EVENT_KEY_DOWN:
+    if (event->key.repeat > 0) {
+      break;
+    }
+
+    // ALT+ENTER toggles fullscreen
+    if (event->key.key == SDLK_RETURN && (event->key.mod & SDL_KMOD_ALT) > 0) {
+      toggle_fullscreen();
+      break;
+    }
+
+    // ALT+F4 quits program
+    if (event->key.key == SDLK_F4 && (event->key.mod & SDL_KMOD_ALT) > 0) {
+      key = (input_msg_s){special, msg_quit, 0, 0};
+      break;
+    }
+
+    // ESC = toggle keyjazz
+    if (event->key.key == SDLK_ESCAPE) {
+      display_keyjazz_overlay(toggle_input_keyjazz(), keyjazz_base_octave, keyjazz_velocity);
+      break;
+    }
+
+    key = handle_m8_buttons(event, &ctx->conf);
+    SDL_Log("key %d",key.value);
+    if (keyjazz_enabled) {
+      key = handle_keyjazz(event, key.value, &ctx->conf);
+    }
+    if (key.type == normal) {
+      keycode |= key.value;
+    } else {
+      keycode = key.value;
+    }
+    break;
+
+  case SDL_EVENT_KEY_UP:
+
+    key = handle_m8_buttons(event, &ctx->conf);
+    if (keyjazz_enabled) {
+      key = handle_keyjazz(event, key.value, &ctx->conf);
+    }
+
+    if (key.type == normal)
+      keycode &= ~key.value;
+    else
+      keycode = 0;
+
+    break;
+
+  default:
+    break;
+  }
+  return ret_val;
+}
+
+// Handles SDL input events
+static void handle_sdl_events(config_params_s *conf) {
+
+  static int prev_key_analog = 0;
+
+  SDL_Event event;
+
+  // Read joysticks
+  const int key_analog = gamecontrollers_handle_buttons(conf);
+  if (prev_key_analog != key_analog) {
+    keycode = key_analog;
+    prev_key_analog = key_analog;
+  }
+
+  const input_msg_s gamepad_msg = gamecontrollers_handle_special_messages(conf);
+  if (gamepad_msg.type == special) {
+    key = gamepad_msg;
+  }
+}
+
+int input_process(config_params_s *conf, enum app_state *app_state) {
+  static uint8_t prev_input = 0;
+  static uint8_t prev_note = 0;
+
+  // get current inputs
+  const input_msg_s input = input_get_msg(conf);
+
+  switch (input.type) {
+  case normal:
+    if (input.value != prev_input) {
+      prev_input = input.value;
+      m8_send_msg_controller(input.value);
+    }
+    break;
+  case keyjazz:
+    if (input.value != 0) {
+      if (input.eventType == SDL_EVENT_KEY_DOWN && input.value != prev_input) {
+        m8_send_msg_keyjazz(input.value, input.value2);
+        prev_note = input.value;
+      } else if (input.eventType == SDL_EVENT_KEY_UP && input.value == prev_note) {
+        m8_send_msg_keyjazz(0xFF, 0);
+      }
+    }
+    prev_input = input.value;
+    break;
+  case special:
+    if (input.value != prev_input) {
+      prev_input = input.value;
+      switch (input.value) {
+      case msg_quit:
+        SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Received msg_quit from input device.");
+        *app_state = 0;
+        break;
+      case msg_reset_display:
+        m8_reset_display();
+        break;
+      case msg_toggle_audio:
+        conf->audio_enabled = !conf->audio_enabled;
+        audio_toggle(conf->audio_device_name, conf->audio_buffer_size);
+        break;
+      default:
+        break;
+      }
+      break;
+    }
+  }
+  return 1;
+}
+
+// Returns the currently pressed keys to main
+input_msg_s input_get_msg(config_params_s *conf) {
+
+  key = (input_msg_s){normal, 0, 0, 0};
+
+  // Query for SDL events
+  handle_sdl_events(conf);
+
+  if (!keyjazz_enabled && keycode == (key_start | key_select | key_opt | key_edit)) {
+    key = (input_msg_s){special, msg_reset_display, 0, 0};
+  }
+
+  if (key.type == normal) {
+    /* Normal input keys go through some event-based manipulation in
+       handle_sdl_events(), the value is stored in keycode variable */
+    const input_msg_s input = (input_msg_s){key.type, keycode, 0, 0};
+    return input;
+  }
+  // Special event keys already have the correct keycode baked in
+  return key;
+}
--- /dev/null
+++ b/src/events.h
@@ -1,0 +1,53 @@
+// Copyright 2021 Jonne Kokkonen
+// Released under the MIT licence, https://opensource.org/licenses/MIT
+
+#ifndef INPUT_H_
+#define INPUT_H_
+
+#include "config.h"
+#include "common.h"
+#include <stdint.h>
+
+typedef enum input_buttons_t {
+  INPUT_UP,
+  INPUT_DOWN,
+  INPUT_LEFT,
+  INPUT_RIGHT,
+  INPUT_OPT,
+  INPUT_EDIT,
+  INPUT_SELECT,
+  INPUT_START,
+  INPUT_MAX
+} input_buttons_t;
+
+// Bits for M8 input messages
+typedef enum keycodes_t {
+  key_left = 1 << 7,
+  key_up = 1 << 6,
+  key_down = 1 << 5,
+  key_select = 1 << 4,
+  key_start = 1 << 3,
+  key_right = 1 << 2,
+  key_opt = 1 << 1,
+  key_edit = 1
+} keycodes_t;
+
+typedef enum input_type_t { normal, keyjazz, special } input_type_t;
+
+typedef enum special_messages_t {
+  msg_quit = 1,
+  msg_reset_display = 2,
+  msg_toggle_audio = 3
+} special_messages_t;
+
+typedef struct input_msg_s {
+  input_type_t type;
+  uint8_t value;
+  uint8_t value2;
+  uint32_t eventType;
+} input_msg_s;
+
+input_msg_s input_get_msg(config_params_s *conf);
+int input_process(config_params_s *conf, enum app_state *app_state);
+
+#endif
--- a/src/gamecontrollers.c
+++ b/src/gamecontrollers.c
@@ -4,7 +4,7 @@
 
 #include "gamecontrollers.h"
 #include "config.h"
-#include "input.h"
+#include "events.h"
 
 #include <SDL3/SDL.h>
 #include <stdio.h>
--- a/src/gamecontrollers.h
+++ b/src/gamecontrollers.h
@@ -6,7 +6,7 @@
 #define GAMECONTROLLERS_H_
 
 #include "config.h"
-#include "input.h"
+#include "events.h"
 
 #define MAX_CONTROLLERS 4
 
--- a/src/input.c
+++ /dev/null
@@ -1,335 +1,0 @@
-#include "input.h"
-#include "backends/audio.h"
-#include "backends/m8.h"
-#include "config.h"
-#include "gamecontrollers.h"
-#include "render.h"
-#include <SDL3/SDL.h>
-
-uint8_t keyjazz_enabled = 0;
-uint8_t keyjazz_base_octave = 2;
-uint8_t keyjazz_velocity = 0x64;
-
-static uint8_t keycode = 0; // value of the pressed key
-
-static input_msg_s key = {normal, 0, 0, 0};
-
-static unsigned char toggle_input_keyjazz() {
-  keyjazz_enabled = !keyjazz_enabled;
-  SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, keyjazz_enabled ? "Keyjazz enabled" : "Keyjazz disabled");
-  return keyjazz_enabled;
-}
-
-// Get note value for a scancode, or -1 if not found
-static int get_note_for_scancode(SDL_Scancode scancode) {
-
-  // Map from SDL scancodes to note offsets
-  const struct keyjazz_scancodes_t {
-    SDL_Scancode scancode;
-    uint8_t note_offset;
-  } NOTE_MAP[] = {
-      {SDL_SCANCODE_Z, 0},  {SDL_SCANCODE_S, 1},  {SDL_SCANCODE_X, 2},  {SDL_SCANCODE_D, 3},
-      {SDL_SCANCODE_C, 4},  {SDL_SCANCODE_V, 5},  {SDL_SCANCODE_G, 6},  {SDL_SCANCODE_B, 7},
-      {SDL_SCANCODE_H, 8},  {SDL_SCANCODE_N, 9},  {SDL_SCANCODE_J, 10}, {SDL_SCANCODE_M, 11},
-      {SDL_SCANCODE_Q, 12}, {SDL_SCANCODE_2, 13}, {SDL_SCANCODE_W, 14}, {SDL_SCANCODE_3, 15},
-      {SDL_SCANCODE_E, 16}, {SDL_SCANCODE_R, 17}, {SDL_SCANCODE_5, 18}, {SDL_SCANCODE_T, 19},
-      {SDL_SCANCODE_6, 20}, {SDL_SCANCODE_Y, 21}, {SDL_SCANCODE_7, 22}, {SDL_SCANCODE_U, 23},
-      {SDL_SCANCODE_I, 24}, {SDL_SCANCODE_9, 25}, {SDL_SCANCODE_O, 26}, {SDL_SCANCODE_0, 27},
-      {SDL_SCANCODE_P, 28},
-  };
-
-  const size_t NOTE_MAP_SIZE = (sizeof(NOTE_MAP) / sizeof(NOTE_MAP[0]));
-
-  for (size_t i = 0; i < NOTE_MAP_SIZE; i++) {
-    if (NOTE_MAP[i].scancode == scancode) {
-      return NOTE_MAP[i].note_offset + keyjazz_base_octave * 12;
-    }
-  }
-  return -1; // Not a note key
-}
-
-// Handle octave and velocity changes
-static void handle_keyjazz_settings(const SDL_Event *event, const config_params_s *conf) {
-
-  // Constants for keyjazz limits and adjustments
-  const unsigned char KEYJAZZ_MIN_OCTAVE = 0;
-  const unsigned char KEYJAZZ_MAX_OCTAVE = 8;
-  const unsigned char KEYJAZZ_MIN_VELOCITY = 0;
-  const unsigned char KEYJAZZ_MAX_VELOCITY = 0x7F;
-  const unsigned char KEYJAZZ_FINE_VELOCITY_STEP = 1;
-  const unsigned char KEYJAZZ_COARSE_VELOCITY_STEP = 0x10;
-
-  if (event->key.repeat > 0 || event->key.type == SDL_EVENT_KEY_UP) {
-    return;
-  }
-
-  const SDL_Scancode scancode = event->key.scancode;
-  const bool is_fine_adjustment = (event->key.mod & SDL_KMOD_ALT) > 0;
-
-  if (scancode == conf->key_jazz_dec_octave && keyjazz_base_octave > KEYJAZZ_MIN_OCTAVE) {
-    keyjazz_base_octave--;
-    display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
-  } else if (scancode == conf->key_jazz_inc_octave && keyjazz_base_octave < KEYJAZZ_MAX_OCTAVE) {
-    keyjazz_base_octave++;
-    display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
-  } else if (scancode == conf->key_jazz_dec_velocity) {
-    const int step = is_fine_adjustment ? KEYJAZZ_FINE_VELOCITY_STEP : KEYJAZZ_COARSE_VELOCITY_STEP;
-    if (keyjazz_velocity > (is_fine_adjustment ? KEYJAZZ_MIN_VELOCITY + step : step)) {
-      keyjazz_velocity -= step;
-      display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
-    }
-  } else if (scancode == conf->key_jazz_inc_velocity) {
-    const int step = is_fine_adjustment ? KEYJAZZ_FINE_VELOCITY_STEP : KEYJAZZ_COARSE_VELOCITY_STEP;
-    const int max = is_fine_adjustment ? KEYJAZZ_MAX_VELOCITY : (KEYJAZZ_MAX_VELOCITY - step);
-    if (keyjazz_velocity < max) {
-      keyjazz_velocity += step;
-      display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
-    }
-  }
-}
-
-static input_msg_s handle_keyjazz(SDL_Event *event, uint8_t keyvalue, config_params_s *conf) {
-  input_msg_s key = {keyjazz, keyvalue, keyjazz_velocity, event->type};
-
-  // Check if this is a note key
-  const int note_value = get_note_for_scancode(event->key.scancode);
-  if (note_value >= 0) {
-    key.value = note_value;
-    return key;
-  }
-
-  // Not a note key, handle other settings
-  key.type = normal;
-  handle_keyjazz_settings(event, conf);
-
-  return key;
-}
-
-static input_msg_s handle_normal_keys(const SDL_Event *event, const config_params_s *conf) {
-  // Default message with normal type and no value
-  input_msg_s key = {normal, 0, 0, 0};
-
-  // Get the current scancode
-  const SDL_Scancode scancode = event->key.scancode;
-
-  // Handle standard keycodes (single key mapping)
-  const struct {
-    SDL_Scancode scancode;
-    uint8_t value;
-  } normal_key_map[] = {
-      {conf->key_up, key_up},
-      {conf->key_left, key_left},
-      {conf->key_down, key_down},
-      {conf->key_right, key_right},
-      {conf->key_select, key_select},
-      {conf->key_select_alt, key_select},
-      {conf->key_start, key_start},
-      {conf->key_start_alt, key_start},
-      {conf->key_opt, key_opt},
-      {conf->key_opt_alt, key_opt},
-      {conf->key_edit, key_edit},
-      {conf->key_edit_alt, key_edit},
-      {conf->key_delete, key_opt | key_edit},
-  };
-
-  // Handle special messages (different message type)
-  const struct {
-    SDL_Scancode scancode;
-    special_messages_t message;
-  } special_key_map[] = {
-      {conf->key_reset, msg_reset_display},
-      {conf->key_toggle_audio, msg_toggle_audio},
-  };
-
-  // Check normal key mappings
-  for (size_t i = 0; i < sizeof(normal_key_map) / sizeof(normal_key_map[0]); i++) {
-    if (scancode == normal_key_map[i].scancode) {
-      key.value = normal_key_map[i].value;
-      return key;
-    }
-  }
-
-  // Check special key mappings
-  for (size_t i = 0; i < sizeof(special_key_map) / sizeof(special_key_map[0]); i++) {
-    if (scancode == special_key_map[i].scancode) {
-      key.type = special;
-      key.value = special_key_map[i].message;
-      return key;
-    }
-  }
-
-  // No matching key found, return default key message
-  return key;
-}
-
-// Handles SDL input events
-static void handle_sdl_events(config_params_s *conf) {
-
-  static int prev_key_analog = 0;
-
-  SDL_Event event;
-
-  // Read joysticks
-  const int key_analog = gamecontrollers_handle_buttons(conf);
-  if (prev_key_analog != key_analog) {
-    keycode = key_analog;
-    prev_key_analog = key_analog;
-  }
-
-  const input_msg_s gamepad_msg = gamecontrollers_handle_special_messages(conf);
-  if (gamepad_msg.type == special) {
-    key = gamepad_msg;
-  }
-
-  while (SDL_PollEvent(&event)) {
-
-    switch (event.type) {
-
-    // Reinitialize game controllers on controller add/remove/remap
-    case SDL_EVENT_GAMEPAD_ADDED:
-    case SDL_EVENT_GAMEPAD_REMOVED:
-      gamecontrollers_initialize();
-      break;
-
-    // Handle SDL quit events (for example, window close)
-    case SDL_EVENT_QUIT:
-      key = (input_msg_s){special, msg_quit, 0, 0};
-      break;
-
-    case SDL_EVENT_WINDOW_RESIZED:
-    case SDL_EVENT_WINDOW_MOVED:
-      renderer_fix_texture_scaling_after_window_resize();
-      break;
-
-    case SDL_EVENT_KEY_DOWN:
-
-      if (event.key.repeat > 0) {
-        break;
-      }
-
-      // ALT+ENTER toggles fullscreen
-      if (event.key.key == SDLK_RETURN && (event.key.mod & SDL_KMOD_ALT) > 0) {
-        toggle_fullscreen();
-        break;
-      }
-
-      // ALT+F4 quits program
-      if (event.key.key == SDLK_F4 && (event.key.mod & SDL_KMOD_ALT) > 0) {
-        key = (input_msg_s){special, msg_quit, 0, 0};
-        break;
-      }
-
-      // ESC = toggle keyjazz
-      if (event.key.key == SDLK_ESCAPE) {
-        display_keyjazz_overlay(toggle_input_keyjazz(), keyjazz_base_octave, keyjazz_velocity);
-        break;
-      }
-
-    // Intentional fallthrough
-    case SDL_EVENT_KEY_UP:
-
-      // Normal keyboard inputs
-      key = handle_normal_keys(&event, conf);
-
-      if (keyjazz_enabled) {
-        key = handle_keyjazz(&event, key.value, conf);
-      }
-      break;
-
-    default:
-      break;
-    }
-
-    switch (key.type) {
-    case normal:
-      if (event.type == SDL_EVENT_KEY_DOWN) {
-        keycode |= key.value;
-      } else if (event.type == SDL_EVENT_KEY_UP) {
-        keycode &= ~key.value;
-      }
-      break;
-    case keyjazz:
-      // Do not allow pressing multiple keys with keyjazz
-    case special:
-      if (event.type == SDL_EVENT_KEY_DOWN) {
-        keycode = key.value;
-      } else if (event.type == SDL_EVENT_KEY_UP) {
-        keycode = 0;
-      }
-      break;
-    default:
-      break;
-    }
-  }
-}
-
-int input_process(config_params_s *conf, enum app_state *app_state) {
-  static uint8_t prev_input = 0;
-  static uint8_t prev_note = 0;
-
-  // get current inputs
-  const input_msg_s input = input_get_msg(conf);
-
-  switch (input.type) {
-  case normal:
-    if (input.value != prev_input) {
-      prev_input = input.value;
-      m8_send_msg_controller(input.value);
-    }
-    break;
-  case keyjazz:
-    if (input.value != 0) {
-      if (input.eventType == SDL_EVENT_KEY_DOWN && input.value != prev_input) {
-        m8_send_msg_keyjazz(input.value, input.value2);
-        prev_note = input.value;
-      } else if (input.eventType == SDL_EVENT_KEY_UP && input.value == prev_note) {
-        m8_send_msg_keyjazz(0xFF, 0);
-      }
-    }
-    prev_input = input.value;
-    break;
-  case special:
-    if (input.value != prev_input) {
-      prev_input = input.value;
-      switch (input.value) {
-      case msg_quit:
-        SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Received msg_quit from input device.");
-        *app_state = 0;
-        break;
-      case msg_reset_display:
-        m8_reset_display();
-        break;
-      case msg_toggle_audio:
-        conf->audio_enabled = !conf->audio_enabled;
-        audio_toggle(conf->audio_device_name, conf->audio_buffer_size);
-        break;
-      default:
-        break;
-      }
-      break;
-    }
-  }
-  return 1;
-}
-
-// Returns the currently pressed keys to main
-input_msg_s input_get_msg(config_params_s *conf) {
-
-  key = (input_msg_s){normal, 0, 0, 0};
-
-  // Query for SDL events
-  handle_sdl_events(conf);
-
-  if (!keyjazz_enabled && keycode == (key_start | key_select | key_opt | key_edit)) {
-    key = (input_msg_s){special, msg_reset_display, 0, 0};
-  }
-
-  if (key.type == normal) {
-    /* Normal input keys go through some event-based manipulation in
-       handle_sdl_events(), the value is stored in keycode variable */
-    const input_msg_s input = (input_msg_s){key.type, keycode, 0, 0};
-    return input;
-  }
-  // Special event keys already have the correct keycode baked in
-  return key;
-}
--- a/src/input.h
+++ /dev/null
@@ -1,54 +1,0 @@
-// Copyright 2021 Jonne Kokkonen
-// Released under the MIT licence, https://opensource.org/licenses/MIT
-
-#ifndef INPUT_H_
-#define INPUT_H_
-
-#include "config.h"
-#include <stdint.h>
-
-enum app_state { QUIT, WAIT_FOR_DEVICE, RUN };
-
-typedef enum input_buttons_t {
-  INPUT_UP,
-  INPUT_DOWN,
-  INPUT_LEFT,
-  INPUT_RIGHT,
-  INPUT_OPT,
-  INPUT_EDIT,
-  INPUT_SELECT,
-  INPUT_START,
-  INPUT_MAX
-} input_buttons_t;
-
-// Bits for M8 input messages
-typedef enum keycodes_t {
-  key_left = 1 << 7,
-  key_up = 1 << 6,
-  key_down = 1 << 5,
-  key_select = 1 << 4,
-  key_start = 1 << 3,
-  key_right = 1 << 2,
-  key_opt = 1 << 1,
-  key_edit = 1
-} keycodes_t;
-
-typedef enum input_type_t { normal, keyjazz, special } input_type_t;
-
-typedef enum special_messages_t {
-  msg_quit = 1,
-  msg_reset_display = 2,
-  msg_toggle_audio = 3
-} special_messages_t;
-
-typedef struct input_msg_s {
-  input_type_t type;
-  uint8_t value;
-  uint8_t value2;
-  uint32_t eventType;
-} input_msg_s;
-
-input_msg_s input_get_msg(config_params_s *conf);
-int input_process(config_params_s *conf, enum app_state *app_state);
-
-#endif
--- a/src/main.c
+++ b/src/main.c
@@ -14,19 +14,12 @@
 #include "SDL2_inprint.h"
 #include "backends/audio.h"
 #include "backends/m8.h"
+#include "common.h"
 #include "config.h"
 #include "gamecontrollers.h"
-#include "input.h"
+#include "events.h"
 #include "render.h"
 
-struct app_context {
-  config_params_s conf;
-  enum app_state app_state;
-  char *preferred_device;
-  unsigned char device_connected;
-  unsigned char app_suspended;
-};
-
 static void do_wait_for_device(struct app_context *ctx) {
   static Uint64 ticks_poll_device = 0;
   static int screensaver_initialized = 0;
@@ -112,6 +105,43 @@
   return device_connected;
 }
 
+// Main callback loop - read inputs, process data from device, render screen
+SDL_AppResult SDL_AppIterate(void *appstate) {
+  struct app_context *ctx = appstate;
+  SDL_AppResult app_result = SDL_APP_CONTINUE;
+
+  switch (ctx->app_state) {
+
+  case WAIT_FOR_DEVICE: {
+    if (ctx->conf.wait_for_device) {
+      do_wait_for_device(ctx);
+    }
+    break;
+  }
+
+  case RUN: {
+    const int result = m8_process_data(&ctx->conf);
+    if (result == DEVICE_DISCONNECTED) {
+      ctx->device_connected = 0;
+      ctx->app_state = WAIT_FOR_DEVICE;
+      audio_close();
+    } else if (result == DEVICE_FATAL_ERROR) {
+      return SDL_APP_FAILURE;
+    }
+    render_screen();
+    break;
+  }
+
+  case QUIT: {
+    app_result = SDL_APP_SUCCESS;
+    break;
+  }
+  }
+
+  return app_result;
+}
+
+// Initialize the app: initialize context, configs, renderer controllers and attempt to find M8
 SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
   char *config_filename = NULL;
 
@@ -132,11 +162,16 @@
   }
 
 #ifndef NDEBUG
+  // Show debug messages in the application log
   SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG);
   SDL_LogDebug(SDL_LOG_CATEGORY_TEST, "Running a Debug build");
 #endif
 
   gamecontrollers_initialize();
+
+  // Process the application's main callback roughly at 120hz
+  SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "120");
+
   *appstate = ctx;
 
   if (ctx->app_state == WAIT_FOR_DEVICE) {
@@ -155,54 +190,14 @@
       ctx->device_connected = 0;
       ctx->app_state = ctx->conf.wait_for_device ? WAIT_FOR_DEVICE : QUIT;
     }
-  }  
-
-  return SDL_APP_CONTINUE;
-}
-
-SDL_AppResult SDL_AppIterate(void *appstate) {
-  struct app_context *ctx = appstate;
-  SDL_AppResult app_result = SDL_APP_CONTINUE;
-
-  switch (ctx->app_state) {
-    case WAIT_FOR_DEVICE: {
-      if (ctx->conf.wait_for_device) {
-        do_wait_for_device(ctx);
-      }
-      break;
-    }
-    
-    case RUN:
-    break;
-    case QUIT:
-    break;
   }
 
-  return app_result;
-
-  if (ctx->conf.wait_for_device && ctx->app_state == WAIT_FOR_DEVICE) {
-    do_wait_for_device(ctx);
-  } else if (!ctx->device_connected && ctx->app_state != WAIT_FOR_DEVICE) {
-    return SDL_APP_FAILURE;
-  }
-
-  // Handle input, process data, and render screen while running.
-  if (ctx->app_state == RUN) {
-    const int result = m8_process_data(&ctx->conf);
-    if (result == DEVICE_DISCONNECTED) {
-      ctx->device_connected = 0;
-      ctx->app_state = WAIT_FOR_DEVICE;
-      audio_close();
-    } else if (result == DEVICE_FATAL_ERROR) {
-      return SDL_APP_FAILURE;
-    }
-    render_screen();
-  }
   return SDL_APP_CONTINUE;
 }
 
 void SDL_AppQuit(void *appstate, SDL_AppResult result) {
   struct app_context *app = appstate;
+
   if (app) {
     if (app->app_state == WAIT_FOR_DEVICE) {
       screensaver_destroy();
@@ -216,61 +211,9 @@
     if (app->device_connected) {
       m8_close();
     }
-    SDL_Quit();
-    SDL_Log("Shutting down.");
-
     SDL_free(app);
-  }
-}
 
-SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
-  struct app_context *ctx = appstate;
-  SDL_AppResult ret_val = SDL_APP_CONTINUE;
-
-  switch (event->type) {
-    case SDL_EVENT_QUIT:
-    case SDL_EVENT_TERMINATING:
-      ret_val = SDL_APP_SUCCESS;
-      break;
-    case SDL_EVENT_DID_ENTER_BACKGROUND:
-      /* This will get called if the user accepted whatever sent your app to the background.
-         If the user got a phone call and canceled it, you'll instead get an
-         SDL_EVENT_DID_ENTER_FOREGROUND event and restart your loops. When you get this, you have 5
-         seconds to save all your state or the app will be terminated. Your app is NOT active at this
-         point.
-      */
-      SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_DID_ENTER_BACKGROUND");
-      ctx->app_suspended = 1;
-      if (ctx->device_connected)
-        m8_pause_processing();
-      break;
-    case SDL_EVENT_LOW_MEMORY:
-      /* You will get this when your app is paused and iOS wants more memory.
-         Release as much memory as possible.
-      */
-      break;
-    case SDL_EVENT_WILL_ENTER_BACKGROUND:
-      /* Prepare your app to go into the background.  Stop loops, etc.
-         This gets called when the user hits the home button, or gets a call.
-      */
-      break;
-    case SDL_EVENT_WILL_ENTER_FOREGROUND:
-      /* This call happens when your app is coming back to the foreground.
-         Restore all your state here.
-      */
-      SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_WILL_ENTER_FOREGROUND");
-      break;
-    case SDL_EVENT_DID_ENTER_FOREGROUND:
-      /* Restart your loops here.
-         Your app is interactive and getting CPU again.
-      */
-      SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Received SDL_EVENT_DID_ENTER_FOREGROUND");
-      ctx->app_suspended = 0;
-      if (ctx->device_connected) {
-        m8_resume_processing();
-      }
-    default:
-      break;
-    }
-  return ret_val;
+    SDL_Log("Shutting down.");
+    SDL_Quit();
+  }
 }
\ No newline at end of file
--- a/src/render.c
+++ b/src/render.c
@@ -77,6 +77,8 @@
     return false;
   }
 
+  SDL_SetRenderVSync(rend, 1);
+
   if (!SDL_SetRenderLogicalPresentation(rend, texture_width, texture_height, window_scaling_mode)) {
     SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set renderer logical presentation: %s",
                  SDL_GetError());
--