shithub: m8c

Download patch

ref: 6e40acac61543546ff0bab95dc7389fcf5b2f3b1
parent: 77d211a56fd2689b5927e1aaa9f316d2d7848f4c
author: Jonne Kokkonen <jonne.kokkonen@gmail.com>
date: Mon Mar 17 10:11:47 EDT 2025

add initial rtmidi things

--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,23 +2,33 @@
 
 project(m8c LANGUAGES C)
 
-set(CMAKE_C_FLAGS "-O2 -Wall -Wextra")
+set(CMAKE_C_FLAGS "-g -Wall -Wextra")
 
 set(APP_NAME m8c)
 
+set(USE_RTMIDI 1)
+
 find_package(PkgConfig REQUIRED)
 find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
 
 pkg_check_modules(SDL3 REQUIRED sdl3)
+if (USE_LIBSERIALPORT)
+    pkg_check_modules(LIBSERIALPORT REQUIRED libserialport)
+    link_directories(${SDL3_LIBRARY_DIRS} ${LIBSERIALPORT_LIBRARY_DIRS})
+    add_compile_definitions(USE_LIBSERIALPORT)
+endif (USE_LIBSERIALPORT)
 if (USE_LIBUSB)
     pkg_check_modules(LIBUSB REQUIRED libusb-1.0)
     link_directories(${SDL3_LIBRARY_DIRS} ${LIBUSB_LIBRARY_DIRS})
     add_compile_definitions(USE_LIBUSB)
-else ()
-    pkg_check_modules(LIBSERIALPORT REQUIRED libserialport)
-    link_directories(${SDL3_LIBRARY_DIRS} ${LIBSERIALPORT_LIBRARY_DIRS})
 endif (USE_LIBUSB)
+if (USE_RTMIDI)
+    pkg_check_modules(RTMIDI REQUIRED rtmidi)
+    link_directories(${SDL3_LIBRARY_DIRS} ${RTMIDI_LIBRARY_DIRS})
+    add_compile_definitions(USE_RTMIDI)
+endif (USE_RTMIDI)
 
+
 file(GLOB m8c_SRC "src/*.h" "src/*.c")
 
 set(MACOS_CONTENTS "${CMAKE_CURRENT_SOURCE_DIR}/package/macos/m8c.app/Contents")
@@ -29,22 +39,30 @@
 
 add_executable(${APP_NAME} WIN32 MACOSX_BUNDLE ${APP_ICON} ${m8c_SRC})
 
+if (USE_LIBSERIALPORT)
+    target_link_libraries(${APP_NAME} ${SDL3_LIBRARIES} ${LIBSERIALPORT_LIBRARIES})
+    target_include_directories(${APP_NAME} PUBLIC ${SDL3_INCLUDE_DIRS} ${LIBSERIALPORT_INCLUDE_DIRS})
+    target_compile_options(${APP_NAME} PUBLIC ${SDL3_CFLAGS_OTHER} ${LIBSERIALPORT_CFLAGS_OTHER})
+endif ()
+
 if (USE_LIBUSB)
     target_link_libraries(${APP_NAME} ${SDL3_LIBRARIES} ${LIBUSB_LIBRARIES})
     target_include_directories(${APP_NAME} PUBLIC ${SDL3_INCLUDE_DIRS} ${LIBUSB_INCLUDE_DIRS})
     target_compile_options(${APP_NAME} PUBLIC ${SDL3_CFLAGS_OTHER} ${LIBUSB_CFLAGS_OTHER})
-else ()
-    target_link_libraries(${APP_NAME} ${SDL3_LIBRARIES} ${LIBSERIALPORT_LIBRARIES})
-    target_include_directories(${APP_NAME} PUBLIC ${SDL3_INCLUDE_DIRS} ${LIBSERIALPORT_INCLUDE_DIRS})
-    target_compile_options(${APP_NAME} PUBLIC ${SDL3_CFLAGS_OTHER} ${LIBSERIALPORT_CFLAGS_OTHER})
 endif ()
 
