shithub: m8c

Download patch

ref: 8e0bad662f99eebde515dbc63c5670b0eb0e87e9
parent: a71d4cd80beec7769ea9e63c41bdf2a5e42f3ab1
parent: 15d162059b0a92c4f94201018be5226cd58b7e14
author: Jonne Kokkonen <jonne.kokkonen@gmail.com>
date: Tue Apr 20 13:19:47 EDT 2021

Merge pull request #5 from laamaa/feature/keyjazz

initial keyjazz support, reorganize input.c/.h

--- a/README.md
+++ b/README.md
@@ -66,18 +66,29 @@
 
 If the stars are aligned correctly, you should see the M8 screen.
 
+-----------
+
+## Keyboard mappings
+
 Keys for controlling the progam:
 
-* Up arrow / i = up
-* Down arrow / k = down
-* Left arrow / j = left
-* Right arrow / l = right
-* a = select
-* s = start
-* z = opt
-* x = edit
+* Up arrow = up
+* Down arrow = down
+* Left arrow = left
+* Right arrow = right
+* a / left shift = select
+* s / space = start
+* z / left alt = opt
+* x / left ctrl = edit
 
-You can toggle full screen mode with ALT+Enter and quit the program with CTRL+q.
+Additional controls:
+* Alt + enter = toggle full screen / windowed
+* Alt + F4 = quit program
+* Delete = opt+edit (deletes a row)
+* Esc = toggle keyjazz on/off 
+
+Keyjazz allows to enter notes with keyboard, oldschool tracker-style. The layout is two octaves, starting from keys Z and Q.
+When keyjazz is active, regular a/s/z/x keys are disabled.
 
 There is also a so-called gamepad support, currently it's working only if there's only one pad connected and it's a Retrobit NES-style controller, since the settings are hardcoded.
 
--- a/input.c
+++ b/input.c
@@ -6,6 +6,7 @@
 
 #include "input.h"
 #include "render.h"
+#include "write.h"
 
 SDL_Joystick *js;
 
@@ -20,6 +21,14 @@
   key_edit = 1
 };
 
