shithub: m8c

Download patch

ref: 7ee19d3d5baa4da0fb7a06f82b5fb2a3f233fd4f
parent: b892a5ab495e3d92a14955d8daf9fece5c651167
parent: bfa999b28198f019b9e9fe9a66363a6a9bb85c76
author: Jonne Kokkonen <jonne.kokkonen@gmail.com>
date: Fri Mar 28 07:50:09 EDT 2025

Merge pull request #188 from laamaa/refactoring

Refactor and enhance input handling, logging, and initialization

--- a/README.md
+++ b/README.md
@@ -21,11 +21,12 @@
 
 Many thanks to:
 
-* Trash80 for the great M8 hardware and the original font (stealth57.ttf) that was converted to a bitmap for use in the
+* Trash80: For the great M8 hardware and the original fonts that were converted to a bitmap for use in the
   progam.
-* driedfruit for a wonderful little routine to blit inline bitmap fonts, https://github.com/driedfruit/SDL_inprint/
-* marcinbor85 for the slip handling routine, https://github.com/marcinbor85/slip
-* turbolent for the great Golang-based g0m8 application, which I used as reference on how the M8 serial protocol works.
+* driedfruit: For a wonderful little routine to blit inline bitmap
+  fonts, [SDL_inprint](https://github.com/driedfruit/SDL_inprint/)
+* marcinbor85: For the slip handling routine, https://github.com/marcinbor85/slip
+* turbolent: For the great Golang-based g0m8 application, which I used as reference on how the M8 serial protocol works.
 * *Everyone who's contributed to m8c!*
 
 Disclaimer: I'm not a coder and hardly understand C, use at your own risk :)
--- a/src/backends/m8.h
+++ b/src/backends/m8.h
@@ -5,13 +5,19 @@
 
 #include "../config.h"
 
+enum return_codes {
+  DEVICE_DISCONNECTED = 0,
+  DEVICE_PROCESSING = 1,
+  DEVICE_FATAL_ERROR = -1
+};
+
 int m8_initialize(int verbose, const char *preferred_device);
 int m8_list_devices(void);
 int m8_reset_display(void);
 int m8_enable_and_reset_display(void);
-int m8_send_msg_controller(const unsigned char input);
-int m8_send_msg_keyjazz(const unsigned char note, unsigned char velocity);
-int m8_process_data(config_params_s conf);
+int m8_send_msg_controller(unsigned char input);
+int m8_send_msg_keyjazz(unsigned char note, unsigned char velocity);
+int m8_process_data(const config_params_s *conf);
 int m8_close(void);
 
 #endif
\ No newline at end of file
--- a/src/backends/m8_libserialport.c
+++ b/src/backends/m8_libserialport.c
@@ -13,8 +13,8 @@
 #include "../command.h"
 #include "../config.h"
 #include "m8.h"
-#include "slip.h"
 #include "queue.h"
+#include "slip.h"
 
 #define SERIAL_READ_SIZE 1024  // maximum amount of bytes to read from the serial in one pass
 #define SERIAL_READ_DELAY_MS 4 // delay between serial reads in milliseconds
@@ -24,7 +24,6 @@
 static uint8_t serial_buffer[SERIAL_READ_SIZE] = {0};
 static uint8_t slip_buffer[SERIAL_READ_SIZE] = {0};
 static slip_handler_s slip;
-static uint16_t zero_byte_packets = 0; // used to detect device disconnection
 message_queue_s queue;
 
 SDL_Thread *serial_thread = NULL;
@@ -39,44 +38,20 @@
 // Helper function for error handling
 static int check(enum sp_return result);
 
-int send_message_to_queue(uint8_t *data, const uint32_t size) {
+static int send_message_to_queue(uint8_t *data, const uint32_t size) {
   push_message(&queue, data, size);
   return 1;
 }
 
-int m8_send_msg_controller(const uint8_t input) {
-  const char buf[2] = {'C', input};
-  const size_t nbytes = 2;
-  const int result = sp_blocking_write(m8_port, buf, nbytes, 5);
-  if (result != nbytes) {
-    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending input, code %d", result);
-    return -1;
-  }
-  return 1;
-}
-
-int m8_send_msg_keyjazz(const uint8_t note, uint8_t velocity) {
-  if (velocity > 0x7F)
-    velocity = 0x7F;
-  const char buf[3] = {'K', note, velocity};
-  const size_t nbytes = 3;
-  const int result = sp_blocking_write(m8_port, buf, nbytes, 5);
-  if (result != nbytes) {
-    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending keyjazz, code %d", result);
-    return -1;
-  }
-
-  return 1;
-}
-
-int disconnect() {
+static int disconnect() {
   SDL_Log("Disconnecting M8");
 
   // wait for serial processing thread to finish
   thread_params.should_stop = 1;
   SDL_WaitThread(serial_thread, NULL);
+  destroy_queue(&queue);
 
-  const char buf[1] = {'D'};
+  const unsigned char buf[1] = {'D'};
 
   int result = sp_blocking_write(m8_port, buf, 1, 5);
   if (result != 1) {
@@ -83,6 +58,7 @@
     SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending disconnect, code %d", result);
     result = 0;
   }
+
   sp_close(m8_port);
   sp_free_port(m8_port);
   m8_port = NULL;
@@ -105,27 +81,6 @@
   return 0;
 }
 
-int m8_list_devices() {
-  struct sp_port **port_list;
-  const enum sp_return result = sp_list_ports(&port_list);
-
-  if (result != SP_OK) {
-    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "sp_list_ports() failed!\n");
-    return 1;
-  }
-
-  for (int i = 0; port_list[i] != NULL; i++) {
-    const struct sp_port *port = port_list[i];
-
-    if (detect_m8_serial_device(port)) {
-      SDL_Log("Found M8 device: %s", sp_get_port_name(port));
-    }
-  }
-
-  sp_free_port_list(port_list);
-  return 0;
-}
-
 static void process_received_bytes(const uint8_t *buffer, int bytes_read, slip_handler_s *slip) {
   const uint8_t *cur = buffer;
   const uint8_t *end = buffer + bytes_read;
@@ -137,8 +92,8 @@
   }
 }
 
-int thread_process_serial_data(void *data) {
-  thread_params_s *thread_params = data;
+static int thread_process_serial_data(void *data) {
+  const thread_params_s *thread_params = data;
 
   while (!thread_params->should_stop) {
     // attempt to read from serial port
@@ -175,38 +130,67 @@
   return 1;
 }
 
-int m8_initialize(const int verbose, const char *preferred_device) {
-  if (m8_port != NULL) {
-    // Port is already initialized
-    return 1;
+// Helper function for error handling.
+static int check(const enum sp_return result) {
+  char *error_message;
+
+  switch (result) {
+  case SP_ERR_ARG:
+    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Invalid argument");
+    break;
+  case SP_ERR_FAIL:
+    error_message = sp_last_error_message();
+    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Failed: %s", error_message);
+    sp_free_error_message(error_message);
+    break;
+  case SP_ERR_SUPP:
+    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Not supported");
+    break;
+  case SP_ERR_MEM:
+    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Couldn't allocate memory");
+    break;
+  case SP_OK:
+  default:
+    break;
   }
+  return result;
+}
 
-  // settings for the slip packet handler
-  static const slip_descriptor_s slip_descriptor = {
-      .buf = slip_buffer,
-      .buf_size = sizeof(slip_buffer),
-      .recv_message = send_message_to_queue, // complete slip packets callback
-  };
+// Extracted function for initializing threads and message queue
+static int initialize_serial_thread() {
 
-  slip_init(&slip, &slip_descriptor);
+  init_queue(&queue);
+  thread_params.should_stop = 0;
+  serial_thread = SDL_CreateThread(thread_process_serial_data, "SerialThread", &thread_params);
 
-  if (verbose)
-    SDL_Log("Looking for USB serial devices.\n");
+  if (!serial_thread) {
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "SDL_CreateThread Error: %s", SDL_GetError());
+    SDL_Quit();
+    return 0;
+  }
+
+  return 1;
+}
+
+// Extracted function for detecting and selecting the M8 device
+static int find_and_select_device(const char *preferred_device) {
   struct sp_port **port_list;
-  enum sp_return port_result = sp_list_ports(&port_list);
+  const enum sp_return port_result = sp_list_ports(&port_list);
+
   if (port_result != SP_OK) {
-    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "sp_list_ports() failed!\n");
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "sp_list_ports() failed!");
     return 0;
   }
 
-  // Iterate through the ports. When port_list[i] is NULL this indicates the end of the list.
   for (int i = 0; port_list[i] != NULL; i++) {
     const struct sp_port *port = port_list[i];
 
     if (detect_m8_serial_device(port)) {
       char *port_name = sp_get_port_name(port);
-      SDL_Log("Found M8 in %s.\n", port_name);
+      SDL_Log("Found M8 in %s", port_name);
       sp_copy_port(port, &m8_port);
+
+      // Break if preferred device is found
       if (preferred_device != NULL && strcmp(preferred_device, port_name) == 0) {
         SDL_Log("Found preferred device, breaking");
         break;
@@ -215,60 +199,113 @@
   }
 
   sp_free_port_list(port_list);
+  return (m8_port != NULL);
+}
 
-  if (m8_port == NULL) {
-    if (verbose)
+int m8_initialize(const int verbose, const char *preferred_device) {
+  if (m8_port != NULL) {
+    // Port is already initialized
+    return 1;
+  }
+
+  // Initialize slip descriptor
+  static const slip_descriptor_s slip_descriptor = {
+      .buf = slip_buffer,
+      .buf_size = sizeof(slip_buffer),
+      .recv_message = send_message_to_queue,
+  };
+  slip_init(&slip, &slip_descriptor);
+
+  if (verbose) {
+    SDL_Log("Looking for USB serial devices.\n");
+  }
+
+  // Detect and select M8 device
+  if (!find_and_select_device(preferred_device)) {
+    if (verbose) {
       SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Cannot find a M8");
+    }
     return 0;
   }
 
-  SDL_Log("Opening and configuring port");
+  // Configure serial port
   if (!configure_serial_port(m8_port)) {
     return 0;
   }
 
-  init_queue(&queue);
-  thread_params.should_stop = 0;
-  serial_thread = SDL_CreateThread(thread_process_serial_data, "SerialThread", &thread_params);
+  // Initialize message queue and threads
+  return initialize_serial_thread();
+}
 
-  if (!serial_thread) {
-    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "SDL_CreateThread Error: %s\n", SDL_GetError());
-    SDL_Quit();
-    return 0;
+int m8_send_msg_controller(const uint8_t input) {
+  SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Sending controller input %d", input);
+  const unsigned char buf[2] = {'C', input};
+  const size_t nbytes = 2;
+  const int result = sp_blocking_write(m8_port, buf, nbytes, 5);
+  if (result != nbytes) {
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending input, code %d", result);
+    return -1;
   }
   return 1;
 }
 
-// Helper function for error handling.
-static int check(const enum sp_return result) {
-  char *error_message;
+int m8_send_msg_keyjazz(const uint8_t note, uint8_t velocity) {
 
-  switch (result) {
-  case SP_ERR_ARG:
-    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Invalid argument.\n");
-    break;
-  case SP_ERR_FAIL:
-    error_message = sp_last_error_message();
-    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Failed: %s\n", error_message);
-    sp_free_error_message(error_message);
-    break;
-  case SP_ERR_SUPP:
-    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Not supported.\n");
-    break;
-  case SP_ERR_MEM:
-    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Couldn't allocate memory.\n");
-    break;
-  case SP_OK:
-  default:
-    break;
+  // Cap velocity to 7bits
+  if (velocity > 0x7F)
+    velocity = 0x7F;
+
+  // Special case for note off
+  if (note == 0xFF && velocity == 0x00) {
+    SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Sending keyjazz note off");
+    const unsigned char buf[2] = {'K', 0xFF};
+    const size_t nbytes = 2;
+    const int result = sp_blocking_write(m8_port, buf, nbytes, 5);
+    if (result != nbytes) {
+      SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending keyjazz, code %d", result);
+      return -1;
+    }
+    return 1;
   }
-  return result;
+
+  // Regular note on message
+  SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Sending keyjazz note %d, velocity %d", note, velocity);
+  const unsigned char buf[3] = {'K', note, velocity};
+  const size_t nbytes = 3;
+  const int result = sp_blocking_write(m8_port, buf, nbytes, 5);
+  if (result != nbytes) {
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending keyjazz, code %d", result);
+    return -1;
+  }
+
+  return 1;
 }
 
+int m8_list_devices() {
+  struct sp_port **port_list;
+  const enum sp_return result = sp_list_ports(&port_list);
+
+  if (result != SP_OK) {
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "sp_list_ports() failed!\n");
+    return 1;
+  }
+
+  for (int i = 0; port_list[i] != NULL; i++) {
+    const struct sp_port *port = port_list[i];
+
+    if (detect_m8_serial_device(port)) {
+      SDL_Log("Found M8 device: %s", sp_get_port_name(port));
+    }
+  }
+
+  sp_free_port_list(port_list);
+  return 0;
+}
+
 int m8_reset_display() {
   SDL_Log("Reset display\n");
 
-  const char buf[1] = {'R'};
+  const unsigned char buf[1] = {'R'};
   const int result = sp_blocking_write(m8_port, buf, 1, 5);
   if (result != 1) {
     SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error resetting M8 display, code %d", result);
@@ -292,12 +329,12 @@
   return result;
 }
 
-int m8_process_data(const config_params_s conf) {
+int m8_process_data(const config_params_s *conf) {
   static unsigned int empty_cycles = 0;
 
   // Device likely has been disconnected
   if (m8_port == NULL) {
-    return 0;
+    return DEVICE_DISCONNECTED;
   }
 
   if (queue_size(&queue) > 0) {
@@ -305,18 +342,23 @@
     empty_cycles = 0;
     size_t length = 0;
     while ((command = pop_message(&queue, &length)) != NULL) {
-      process_command(command, length);
+      if (length > 0) {
+        process_command(command, length);
+      }
       SDL_free(command);
     }
   } else {
     empty_cycles++;
-    if (empty_cycles >= conf.wait_packets) {
-      SDL_Log("No messages received for %d cycles, assuming device disconnected", empty_cycles);
+    if (empty_cycles >= conf->wait_packets) {
+      SDL_LogError(SDL_LOG_CATEGORY_SYSTEM,
+                   "No messages received for %d cycles, assuming device disconnected",
+                   empty_cycles);
+      empty_cycles = 0;
       disconnect();
-      return 0;
+      return DEVICE_DISCONNECTED;
     }
   }
-  return 1;
+  return DEVICE_PROCESSING;
 }
 
 int m8_close() { return disconnect(); }
--- a/src/backends/m8_rtmidi.c
+++ b/src/backends/m8_rtmidi.c
@@ -216,7 +216,13 @@
 }
 
 int m8_send_msg_controller(const unsigned char input) {
-  const unsigned char input_sysex[9] = {0xF0, 0x00, 0x02, 0x61, 0x00, 0x00, 'C', input, 0xF7};
+  SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "MIDI key inputs not implemented yet");
+  return 0;
+#if 0
+  SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Sending controller input 0x%02X", input);
+  // Encode a 8bit byte to two 7-bit bytes
+  //const unsigned char sysex_encoded_input[2] = {input & 0x7F, (input >> 7) & 0x01};
+  const unsigned char input_sysex[10] = {0xF0, 0x00, 0x02, 0x61, 0x00, 0x00, 'C', sysex_encoded_input[0], sysex_encoded_input[1], 0xF7};
   const int result = rtmidi_out_send_message(midi_out, &input_sysex[0], sizeof(input_sysex));
   if (result != 0) {
     SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Failed to send key input message");
@@ -223,12 +229,29 @@
     return 0;
   }
   return 1;
+#endif
 }
 
 int m8_send_msg_keyjazz(const unsigned char note, unsigned char velocity) {
+  SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "MIDI keyjazz not implemented yet");
+  return 0;
+#if 0
   if (velocity > 0x7F) {
     velocity = 0x7F;
   }
+
+  // Special case for note off
+  if (note == 0xFF && velocity == 0x00) {
+    const unsigned char all_notes_off_sysex[9] = {0xF0, 0x00, 0x02, 0x61, 0x00, 0x00, 'K', 0xFF, 0xF7};
+    const int result =
+        rtmidi_out_send_message(midi_out, &all_notes_off_sysex[0], sizeof(all_notes_off_sysex));
+    if (result != 0) {
+      SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Failed to send all notes off");
+      return 0;
+    }
+    return 1;
+  }
+
   const unsigned char keyjazz_sysex[10] = {0xF0, 0x00, 0x02, 0x61,     0x00,
                                            0x00, 'K',  note, velocity, 0xF7};
   const int result = rtmidi_out_send_message(midi_out, &keyjazz_sysex[0], sizeof(keyjazz_sysex));
@@ -237,9 +260,10 @@
     return 0;
   }
   return 1;
+#endif
 }
 
-int m8_process_data(config_params_s conf) {
+int m8_process_data(const config_params_s *conf) {
 
   static unsigned int empty_cycles = 0;
 
@@ -253,7 +277,7 @@
     }
   } else {
     empty_cycles++;
-    if (empty_cycles >= conf.wait_packets) {
+    if (empty_cycles >= conf->wait_packets) {
       SDL_Log("No messages received for %d cycles, assuming device disconnected", empty_cycles);
       close_and_free_midi_ports();
       return 0;
--- a/src/backends/queue.h
+++ b/src/backends/queue.h
@@ -26,6 +26,13 @@
 void init_queue(message_queue_s *queue);
 
 /**
+ * Destroys the message queue and releases associated resources.
+ *
+ * @param queue A pointer to the message queue structure to be destroyed.
+ */
+void destroy_queue(message_queue_s *queue);
+
+/**
  * Retrieves and removes a message from the front of the message queue.
  * If the queue is empty, the function returns NULL.
  *
--- a/src/input.c
+++ b/src/input.c
@@ -14,228 +14,156 @@
 
 static input_msg_s key = {normal, 0, 0, 0};
 
-int input_process(config_params_s conf, enum app_state *app_state) {
-  static uint8_t prev_input = 0;
-  static uint8_t prev_note = 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 current inputs
-  const input_msg_s input = input_get_msg(&conf);
+// Get note value for a scancode, or -1 if not found
+static int get_note_for_scancode(SDL_Scancode scancode) {
 
-  switch (input.type) {
-  case normal:
-    if (input.value != prev_input) {
-      prev_input = input.value;
-      m8_send_msg_controller(input.value);
+  // 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;
     }
-    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;
+  return -1; // Not a note key
 }
 
-uint8_t toggle_input_keyjazz() {
-  keyjazz_enabled = !keyjazz_enabled;
-  SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, keyjazz_enabled ? "Keyjazz enabled" : "Keyjazz disabled");
-  return keyjazz_enabled;
-}
+// Handle octave and velocity changes
+static void handle_keyjazz_settings(const SDL_Event *event, const config_params_s *conf) {
 
-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};
-  switch (event->key.scancode) {
-  case SDL_SCANCODE_Z:
-    key.value = keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_S:
-    key.value = 1 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_X:
-    key.value = 2 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_D:
-    key.value = 3 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_C:
-    key.value = 4 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_V:
-    key.value = 5 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_G:
-    key.value = 6 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_B:
-    key.value = 7 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_H:
-    key.value = 8 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_N:
-    key.value = 9 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_J:
-    key.value = 10 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_M:
-    key.value = 11 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_Q:
-    key.value = 12 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_2:
-    key.value = 13 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_W:
-    key.value = 14 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_3:
-    key.value = 15 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_E:
-    key.value = 16 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_R:
-    key.value = 17 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_5:
-    key.value = 18 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_T:
-    key.value = 19 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_6:
-    key.value = 20 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_Y:
-    key.value = 21 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_7:
-    key.value = 22 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_U:
-    key.value = 23 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_I:
-    key.value = 24 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_9:
-    key.value = 25 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_O:
-    key.value = 26 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_0:
-    key.value = 27 + keyjazz_base_octave * 12;
-    break;
-  case SDL_SCANCODE_P:
-    key.value = 28 + keyjazz_base_octave * 12;
-    break;
-  default:
-    key.type = normal;
-    if (event->key.repeat > 0 || event->key.type == SDL_EVENT_KEY_UP) {
-      break;
+  // 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);
     }
-    if (event->key.scancode == conf->key_jazz_dec_octave) {
-      if (keyjazz_base_octave > 0) {
-        keyjazz_base_octave--;
-        display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
-      }
-    } else if (event->key.scancode == conf->key_jazz_inc_octave) {
-      if (keyjazz_base_octave < 8) {
-        keyjazz_base_octave++;
-        display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
-      }
-    } else if (event->key.scancode == conf->key_jazz_dec_velocity) {
-      if ((event->key.mod & SDL_KMOD_ALT) > 0) {
-        if (keyjazz_velocity > 1)
-          keyjazz_velocity -= 1;
-      } else {
-        if (keyjazz_velocity > 0x10)
-          keyjazz_velocity -= 0x10;
-      }
+  } 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);
-    } else if (event->key.scancode == conf->key_jazz_inc_velocity) {
-      if ((event->key.mod & SDL_KMOD_ALT) > 0) {
-        if (keyjazz_velocity < 0x7F)
-          keyjazz_velocity += 1;
-      } else {
-        if (keyjazz_velocity < 0x6F)
-          keyjazz_velocity += 0x10;
-      }
-      display_keyjazz_overlay(1, keyjazz_base_octave, keyjazz_velocity);
     }
-    break;
   }
+}
 
+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};
 
-  if (event->key.scancode == conf->key_up) {
-    key.value = key_up;
-  } else if (event->key.scancode == conf->key_left) {
-    key.value = key_left;
-  } else if (event->key.scancode == conf->key_down) {
-    key.value = key_down;
-  } else if (event->key.scancode == conf->key_right) {
-    key.value = key_right;
-  } else if (event->key.scancode == conf->key_select ||
-             event->key.scancode == conf->key_select_alt) {
-    key.value = key_select;
-  } else if (event->key.scancode == conf->key_start ||
-             event->key.scancode == conf->key_start_alt) {
-    key.value = key_start;
-  } else if (event->key.scancode == conf->key_opt ||
-             event->key.scancode == conf->key_opt_alt) {
-    key.value = key_opt;
-  } else if (event->key.scancode == conf->key_edit ||
-             event->key.scancode == conf->key_edit_alt) {
-    key.value = key_edit;
-  } else if (event->key.scancode == conf->key_delete) {
-    key.value = key_opt | key_edit;
-  } else if (event->key.scancode == conf->key_reset) {
-    key = (input_msg_s){special, msg_reset_display, 0, 0};
-  } else if (event->key.scancode == conf->key_toggle_audio) {
-    key = (input_msg_s){special, msg_toggle_audio, 0, 0};
-  } else {
-    key.value = 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
-void handle_sdl_events(config_params_s *conf) {
+static void handle_sdl_events(config_params_s *conf) {
 
   static int prev_key_analog = 0;
   static unsigned int ticks_window_resized = 0;
@@ -270,11 +198,11 @@
       break;
 
     case SDL_EVENT_WINDOW_RESIZED:
-        if (SDL_GetTicks() - ticks_window_resized > 500) {
-          SDL_Log("Resizing window...");
-          key = (input_msg_s){special, msg_reset_display, 0, 0};
-          ticks_window_resized = SDL_GetTicks();
-        }
+      if (SDL_GetTicks() - ticks_window_resized > 500) {
+        SDL_Log("Resizing window...");
+        key = (input_msg_s){special, msg_reset_display, 0, 0};
+        ticks_window_resized = SDL_GetTicks();
+      }
       break;
 
     case SDL_EVENT_KEY_DOWN:
@@ -337,6 +265,55 @@
       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
--- a/src/input.h
+++ b/src/input.h
@@ -49,6 +49,6 @@
 } 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);
+int input_process(config_params_s *conf, enum app_state *app_state);
 
 #endif
--- a/src/main.c
+++ b/src/main.c
@@ -17,15 +17,26 @@
 #include "input.h"
 #include "render.h"
 
+#include <stdlib.h>
+
 enum app_state app_state = WAIT_FOR_DEVICE;
 
 // Handle CTRL+C / SIGINT, SIGKILL etc.
-void signal_handler(int unused) {
+static void signal_handler(int unused) {
   (void)unused;
   app_state = QUIT;
 }
 
-void do_wait_for_device(const char *preferred_device, unsigned char *m8_connected,
+static void initialize_signals() {
+  signal(SIGINT, signal_handler);
+  signal(SIGTERM, signal_handler);
+#ifdef SIGQUIT
+  signal(SIGQUIT, signal_handler);
+#endif
+}
+
+
+static void do_wait_for_device(const char *preferred_device, unsigned char *m8_connected,
                         config_params_s *conf) {
   static Uint64 ticks_poll_device = 0;
   static Uint64 ticks_update_screen = 0;
@@ -77,124 +88,114 @@
   }
 }
 
-int main(const int argc, char *argv[]) {
-
-  char *preferred_device = NULL;
-  unsigned char m8_connected = 0;
-
-  if (argc == 2 && SDL_strcmp(argv[1], "--list") == 0) {
-    return m8_list_devices();
+static config_params_s initialize_config(int argc, char *argv[], char **preferred_device, char **config_filename) {
+  for (int i = 1; i < argc; i++) {
+    if (SDL_strcmp(argv[i], "--list") == 0) {
+      exit(m8_list_devices());
+    }
+    if (SDL_strcmp(argv[i], "--dev") == 0 && i + 1 < argc) {
+      *preferred_device = argv[i + 1];
+      SDL_Log("Using preferred device: %s.\n", *preferred_device);
+      i++;
+    } else if (SDL_strcmp(argv[i], "--config") == 0 && i + 1 < argc) {
+      *config_filename = argv[i + 1];
+      SDL_Log("Using config file: %s.\n", *config_filename);
+      i++;
+    }
   }
 
-  if (argc == 3 && SDL_strcmp(argv[1], "--dev") == 0) {
-    preferred_device = argv[2];
-    SDL_Log("Using preferred device %s.\n", preferred_device);
-  }
-
-  char *config_filename = NULL;
-  if (argc == 3 && SDL_strcmp(argv[1], "--config") == 0) {
-    config_filename = argv[2];
-    SDL_Log("Using config file %s.\n", config_filename);
-  }
-
-  // Initialize the config to defaults
-  config_params_s conf = config_initialize(config_filename);
-  // Read in the params from the configfile if present
+  config_params_s conf = config_initialize(*config_filename);
   config_read(&conf);
+  return conf;
+}
 
-  signal(SIGINT, signal_handler);
-  signal(SIGTERM, signal_handler);
-#ifdef SIGQUIT
-  signal(SIGQUIT, signal_handler);
-#endif
-
-  // First device detection to avoid SDL init if it isn't necessary. To be run
-  // only if we shouldn't wait for M8 to be connected.
-  if (conf.wait_for_device == 0) {
-    m8_connected = m8_initialize(1, preferred_device);
-    if (m8_connected == 0) {
-      return 1;
-    }
+static void cleanup_resources(const unsigned char device_connected, const config_params_s *conf) {
+  if (conf->audio_enabled) {
+    audio_close();
   }
+  gamecontrollers_close();
+  renderer_close();
+  inline_font_close();
+  if (device_connected) {
+    m8_close();
+  }
+  SDL_Quit();
+  SDL_Log("Shutting down.");
+}
 
-  // initialize all SDL systems
-  if (renderer_initialize(conf.init_fullscreen) == false) {
-    SDL_Quit();
-    return 1;
+static unsigned char handle_device_initialization(unsigned char wait_for_device, const char *preferred_device) {
+  const unsigned char device_connected = m8_initialize(1, preferred_device);
+  if (!wait_for_device && device_connected == 0) {
+    SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Device not detected!");
+    exit(EXIT_FAILURE);
   }
-  app_state = QUIT;
+  return device_connected;
+}
 
-  // initial scan for (existing) gamepads
-  gamecontrollers_initialize();
+static void main_loop(config_params_s *conf, const char *preferred_device) {
+  unsigned char device_connected = 0;
 
-#ifndef NDEBUG
-  SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG);
-  SDL_LogDebug(SDL_LOG_CATEGORY_TEST, "Running a Debug build");
-#endif
-
-  // main loop begin
   do {
-    // try to init connection to M8
-    m8_connected = m8_initialize(1, preferred_device);
-    if (m8_connected == 1 && m8_enable_and_reset_display() == 1) {
-      if (conf.audio_enabled == 1) {
-        audio_initialize(conf.audio_device_name, conf.audio_buffer_size);
-        // if audio is enabled, reset the display for second time to avoid glitches
-        m8_reset_display();
+    device_connected = m8_initialize(1, preferred_device);
+    if (device_connected && m8_enable_and_reset_display()) {
+      if (conf->audio_enabled) {
+        audio_initialize(conf->audio_device_name, conf->audio_buffer_size);
+        m8_reset_display();  // Avoid display glitches.
       }
       app_state = RUN;
     } else {
-      SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Device not detected on initial check.");
-      if (conf.wait_for_device == 1) {
-        app_state = WAIT_FOR_DEVICE;
-      } else {
-        app_state = QUIT;
-      }
+      SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Device not detected.");
+      app_state = conf->wait_for_device ? WAIT_FOR_DEVICE : QUIT;
     }
 
-    // wait until device is connected
-    if (conf.wait_for_device == 1) {
-      do_wait_for_device(preferred_device, &m8_connected, &conf);
-    } else {
-      // classic startup behaviour, exit if device is not found
-      if (m8_connected == 0) {
-        if (conf.audio_enabled == 1) {
-          audio_close();
-        }
-        gamecontrollers_close();
-        renderer_close();
-        inline_font_close();
-        SDL_Quit();
-        return 1;
-      }
+    if (conf->wait_for_device && app_state == WAIT_FOR_DEVICE) {
+      do_wait_for_device(preferred_device, &device_connected, conf);
+    } else if (!device_connected && app_state != WAIT_FOR_DEVICE) {
+      cleanup_resources(0, conf);
+      exit(EXIT_FAILURE);
     }
 
-    // main loop
+    // Handle input, process data, and render screen while running.
     while (app_state == RUN) {
       input_process(conf, &app_state);
       const int result = m8_process_data(conf);
-      if (result == 0) {
-        // Device disconnected
-        m8_connected = 0;
+      if (result == DEVICE_DISCONNECTED) {
+        device_connected = 0;
         app_state = WAIT_FOR_DEVICE;
         audio_close();
-      } else if (result == -1) {
-        // Fatal error
+      } else if (result == DEVICE_FATAL_ERROR) {
         app_state = QUIT;
       }
       render_screen();
-      SDL_Delay(conf.idle_ms);
+      SDL_Delay(conf->idle_ms);
     }
   } while (app_state > QUIT);
-  // Main loop end
 
-  // Exit, clean up
-  SDL_Log("Shutting down");
-  audio_close();
-  gamecontrollers_close();
-  renderer_close();
-  if (m8_connected == 1)
-    m8_close();
-  SDL_Quit();
-  return 0;
+  cleanup_resources(device_connected, conf);
+}
+
+
+int main(const int argc, char *argv[]) {
+  char *preferred_device = NULL;
+  char *config_filename = NULL;
+
+  config_params_s conf = initialize_config(argc, argv, &preferred_device, &config_filename);
+  initialize_signals();
+
+  const unsigned char initial_device_connected = handle_device_initialization(conf.wait_for_device, preferred_device);
+  if (!renderer_initialize(conf.init_fullscreen)) {
+    SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Failed to initialize renderer.");
+    cleanup_resources(initial_device_connected, &conf);
+    return EXIT_FAILURE;
+  }
+
+  gamecontrollers_initialize();
+
+#ifndef NDEBUG
+  SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG);
+  SDL_LogDebug(SDL_LOG_CATEGORY_TEST, "Running a Debug build");
+#endif
+
+  main_loop(&conf, preferred_device);
+  return EXIT_SUCCESS;
 }
--