+if (USE_RTMIDI)
+    target_link_libraries(${APP_NAME} ${SDL3_LIBRARIES} ${RTMIDI_LIBRARIES})
+    target_include_directories(${APP_NAME} PUBLIC ${SDL3_INCLUDE_DIRS} ${RTMIDI_INCLUDE_DIRS})
+    target_compile_options(${APP_NAME} PUBLIC ${SDL3_CFLAGS_OTHER} ${RTMIDI_CFLAGS_OTHER})
+endif ()
+
 if (APPLE)
     # Destination paths below are relative to ${CMAKE_INSTALL_PREFIX}
     install(TARGETS ${APP_NAME}
             BUNDLE DESTINATION . COMPONENT Runtime
             RUNTIME DESTINATION bin COMPONENT Runtime
-            )
+    )
 
     set_target_properties(${APP_NAME} PROPERTIES
             MACOSX_BUNDLE TRUE
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
 #Set all your object files (the object files of all the .c files in your project, e.g. main.o my_sub_functions.o )
-OBJ = src/main.o src/serial.o src/slip.o src/command.o src/render.o src/ini.o src/config.o src/input.o src/gamecontrollers.o src/fx_cube.o src/usb.o src/audio.o src/usb_audio.o src/ringbuffer.o src/inprint2.o
+OBJ = src/main.o src/serial.o src/slip.o src/command.o src/render.o src/ini.o src/config.o src/input.o src/gamecontrollers.o src/fx_cube.o src/usb.o src/audio.o src/usb_audio.o src/ringbuffer.o src/inprint2.o src/midi.o
 
 #Set any dependant header files so that if they are edited they cause a complete re-compile (e.g. main.h some_subfunctions.h some_definitions_file.h ), or leave blank
-DEPS = src/serial.h src/slip.h src/command.h src/render.h src/ini.h src/config.h src/input.h src/gamecontrollers.h src/fx_cube.h src/audio.h src/ringbuffer.h src/inline_font.h
+DEPS = src/serial.h src/slip.h src/command.h src/render.h src/ini.h src/config.h src/input.h src/gamecontrollers.h src/fx_cube.h src/audio.h src/ringbuffer.h src/inline_font.h src/midi.h
 
 #Any special libraries you are using in your project (e.g. -lbcm2835 -lrt `pkg-config --libs gtk+-3.0` ), or leave blank
 INCLUDES = $(shell pkg-config --libs sdl3 libserialport | sed 's/-mwindows//')
@@ -9,7 +9,6 @@
 
 #Set any compiler flags you want to use (e.g. -I/usr/include/somefolder `pkg-config --cflags gtk+-3.0` ), or leave blank
 local_CFLAGS = $(CFLAGS) $(shell pkg-config --cflags sdl3 libserialport) -DUSE_LIBSERIALPORT -Wall -Wextra -O2 -pipe -I.
-# local_CFLAGS = $(CFLAGS) $(shell pkg-config --cflags sdl3 libserialport) -DUSE_RTMIDI -Wall -Wextra -O2 -pipe -I.
 
 
 #Set the compiler you are using ( gcc for C or g++ for C++ )
@@ -30,8 +29,12 @@
 	$(CC) -o $@ $^ $(local_CFLAGS) $(INCLUDES)
 
 libusb: INCLUDES = $(shell pkg-config --libs sdl3 libusb-1.0)
-libusb: local_CFLAGS = $(CFLAGS) $(shell pkg-config --cflags sdl3 libusb-1.0) -Wall -O2 -pipe -I. -DUSE_LIBUSB=1
+libusb: local_CFLAGS = $(CFLAGS) $(shell pkg-config --cflags sdl3 libusb-1.0) -Wall -Wextra -O2 -pipe -I. -DUSE_LIBUSB=1
 libusb: m8c
+
+rtmidi: INCLUDES = $(shell pkg-config --libs sdl3 rtmidi)
+rtmidi: local_CFLAGS = $(CFLAGS) $(shell pkg-config --cflags sdl3 rtmidi) -Wall -Wextra -O2 -pipe -I. -DUSE_RTMIDI
+rtmidi: m8c
 
 #Cleanup
 .PHONY: clean
--- a/src/main.c
+++ b/src/main.c
@@ -14,9 +14,9 @@
 #include "config.h"
 #include "gamecontrollers.h"
 #include "input.h"
+#include "midi.h"
 #include "render.h"
 #include "serial.h"
-#include "slip.h"
 
 enum state { QUIT, WAIT_FOR_DEVICE, RUN };
 
@@ -77,12 +77,13 @@
 
 int main(const int argc, char *argv[]) {
 
+  char *preferred_device = NULL;
+
+#ifdef USE_LIBSERIALPORT // Device selection should be only available with libserialport
   if (argc == 2 && SDL_strcmp(argv[1], "--list") == 0) {
     return list_devices();
   }
 
-  char *preferred_device = NULL;
-#ifdef USE_LIBSERIALPORT // Device selection should be only available with libserialport
   if (argc == 3 && SDL_strcmp(argv[1], "--dev") == 0) {
     preferred_device = argv[2];
     SDL_Log("Using preferred device %s.\n", preferred_device);
@@ -220,7 +221,6 @@
     // main loop
     while (run == RUN) {
       process_inputs(conf);
-
       const int result = process_serial(conf);
       if (result == 0) {
         port_inited = 0;
@@ -230,7 +230,6 @@
         run = QUIT;
       }
       render_screen();
-      SDL_Delay(conf.idle_ms);
     }
   } while (run > QUIT);
   // main loop end
--- /dev/null
+++ b/src/midi.c
@@ -1,0 +1,249 @@
+// Copyright 2021 Jonne Kokkonen
+// Released under the MIT licence, https://opensource.org/licenses/MIT
+#ifdef USE_RTMIDI
+
+#include "midi.h"
+#include "command.h"
+#include "config.h"
+#include <SDL3/SDL.h>
+#include <rtmidi_c.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+RtMidiInPtr midi_in;
+RtMidiOutPtr midi_out;
+
+const unsigned char m8_sysex_header[4] = {0xF0, 0x00, 0x02, 0x61};
+const unsigned int m8_sysex_header_size = sizeof(m8_sysex_header);
+const unsigned char sysex_message_end = 0xF7;
+
+bool message_is_m8_sysex(const unsigned char *message) {
+  if (memcmp(m8_sysex_header, message, m8_sysex_header_size) == 0) {
+    return true;
+  }
+  return false;
+}
+
+void midi_decode(const uint8_t *encoded_data, size_t length, uint8_t **decoded_data,
+                 size_t *decoded_length) {
+  if (length < m8_sysex_header_size) {
+    // Invalid data
+    *decoded_data = NULL;
+    *decoded_length = 0;
+    return;
+  }
+
+  // Skip header "F0 00 02 61" and the first MSB byte
+  size_t pos = m8_sysex_header_size + 1;
+
+  // Calculate expected output size (ignoring EOT if present)
+  size_t expected_output_size =
+      (length - m8_sysex_header_size) - ((length - m8_sysex_header_size) / 8);
+  if (encoded_data[length - 1] == sysex_message_end) {
+    length--; // Ignore the EOT byte
+  }
+
+  // Allocate memory for decoded output
+  *decoded_data = (uint8_t *)SDL_malloc(expected_output_size);
+  if (*decoded_data == NULL) {
+    *decoded_length = 0;
+    return;
+  }
+
+  uint8_t bitCounter = 0;
+  uint8_t bitByteCounter = 0;
+  uint8_t *out = *decoded_data;
+  *decoded_length = 0;
+
+  while (pos < length) {
+    // Extract MSB from the "bit field" position
+    uint8_t msb = (encoded_data[bitByteCounter * 8 + m8_sysex_header_size] >> bitCounter) & 0x01;
+
+    // Extract LSB from data byte
+    uint8_t lsb = encoded_data[pos] & 0x7F;
+
+    // Reconstruct original byte, skipping the MSB bytes in output
+    *out = (msb << 7) | lsb;
+    out++;
+    (*decoded_length)++;
+
+    bitCounter++;
+    pos++;
+
+    if (bitCounter == 7) {
+      bitCounter = 0;
+      bitByteCounter++;
+      pos++; // Skip the MSB byte
+    }
+  }
+}
+
+static void midi_callback(double delta_time, const unsigned char *message, size_t message_size,
+                          void *user_data) {
+  if (message_size < 5 || !message_is_m8_sysex(message))
+    return;
+
+  uint8_t *decoded_data;
+  size_t decoded_length;
+  midi_decode(message, message_size, &decoded_data, &decoded_length);
+
+  // printf("Original data: ");
+  for (size_t i = 0; i < message_size; i++) {
+    // printf("%02X ", message[i]);
+  }
+
+  if (decoded_data) {
+    // printf("\nDecoded MIDI Data: ");
+    for (size_t i = 0; i < decoded_length; i++) {
+      // printf("%02X ", decoded_data[i]);
+    }
+    // printf("\n");
+    process_command(decoded_data, decoded_length);
+    SDL_free(decoded_data);
+  } else {
+    printf("Decoding failed.\n");
+  }
+}
+
+int initialize_rtmidi(void) {
+  SDL_Log("init rtmidi");
+  midi_in = rtmidi_in_create(RTMIDI_API_UNSPECIFIED, "m8c_in", 1024);
+  midi_out = rtmidi_out_create(RTMIDI_API_UNSPECIFIED, "m8c_out");
+  if (midi_in == NULL || midi_out == NULL) {
+    return 0;
+  }
+  return 1;
+}
+
+int init_serial(int verbose, const char *preferred_device) {
+
+  int midi_in_initialized = 0;
+  int midi_out_initialized = 0;
+
+  SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "init_serial called");
+  if (midi_in == NULL || midi_out == NULL) {
+    initialize_rtmidi();
+  };
+  unsigned int ports_total_in = rtmidi_get_port_count(midi_in);
+  if (verbose)
+    SDL_Log("Number of MIDI in ports: %d", ports_total_in);
+  for (unsigned int port_number = 0; port_number < ports_total_in; port_number++) {
+    int port_name_length_in;
+    rtmidi_get_port_name(midi_in, port_number, NULL, &port_name_length_in);
+    char port_name[port_name_length_in];
+    rtmidi_get_port_name(midi_in, port_number, &port_name[0], &port_name_length_in);
+    if (verbose)
+      SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "MIDI IN port %d, name: %s", port_number, port_name);
+    if (strncmp("M8", port_name, 2) == 0) {
+      if (verbose)
+        SDL_Log("Found M8 Input in MIDI port %d", port_number);
+
+      rtmidi_in_ignore_types(midi_in, false, true, true);
+      rtmidi_open_port(midi_in, port_number, "M8");
+      midi_in_initialized = 1;
+      rtmidi_open_port(midi_out, port_number, "M8");
+      midi_out_initialized = 1;
+    }
+  }
+  return (midi_in_initialized && midi_out_initialized);
+}
+
+int check_serial_port(void) {
+  SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "check_serial_port called");
+  return 0;
+}
+
+int reset_display(void) {
+  SDL_Log("Reset display\n");
+  const unsigned char reset_sysex[8] = {0xF0, 0x00, 0x02, 0x61, 0x00, 0x00, 'R', 0xF7};
+  int result = rtmidi_out_send_message(midi_out, &reset_sysex[0], sizeof(reset_sysex));
+  if (result != 0) {
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error resetting M8 display, code %d", result);
+    return 0;
+  }
+  return 1;
+}
+
+int enable_and_reset_display(void) {
+  //rtmidi_in_set_callback(midi_in, midi_callback, NULL);
+  const unsigned char enable_sysex[8] = {0xF0, 0x00, 0x02, 0x61, 0x00, 0x00, 'E', 0xF7};
+  int result = rtmidi_out_send_message(midi_out, &enable_sysex[0], sizeof(enable_sysex));
+  if (result != 0) {
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Failed to enable display");
+    return 0;
+  }
+  result = reset_display();
+  return result;
+}
+
+int disconnect(void) {
+  const unsigned char disconnect_sysex[8] = {0xF0, 0x00, 0x02, 0x61, 0x00, 0x00, 'D', 0xF7};
+  int result = rtmidi_out_send_message(midi_out, &disconnect_sysex[0], sizeof(disconnect_sysex));
+  if (result != 0) {
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Failed to send disconnect");
+  }
+  SDL_Log("Freeing MIDI ports");
+  rtmidi_in_free(midi_in);
+  rtmidi_out_free(midi_out);
+  return !result;
+}
+
+int send_msg_controller(const unsigned char input) {
+  const unsigned char input_sysex[9] = {0xF0, 0x00, 0x02, 0x61, 0x00, 0x00, 'C', input, 0xF7};
+  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");
+    return 0;
+  }
+  return 1;
+}
+
+int send_msg_keyjazz(const unsigned char note, unsigned char velocity) {
+  if (velocity > 0x7F) {
+    velocity = 0x7F;
+  }
+  const unsigned char keyjazz_sysex[10] = {0xF0, 0x00, 0x02, 0x61,     0x00,
+                                           0x00, 'K',  note, velocity, 0xF7};
+  int result = rtmidi_out_send_message(midi_out, &keyjazz_sysex[0], sizeof(keyjazz_sysex));
+  if (result != 0) {
+    SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Failed to send keyjazz input message");
+    return 0;
+  }
+  return 1;
+}
+
+int process_serial(config_params_s conf) {
+  unsigned char midi_message[1024];
+  size_t midi_message_size = 1024;
+  while (rtmidi_in_get_message(midi_in,midi_message,&midi_message_size)) {
+    if (midi_message_size < 5 || !message_is_m8_sysex(midi_message))
+      return 1;
+
+    uint8_t *decoded_data;
+    size_t decoded_length;
+    midi_decode(midi_message, midi_message_size, &decoded_data, &decoded_length);
+
+    // printf("Original data: ");
+    for (size_t i = 0; i < midi_message_size; i++) {
+      // printf("%02X ", message[i]);
+    }
+
+    if (decoded_data) {
+      // printf("\nDecoded MIDI Data: ");
+      for (size_t i = 0; i < decoded_length; i++) {
+        // printf("%02X ", decoded_data[i]);
+      }
+      // printf("\n");
+      process_command(decoded_data, decoded_length);
+      SDL_free(decoded_data);
+    }
+    printf("Decoding failed.\n");
+    return 0;
+  }
+  return 1;
+}
+
+int destroy_serial() { return disconnect(); }
+
+#endif
\ No newline at end of file
--- /dev/null
+++ b/src/midi.h
@@ -1,0 +1,23 @@
+// Copyright 2021 Jonne Kokkonen
+// Released under the MIT licence, https://opensource.org/licenses/MIT
+#ifndef _MIDI_H_
+#define _MIDI_H_
+#ifdef USE_RTMIDI
+
+#include "config.h"
+#include <stdint.h>
+
+int initialize_rtmidi(void);
+int init_serial(int verbose, const char *preferred_device);
+int list_devices(void);
+int check_serial_port(void);
+int reset_display(void);
+int enable_and_reset_display(void);
+int disconnect(void);
+int send_msg_controller(const unsigned char input);
+int send_msg_keyjazz(const unsigned char note, unsigned char velocity);
+int process_serial(config_params_s conf);
+int destroy_serial();
+
+#endif
+#endif
\ No newline at end of file
--- a/src/render.c
+++ b/src/render.c
@@ -76,7 +76,7 @@
 
   set_font_mode(0);
 
-  SDL_SetLogPriorities(SDL_LOG_PRIORITY_INFO);
+  SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG);
 
   dirty = 1;
 
--