+uint8_t keyjazz_enabled = 0;
+uint8_t keyjazz_base_octave = 2;
+
+uint8_t toggle_input_keyjazz() {
+  keyjazz_enabled = !keyjazz_enabled;
+  return keyjazz_enabled;
+}
+
 int initialize_input() {
 
   int num_joysticks = SDL_NumJoysticks();
@@ -27,7 +36,6 @@
   if (num_joysticks > 0) {
     js = SDL_JoystickOpen(0);
     SDL_JoystickEventState(SDL_ENABLE);
-    fprintf(stderr, "Opened Joystick 0");
   }
 
   return num_joysticks;
@@ -35,19 +43,210 @@
 
 void close_input() { SDL_JoystickClose(js); }
 
-uint8_t process_input() {
+static input_msg_s handle_keyjazz(SDL_Event *event, uint8_t keyvalue) {
+  input_msg_s key = {normal, keyvalue};
+  switch (event->key.keysym.scancode) {
+  case SDL_SCANCODE_Z:
+    key.type = keyjazz;
+    key.value = keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_S:
+    key.type = keyjazz;
+    key.value = 1 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_X:
+    key.type = keyjazz;
+    key.value = 2 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_D:
+    key.type = keyjazz;
+    key.value = 3 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_C:
+    key.type = keyjazz;
+    key.value = 4 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_V:
+    key.type = keyjazz;
+    key.value = 5 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_G:
+    key.type = keyjazz;
+    key.value = 6 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_B:
+    key.type = keyjazz;
+    key.value = 7 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_H:
+    key.type = keyjazz;
+    key.value = 8 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_N:
+    key.type = keyjazz;
+    key.value = 9 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_J:
+    key.type = keyjazz;
+    key.value = 10 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_M:
+    key.type = keyjazz;
+    key.value = 11 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_Q:
+    key.type = keyjazz;
+    key.value = 12 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_2:
+    key.type = keyjazz;
+    key.value = 13 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_W:
+    key.type = keyjazz;
+    key.value = 14 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_3:
+    key.type = keyjazz;
+    key.value = 15 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_E:
+    key.type = keyjazz;
+    key.value = 16 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_R:
+    key.type = keyjazz;
+    key.value = 17 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_5:
+    key.type = keyjazz;
+    key.value = 18 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_T:
+    key.type = keyjazz;
+    key.value = 19 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_6:
+    key.type = keyjazz;
+    key.value = 20 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_Y:
+    key.type = keyjazz;
+    key.value = 21 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_7:
+    key.type = keyjazz;
+    key.value = 22 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_U:
+    key.type = keyjazz;
+    key.value = 23 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_I:
+    key.type = keyjazz;
+    key.value = 24 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_9:
+    key.type = keyjazz;
+    key.value = 25 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_O:
+    key.type = keyjazz;
+    key.value = 26 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_0:
+    key.type = keyjazz;
+    key.value = 27 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_P:
+    key.type = keyjazz;
+    key.value = 28 + keyjazz_base_octave * 12;
+    break;
+  case SDL_SCANCODE_KP_DIVIDE:
+    if (event->type == SDL_KEYDOWN && keyjazz_base_octave > 0) {
+      keyjazz_base_octave--;
+      display_keyjazz_overlay(1, keyjazz_base_octave);
+    }
+    break;
+  case SDL_SCANCODE_KP_MULTIPLY:
+    if (event->type == SDL_KEYDOWN && keyjazz_base_octave < 8) {
+      keyjazz_base_octave++;
+      display_keyjazz_overlay(1, keyjazz_base_octave);
+    }
+    break;
+  default:
+    break;
+  }
 
+  return key;
+}
+
+static input_msg_s handle_normal_keys(SDL_Event *event, uint8_t keyvalue) {
+  input_msg_s key = {normal, keyvalue};
+  switch (event->key.keysym.scancode) {
+
+  case SDL_SCANCODE_UP:
+    key.value = key_up;
+    break;
+
+  case SDL_SCANCODE_LEFT:
+    key.value = key_left;
+    break;
+
+  case SDL_SCANCODE_DOWN:
+    key.value = key_down;
+    break;
+
+  case SDL_SCANCODE_RIGHT:
+    key.value = key_right;
+    break;
+
+  case SDL_SCANCODE_LSHIFT:
+  case SDL_SCANCODE_A:
+    key.value = key_select;
+    break;
+
+  case SDL_SCANCODE_SPACE:
+  case SDL_SCANCODE_S:
+    key.value = key_start;
+    break;
+
+  case SDL_SCANCODE_LALT:
+  case SDL_SCANCODE_Z:
+    key.value = key_opt;
+    break;
+
+  case SDL_SCANCODE_LCTRL:
+  case SDL_SCANCODE_X:
+    key.value = key_edit;
+    break;
+
+  case SDL_SCANCODE_DELETE:
+    key.value = key_opt | key_edit;
+    break;
+
+  default:
+    key.value = 0;
+    break;
+  }
+  return key;
+}
+
+input_msg_s get_input_msg() {
+
   SDL_Event event;
-  uint8_t key = 0;
-  static uint8_t input = 0;
+  static uint8_t keycode = 0;
+  input_msg_s key = {normal, 0};
 
   SDL_PollEvent(&event);
 
   switch (event.type) {
 
+  // Handle SDL quit events (for example, X11 window close)
   case SDL_QUIT:
-    return 21;
+    return (input_msg_s){special, msg_quit};
 
+  // Joystick axis movement events (directional buttons)
   case SDL_JOYAXISMOTION:
 
     switch (event.jaxis.axis) {
@@ -55,16 +254,16 @@
     case 0:
 
       if (event.jaxis.value < 0) {
-        input |= key_left;
+        keycode |= key_left;
         break;
       }
       if (event.jaxis.value > 0) {
-        input |= key_right;
+        keycode |= key_right;
         break;
       }
       if (event.jaxis.value == 0) {
-        input &= ~key_left;
-        input &= ~key_right;
+        keycode &= ~key_left;
+        keycode &= ~key_right;
         break;
       }
 
@@ -71,18 +270,18 @@
     case 1:
 
       if (event.jaxis.value < 0) {
-        input |= key_up;
+        keycode |= key_up;
         break;
       }
 
       if (event.jaxis.value > 0) {
-        input |= key_down;
+        keycode |= key_down;
         break;
       }
 
       if (event.jaxis.value == 0) {
-        input &= ~key_up;
-        input &= ~key_down;
+        keycode &= ~key_up;
+        keycode &= ~key_down;
         break;
       }
     }
@@ -95,28 +294,33 @@
     switch (event.jbutton.button) {
 
     case 1:
-      key = key_edit;
+      key.type = normal;
+      key.value = key_edit;
       break;
     case 2:
-      key = key_opt;
+      key.type = normal;
+      key.value = key_opt;
       break;
     case 8:
-      key = key_select;
+      key.type = normal;
+      key.value = key_select;
       break;
     case 9:
-      key = key_start;
+      key.type = normal;
+      key.value = key_start;
       break;
     }
 
     if (event.type == SDL_JOYBUTTONDOWN)
-      input |= key;
+      keycode |= key.value;
     else
-      input &= ~key;
+      keycode &= ~key.value;
 
-    return input;
+    return (input_msg_s){key.type, keycode};
 
     break;
 
+  // Special keyboard events
   case SDL_KEYDOWN:
 
     // ALT+ENTER toggles fullscreen
@@ -123,59 +327,26 @@
     if (event.key.keysym.sym == SDLK_RETURN &&
         (event.key.keysym.mod & KMOD_ALT) > 0) {
       toggle_fullscreen();
+      break;
     }
 
-    // CTRL+q quits program
-    if (event.key.keysym.sym == SDLK_q &&
-        (event.key.keysym.mod & KMOD_CTRL) > 0) {
-      return 21; // arbitary quit code for main loop
+    // ALT+F4 quits program
+    if (event.key.keysym.sym == SDLK_F4 &&
+        (event.key.keysym.mod & KMOD_ALT) > 0) {
+      return (input_msg_s){special, msg_quit};
     }
 
-  case SDL_KEYUP:
-
-    switch (event.key.keysym.scancode) {
-
-    case SDL_SCANCODE_I:
-    case SDL_SCANCODE_UP:
-      key = key_up;
-      break;
-
-    case SDL_SCANCODE_J:
-    case SDL_SCANCODE_LEFT:
-      key = key_left;
-      break;
-
-    case SDL_SCANCODE_K:
-    case SDL_SCANCODE_DOWN:
-      key = key_down;
-      break;
-
-    case SDL_SCANCODE_L:
-    case SDL_SCANCODE_RIGHT:
-      key = key_right;
-      break;
-
-    case SDL_SCANCODE_A:
-      key = key_select;
-      break;
-
-    case SDL_SCANCODE_S:
-      key = key_start;
-      break;
-
-    case SDL_SCANCODE_Z:
-      key = key_opt;
-      break;
-
-    case SDL_SCANCODE_X:
-      key = key_edit;
-      break;
-
-    default:
-      key = 0;
-      break;
+    // ESC = toggle keyjazz
+    if (event.key.keysym.sym == SDLK_ESCAPE) {
+      display_keyjazz_overlay(toggle_input_keyjazz(), keyjazz_base_octave);
     }
 
+  // Normal keyboard inputs
+  case SDL_KEYUP:
+    key = handle_normal_keys(&event, 0);
+
+    if (keyjazz_enabled)
+      key = handle_keyjazz(&event, key.value);
     break;
 
   default:
@@ -182,10 +353,18 @@
     break;
   }
 
-  if (event.type == SDL_KEYDOWN)
-    input |= key;
-  else
-    input &= ~key;
+  // do not allow pressing multiple keys with keyjazz
+  if (key.type == normal) {
+    if (event.type == SDL_KEYDOWN)
+      keycode |= key.value;
+    else
+      keycode &= ~key.value;
+  } else {
+    if (event.type == SDL_KEYDOWN)
+      keycode = key.value;
+    else
+      keycode = 0;
+  }
 
-  return input;
+  return (input_msg_s){key.type, keycode};
 }
\ No newline at end of file
--- a/input.h
+++ b/input.h
@@ -1,10 +1,25 @@
 #ifndef INPUT_H_
 #define INPUT_H_
 
-#include <SDL2/SDL_joystick.h>
+#include <stdint.h>
 
+typedef enum input_type_t {
+  normal,
+  keyjazz,
+  special
+} input_type_t;
+
+typedef enum special_messages_t {
+  msg_quit
+} special_messages_t;
+
+typedef struct input_msg_s {
+  input_type_t type;
+  uint8_t value;
+} input_msg_s;
+
 int initialize_input();
 void close_input();
-uint8_t process_input();
+input_msg_s get_input_msg();
 
 #endif
\ No newline at end of file
--- a/main.c
+++ b/main.c
@@ -33,7 +33,8 @@
   static const slip_descriptor_s slip_descriptor = {
       .buf = slip_buffer,
       .buf_size = sizeof(slip_buffer),
-      .recv_message = process_command,  //the function where complete slip packets are processed further
+      .recv_message = process_command, // the function where complete slip
+                                       // packets are processed further
   };
 
   static slip_handler_s slip;
@@ -47,7 +48,7 @@
   if (argc > 1) {
     portname = argv[1];
   } else {
-    portname = "/dev/ttyACM1";
+    portname = "/dev/ttyACM0";
   }
   int port = init_serial(portname);
   if (port == -1)
@@ -62,7 +63,7 @@
   // initialize joystick etc.
   initialize_input();
 
-  static uint8_t prev_input = 0;
+  uint8_t prev_input = 0;
 
   // main loop
   while (run) {
@@ -84,23 +85,36 @@
       }
     }
 
-    uint8_t input = process_input();
+    input_msg_s input = get_input_msg();
 
-    if (input != prev_input) {
-      prev_input = input;
-      switch (input) {
-      case 21: // arbitary quit code
+    switch (input.type) {
+    case normal:
+      if (input.value != prev_input) {
+        prev_input = input.value;
+        send_msg_controller(port, input.value);
+      }
+      break;
+    case keyjazz:
+      if (input.value != prev_input) {
+        prev_input = input.value;
+        if (input.value != 0) {
+          send_msg_keyjazz(port, input.value, 64);
+        } else {
+          send_msg_keyjazz(port, 0, 0);
+        }
+      }
+      break;
+    case special:
+      switch (input.value) {
+      case msg_quit:
         run = 0;
         break;
-      default:
-        send_input(port, input);
-        break;
       }
-    } else {
+      break;
     }
 
     render_screen();
-    
+
     usleep(10);
   }
 
--- a/render.c
+++ b/render.c
@@ -47,7 +47,8 @@
 
   rend = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
 
-  font = TTF_OpenFontRW(SDL_RWFromMem(stealth57_ttf, stealth57_ttf_len), 1, font_size);
+  font = TTF_OpenFontRW(SDL_RWFromMem(stealth57_ttf, stealth57_ttf_len), 1,
+                        font_size);
 
   surface =
       SDL_CreateRGBSurfaceWithFormat(0, 320, 240, 8, SDL_PIXELFORMAT_ARGB8888);
@@ -153,6 +154,38 @@
       command->waveform[i] = 20;
     // draw the pixels directly to the surface
     set_pixel(surface, i, command->waveform[i], color);
+  }
+}
+
+void display_keyjazz_overlay(uint8_t show, uint8_t base_octave) {
+  if (show) {
+    struct draw_rectangle_command drc;
+    drc.color = (struct color){255, 0, 0};
+    drc.pos.x = 310;
+    drc.pos.y = 230;
+    drc.size.width = 5;
+    drc.size.height = 5;
+
+    draw_rectangle(&drc);
+
+    struct draw_character_command dcc;
+    dcc.background = (struct color){0, 0, 0};
+    dcc.foreground = (struct color){200, 200, 200};
+    dcc.c = base_octave+48;
+    dcc.pos.x = 300;
+    dcc.pos.y = 226;
+
+    draw_character(&dcc);
+    
+  } else {
+    struct draw_rectangle_command drc;
+    drc.color = (struct color){0, 0, 0};
+    drc.pos.x = 300;
+    drc.pos.y = 226;
+    drc.size.width = 20;
+    drc.size.height = 14;
+
+    draw_rectangle(&drc);
   }
 }
 
--- a/render.h
+++ b/render.h
@@ -13,5 +13,6 @@
 
 void render_screen();
 void toggle_fullscreen();
+void display_keyjazz_overlay(uint8_t show, uint8_t base_octave);
 
 #endif
\ No newline at end of file
--- a/write.c
+++ b/write.c
@@ -30,14 +30,12 @@
 }
 
 int disconnect(int port) {
-  char buf[20];
-  size_t nbytes;
+  char buf[1] = {'D'};
+  size_t nbytes = 1;
   ssize_t bytes_written;
 
   fprintf(stderr, "Disconnecting M8\n");
 
-  strcpy(buf, "D");
-  nbytes = strlen(buf);
   bytes_written = write(port, buf, nbytes);
   if (bytes_written != nbytes) {
     fprintf(stderr,
@@ -52,7 +50,7 @@
   return 1;
 }
 
-int send_input(int port, uint8_t input) {
+int send_msg_controller(int port, uint8_t input) {
   char buf[2] = {'C',input};
   size_t nbytes = 2;
   ssize_t bytes_written;
@@ -59,7 +57,28 @@
   bytes_written = write(port, buf, nbytes);
   if (bytes_written != nbytes) {
     fprintf(stderr,
-            "Error sending input, expected to write %zu bytes, %zd written\n",
+            "Error sending controller message, expected to write %zu bytes, %zd written\n",
+            nbytes, bytes_written);
+
+    if (bytes_written == -1) {
+      fprintf(stderr, "Error code %d: %s\n", errno, strerror(errno));
+    }
+    return -1;
+  }
+  return 1;
+
+}
+
+int send_msg_keyjazz(int port, uint8_t note, uint8_t velocity) {
+  if (velocity > 64)
+    velocity = 64;
+  char buf[3] = {'K',note,velocity};
+  size_t nbytes = 3;
+  ssize_t bytes_written;
+  bytes_written = write(port, buf, nbytes);
+  if (bytes_written != nbytes) {
+    fprintf(stderr,
+            "Error sending keyjazz message, expected to write %zu bytes, %zd written\n",
             nbytes, bytes_written);
 
     if (bytes_written == -1) {
--- a/write.h
+++ b/write.h
@@ -4,9 +4,8 @@
 #include <stdint.h>
 int enable_and_reset_display(int port);
 int disconnect(int port);
-int send_input(int port, uint8_t input);
-
-void draw_rectangle(struct draw_rectangle_command *command);
+int send_msg_controller(int port, uint8_t input);
+int send_msg_keyjazz(int port, uint8_t note, uint8_t velocity);
 
 
 #endif
\ No newline at end of file
--