├── macos └── m8c.app │ └── Contents │ ├── PkgInfo │ ├── Resources │ └── m8c.icns │ └── Info.plist ├── m8c-nojack.sh ├── .gitignore ├── shell.nix ├── fx_cube.h ├── es_systems.cfg.m8c.paste.txt ├── serial.h ├── write.h ├── ini.h ├── m8c-custom.sh.example ├── midi-connect.sh ├── SDL2_inprint.h ├── render.h ├── list-sdl-controllers.py ├── input.h ├── command.h ├── default.nix ├── m8c-choose.sh ├── config.h ├── m8c.sh ├── config.ini.sample ├── slip.h ├── write.c ├── Makefile ├── LICENSE ├── slip.c ├── fx_cube.c ├── command.c ├── inprint2.c ├── .github └── workflows │ └── build.yml ├── serial.c ├── AUDIOGUIDE.md ├── inline_font.h.comicsans ├── ini.c ├── inline_font.h ├── render.c ├── main.c ├── config.c ├── input.c └── README.md /macos/m8c.app/Contents/PkgInfo: -------------------------------------------------------------------------------- 1 | APPL???? 2 | -------------------------------------------------------------------------------- /m8c-nojack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # start m8 client 4 | pushd /home/pi/code/m8c-piboy 5 | ./m8c 6 | popd 7 | -------------------------------------------------------------------------------- /macos/m8c.app/Contents/Resources/m8c.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rasprague/m8c-piboy/HEAD/macos/m8c.app/Contents/Resources/m8c.icns -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dylib 2 | *.a 3 | *.o 4 | *.exe 5 | .DS_Store 6 | m8c 7 | .vscode 8 | .cache/ 9 | font.c 10 | build/ 11 | compile_commands.json 12 | .clangd 13 | result* 14 | *~ 15 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: 2 | 3 | with pkgs; 4 | 5 | mkShell { 6 | packages = with pkgs; [ nix-prefetch-github ]; 7 | inputsFrom = [ (import ./default.nix {}).m8c-dev ]; 8 | } 9 | -------------------------------------------------------------------------------- /fx_cube.h: -------------------------------------------------------------------------------- 1 | #ifndef FX_CUBE_H_ 2 | #define FX_CUBE_H_ 3 | 4 | #include "SDL_render.h" 5 | void fx_cube_init(SDL_Renderer *target_renderer, SDL_Color foreground_color); 6 | void fx_cube_destroy(); 7 | void fx_cube_update(); 8 | #endif -------------------------------------------------------------------------------- /es_systems.cfg.m8c.paste.txt: -------------------------------------------------------------------------------- 1 | 2 | m8 3 | M8 Tracker 4 | /home/pi/RetroPie/roms/m8 5 | .sh 6 | %ROM% 7 | ignore 8 | m8 9 | 10 | -------------------------------------------------------------------------------- /serial.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Jonne Kokkonen 2 | // Released under the MIT licence, https://opensource.org/licenses/MIT 3 | 4 | #ifndef _SERIAL_H_ 5 | #define _SERIAL_H_ 6 | 7 | #include 8 | 9 | struct sp_port *init_serial(int verbose); 10 | int check_serial_port(struct sp_port *m8_port); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /write.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Jonne Kokkonen 2 | // Released under the MIT licence, https://opensource.org/licenses/MIT 3 | 4 | #ifndef WRITE_H_ 5 | #define WRITE_H_ 6 | 7 | #include 8 | #include 9 | 10 | int reset_display(struct sp_port *port); 11 | int enable_and_reset_display(struct sp_port *port); 12 | int disconnect(struct sp_port *port); 13 | int send_msg_controller(struct sp_port *port, uint8_t input); 14 | int send_msg_keyjazz(struct sp_port *port, uint8_t note, uint8_t velocity); 15 | 16 | #endif -------------------------------------------------------------------------------- /ini.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 rxi 3 | * 4 | * This library is free software; you can redistribute it and/or modify it 5 | * under the terms of the MIT license. See `ini.c` for details. 6 | */ 7 | 8 | #ifndef INI_H 9 | #define INI_H 10 | 11 | #define INI_VERSION "0.1.1" 12 | 13 | typedef struct ini_t ini_t; 14 | 15 | ini_t* ini_load(const char *filename); 16 | void ini_free(ini_t *ini); 17 | const char* ini_get(ini_t *ini, const char *section, const char *key); 18 | int ini_sget(ini_t *ini, const char *section, const char *key, const char *scanfmt, void *dst); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /m8c-custom.sh.example: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set this to the audio interface device number or name 4 | # you wish to use with m8-headless 5 | # see output from command aplay -l 6 | HWAUDIODEVICE=1 7 | 8 | # set this to the sample rate 9 | RATE=44100 10 | 11 | # set to 0 to disable audio input, 1 to enable audio input 12 | ENABLEINPUT=0 13 | 14 | ############################################################################### 15 | OPTIONS="" 16 | 17 | if [ ENABLEAUDIOINPUT = 1 ]; then 18 | OPTIONS+=" --enable-input" 19 | fi 20 | 21 | pushd /home/pi/code/m8c-piboy 22 | ./m8c.sh --interface $HWAUDIODEVICE --rate $RATE $OPTIONS 23 | popd 24 | -------------------------------------------------------------------------------- /midi-connect.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # requires naconnect installed at ~/code/naconnect/naconnect 4 | # $ sudo apt install libncurses-dev 5 | # $ mkdir -p ~/code && cd ~/code 6 | # $ git clone https://github.com/dctucker/naconnect.git && cd naconnect 7 | # $ make 8 | 9 | # button-to-keyboard mappings: 10 | # * cursor keys for axis/dpad 11 | # * c (0x63),d (0x64),q (0x71),TAB (0x09) for buttons 'a','b','x','y' 12 | # * r (0x72),r for buttons lt,rt (shoulder buttons) 13 | # l r u d a b x y lt rt 14 | params=(0x09 0x09 kcuu1 kcud1 0x63 0x64 0x71 0x09 0x72 0x72) 15 | 16 | /opt/retropie/admin/joy2key/joy2key start "${params[@]}" 17 | ~/code/naconnect/naconnect 18 | echo "Quitting . . ." 19 | /opt/retropie/admin/joy2key/joy2key stop 20 | echo "Done." 21 | clear 22 | -------------------------------------------------------------------------------- /SDL2_inprint.h: -------------------------------------------------------------------------------- 1 | // Bitmap font routine by driedfruit, https://github.com/driedfruit/SDL_inprint 2 | // Released into public domain. 3 | // Modified to support adding a background to text. 4 | 5 | #ifndef SDL2_inprint_h 6 | #define SDL2_inprint_h 7 | 8 | #include 9 | 10 | extern void prepare_inline_font(void); 11 | extern void kill_inline_font(void); 12 | 13 | extern void inrenderer(SDL_Renderer *renderer); 14 | extern void infont(SDL_Texture *font); 15 | extern void incolor1(SDL_Color *color); 16 | extern void incolor(Uint32 color, Uint32 unused); /* Color must be in 0x00RRGGBB format ! */ 17 | extern void inprint(SDL_Renderer *dst, const char *str, Uint32 x, Uint32 y, 18 | Uint32 fgcolor, Uint32 bgcolor); 19 | 20 | extern SDL_Texture *get_inline_font(void); 21 | 22 | #endif /* SDL2_inprint_h */ 23 | -------------------------------------------------------------------------------- /render.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Jonne Kokkonen 2 | // Released under the MIT licence, https://opensource.org/licenses/MIT 3 | 4 | #ifndef RENDER_H_ 5 | #define RENDER_H_ 6 | 7 | #include "command.h" 8 | 9 | int initialize_sdl(int init_fullscreen, int init_use_gpu); 10 | void close_renderer(); 11 | 12 | int process_queues(struct command_queues *queues); 13 | void draw_waveform(struct draw_oscilloscope_waveform_command *command); 14 | void draw_rectangle(struct draw_rectangle_command *command); 15 | int draw_character(struct draw_character_command *command); 16 | 17 | void render_screen(); 18 | void toggle_fullscreen(); 19 | void display_keyjazz_overlay(uint8_t show, uint8_t base_octave, uint8_t velocity); 20 | 21 | void screensaver_init(); 22 | void screensaver_draw(); 23 | void screensaver_destroy(); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /list-sdl-controllers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Lists controller recognized by SDL 4 | 5 | # Dependency: pygame_sdl2 6 | # on RetroPie, install with 7 | # $ sudo apt install python-pygame-sdl2 8 | 9 | import pygame_sdl2 as pygame 10 | 11 | pygame.controller.init() 12 | pygame.joystick.init() 13 | for i in range(pygame.controller.get_count()): 14 | c = pygame.controller.Controller(i) 15 | j = pygame.joystick.Joystick(i) 16 | j.init() 17 | 18 | print "Controller %s" % i 19 | print "Name: %s" % j.get_name() 20 | print "GUID: %s" % c.get_guid_string() 21 | print "Num Axes: %s" % j.get_numaxes() 22 | print "Num Hats: %s" % j.get_numhats() 23 | print "Num Buttons: %s" % j.get_numbuttons() 24 | print "" 25 | c.quit() 26 | j.quit() 27 | 28 | pygame.controller.quit() 29 | pygame.joystick.quit() 30 | -------------------------------------------------------------------------------- /input.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Jonne Kokkonen 2 | // Released under the MIT licence, https://opensource.org/licenses/MIT 3 | 4 | #ifndef INPUT_H_ 5 | #define INPUT_H_ 6 | 7 | #include 8 | 9 | typedef enum input_buttons_t { 10 | INPUT_UP, 11 | INPUT_DOWN, 12 | INPUT_LEFT, 13 | INPUT_RIGHT, 14 | INPUT_OPT, 15 | INPUT_EDIT, 16 | INPUT_SELECT, 17 | INPUT_START, 18 | INPUT_MAX 19 | } input_buttons_t; 20 | 21 | typedef enum input_type_t { 22 | normal, 23 | keyjazz, 24 | special 25 | } input_type_t; 26 | 27 | typedef enum special_messages_t { 28 | msg_quit = 1, 29 | msg_reset_display = 2 30 | } special_messages_t; 31 | 32 | typedef struct input_msg_s { 33 | input_type_t type; 34 | uint8_t value; 35 | uint8_t value2; 36 | uint32_t eventType; 37 | } input_msg_s; 38 | 39 | int initialize_game_controllers(); 40 | void close_game_controllers(); 41 | input_msg_s get_input_msg(); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /command.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Jonne Kokkonen 2 | // Released under the MIT licence, https://opensource.org/licenses/MIT 3 | 4 | #ifndef COMMAND_H_ 5 | #define COMMAND_H_ 6 | 7 | #include 8 | 9 | struct position { 10 | uint16_t x; 11 | uint16_t y; 12 | }; 13 | 14 | struct size { 15 | uint16_t width; 16 | uint16_t height; 17 | }; 18 | 19 | struct color { 20 | uint8_t r; 21 | uint8_t g; 22 | uint8_t b; 23 | }; 24 | 25 | struct draw_rectangle_command { 26 | struct position pos; 27 | struct size size; 28 | struct color color; 29 | }; 30 | 31 | struct draw_character_command { 32 | int c; 33 | struct position pos; 34 | struct color foreground; 35 | struct color background; 36 | }; 37 | 38 | struct draw_oscilloscope_waveform_command { 39 | struct color color; 40 | uint8_t waveform[320]; 41 | uint16_t waveform_size; 42 | }; 43 | 44 | struct command_queues { 45 | struct draw_rectangle_command rectangles[128]; 46 | uint8_t rectangles_queue_size; 47 | struct draw_character_command characters[128]; 48 | uint8_t characters_queue_size; 49 | struct draw_oscilloscope_waveform_command waveform; 50 | uint8_t waveforms_queue_size; 51 | }; 52 | 53 | 54 | int process_command(uint8_t *data, uint32_t size); 55 | 56 | #endif -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: 2 | 3 | with pkgs; 4 | 5 | # HOWTO keep this package definition up-to-date: 6 | # 7 | # 1. NEW VERSION: 8 | # After the new version is tagged and pushed, update the `version` var to the 9 | # proper value and update the hash. You can use the following command to find 10 | # out the sha256, for example, with version 1.0.3: 11 | # `nix-prefetch-github --rev v1.0.3 laamaa m8c` 12 | # 13 | # 2. NEW DEPENDENCIES: 14 | # Make sure new dependencies are added. Runtime deps go to buildInputs and 15 | # compile-time deps go to nativeBuildInputs. Use the nixpkgs manual for help 16 | # 17 | let m8c-package = 18 | { stdenv 19 | , gnumake 20 | , SDL2 21 | , libserialport 22 | , fetchFromGitHub 23 | }: 24 | 25 | let 26 | pname = "m8c"; 27 | version = "1.0.3"; 28 | in 29 | stdenv.mkDerivation { 30 | inherit pname version; 31 | 32 | src = fetchFromGitHub { 33 | owner = "laamaa"; 34 | repo = pname; 35 | rev = "v${version}"; 36 | hash = "sha256:0yrd6lnb2chgafhw1cz4awx2s1sws6mch5irvgyddgnsa8ishcr5"; 37 | }; 38 | 39 | installFlags = [ "PREFIX=$(out)" ]; 40 | nativeBuildInputs = [ gnumake ]; 41 | buildInputs = [ SDL2 libserialport ]; 42 | }; 43 | in { 44 | m8c-stable = pkgs.callPackage m8c-package {}; 45 | m8c-dev = (pkgs.callPackage m8c-package {}).overrideAttrs (oldAttrs: {src = ./.;}); 46 | } 47 | -------------------------------------------------------------------------------- /m8c-choose.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function joy2key_start { 4 | echo "Starting joy2key . . ." 5 | /opt/retropie/admin/joy2key/joy2key start 6 | } 7 | 8 | function joy2key_stop { 9 | echo "Stopping joy2key . . ." 10 | /opt/retropie/admin/joy2key/joy2key stop 11 | } 12 | 13 | trap joy2key_stop EXIT 14 | 15 | CARDCMD="dialog --stdout --title \"m8-headless tracker client\" --menu \"Choose an Audio Interface\" 20 80 20" 16 | RATECMD="dialog --stdout --title \"m8-headless tracker client\" --menu \"Choose a Sample Rate\" 10 30 20 0 44.1kHz 1 48kHz" 17 | AUDIOINCMD="dialog --stdout --title \"m8-headless tracker client\" --defaultno --yesno \"Enable Audio Input?\" 6 25" 18 | 19 | CARDLIST="" 20 | OPTIONS="" 21 | I=0 22 | 23 | OUTPUT=$(aplay -l | grep ^card) 24 | while read line; do 25 | CARDLIST="$CARDLIST $I \"$line\"" 26 | ((I=I+1)) 27 | done <<< "$OUTPUT" 28 | 29 | joy2key_start 30 | CARD=$(eval $CARDCMD $CARDLIST) 31 | [ $? != 0 ] && echo "Cancelled." && exit 1 32 | 33 | RATE=$(eval $RATECMD) 34 | [ $? != 0 ] && echo "Cancelled." && exit 1 35 | if [ $RATE = 0 ]; then 36 | RATE=44100 37 | elif [ $RATE = 1 ]; then 38 | RATE=48000 39 | fi 40 | 41 | eval $AUDIOINCMD 42 | case $? in 43 | # yes 44 | 0) OPTIONS+=" --enable-input" ;; 45 | # no 46 | 1) ;; 47 | # [ESC] 48 | 255) echo "Cancelled." ; exit 1 ;; 49 | esac 50 | joy2key_stop 51 | 52 | pushd /home/pi/code/m8c-piboy 53 | echo ./m8c.sh --interface $CARD --rate $RATE $OPTIONS 54 | ./m8c.sh --interface $CARD --rate $RATE $OPTIONS 55 | popd 56 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Jonne Kokkonen 2 | // Released under the MIT licence, https://opensource.org/licenses/MIT 3 | 4 | #ifndef CONFIG_H_ 5 | #define CONFIG_H_ 6 | 7 | #include "ini.h" 8 | 9 | typedef struct config_params_s { 10 | char *filename; 11 | int init_fullscreen; 12 | int init_use_gpu; 13 | int init_disable_cursor; 14 | int idle_ms; 15 | int wait_for_device; 16 | int wait_packets; 17 | 18 | int key_up; 19 | int key_left; 20 | int key_down; 21 | int key_right; 22 | int key_select; 23 | int key_select_alt; 24 | int key_start; 25 | int key_start_alt; 26 | int key_opt; 27 | int key_opt_alt; 28 | int key_edit; 29 | int key_edit_alt; 30 | int key_delete; 31 | int key_reset; 32 | 33 | int gamepad_up; 34 | int gamepad_left; 35 | int gamepad_down; 36 | int gamepad_right; 37 | int gamepad_select; 38 | int gamepad_start; 39 | int gamepad_opt; 40 | int gamepad_edit; 41 | int gamepad_quit; 42 | int gamepad_reset; 43 | 44 | int gamepad_analog_threshold; 45 | int gamepad_analog_invert; 46 | int gamepad_analog_axis_updown; 47 | int gamepad_analog_axis_leftright; 48 | int gamepad_analog_axis_start; 49 | int gamepad_analog_axis_select; 50 | int gamepad_analog_axis_opt; 51 | int gamepad_analog_axis_edit; 52 | 53 | } config_params_s; 54 | 55 | 56 | config_params_s init_config(); 57 | void read_config(); 58 | void read_graphics_config(ini_t *config, config_params_s *conf); 59 | void read_key_config(ini_t *config, config_params_s *conf); 60 | void read_gamepad_config(ini_t *config, config_params_s *conf); 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /m8c.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HWAUDIODEVICE=0 4 | ENABLEINPUT=0 5 | RATE=44100 6 | 7 | usage() 8 | { 9 | echo "Usage $0" 10 | echo " options:" 11 | echo " [ -i | --interface ] - use audio interface I, defaults to 0" 12 | echo " (see output from aplay -l command)" 13 | echo " [ -r | --rate ] - use R sample rate, defaults to 44100" 14 | echo " [ -e | --enable-input = enable audio input, defaults is off" 15 | echo " [ -h | --help ] show this helpful message" 16 | } 17 | 18 | OPTIONS=$(getopt -o i:r:eh --long interface:,rate:,enable-input,help -- "$@") 19 | if [ $? -ne 0 ]; then 20 | usage 21 | exit 1 22 | fi 23 | 24 | eval set -- "$OPTIONS" 25 | 26 | while true; do 27 | case "$1" in 28 | -i|--interface) 29 | HWAUDIODEVICE=$2 ; shift 2 ;; 30 | -r|--rate) 31 | RATE=$2 ; shift 2 ;; 32 | -e|--enable-input) 33 | ENABLEINPUT=1 ; shift ;; 34 | -h|--help) 35 | usage ; shift ; exit 0 ;; 36 | --) 37 | shift ; break ;; 38 | esac 39 | done 40 | 41 | # audio routing 42 | export JACK_NO_AUDIO_RESERVATION=1 43 | jackd -d alsa -d hw:M8 -r$RATE -p512 & 44 | sleep 1 45 | 46 | # setup output 47 | alsa_out -j m8out -d hw:$HWAUDIODEVICE -r $RATE & 48 | sleep 1 49 | jack_connect system:capture_1 m8out:playback_1 50 | jack_connect system:capture_2 m8out:playback_2 51 | 52 | # setup input 53 | if [ $ENABLEINPUT -eq 1 ]; then 54 | alsa_in -j m8in -d hw:$HWAUDIODEVICE -r $RATE & 55 | sleep 1 56 | jack_connect m8in:capture_1 system:playback_1 57 | jack_connect m8in:capture_2 system:playback_2 58 | fi 59 | 60 | # start m8 client 61 | pushd /home/pi/code/m8c-piboy 62 | ./m8c 63 | popd 64 | 65 | # clean up audio routing 66 | killall -s SIGINT jackd alsa_out alsa_in 67 | -------------------------------------------------------------------------------- /macos/m8c.app/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 17G11023 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleIconFile 10 | m8c 11 | CFBundleExecutable 12 | m8c 13 | CFBundleIdentifier 14 | com.laamaa.m8c 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | m8c 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 10B61 33 | DTPlatformVersion 34 | GM 35 | DTSDKBuild 36 | 18B71 37 | DTSDKName 38 | macosx10.14 39 | DTXcode 40 | 1010 41 | DTXcodeBuild 42 | 10B61 43 | LSApplicationCategoryType 44 | public.app-category.utilities 45 | LSMinimumSystemVersion 46 | 10.13 47 | NSHumanReadableCopyright 48 | Copyright © 2021 laamaa. All rights reserved. 49 | NSMainNibFile 50 | MainMenu 51 | NSPrincipalClass 52 | NSApplication 53 | 54 | 55 | -------------------------------------------------------------------------------- /config.ini.sample: -------------------------------------------------------------------------------- 1 | ; edit this file to change m8c defaults 2 | ; this file is re-written every time it is read, 3 | ; so do not expect comments or commented out values to survive! 4 | ; valid parameter changes will be written back and persisted though. 5 | 6 | [graphics] 7 | ; set this to true to have m8c start fullscreen 8 | fullscreen=false 9 | ; set this to false to run m8c in software rendering mode (may be useful for Raspberry Pi) 10 | use_gpu=true 11 | ; set this to true to disable the mouse cursor at startup 12 | diable_cursor=false 13 | ; the delay amount in ms in the main loop, decrease value for faster operation, increase value if too much cpu usage 14 | idle_ms = 10 15 | ; show a spinning cube if device is not inserted 16 | wait_for_device = true 17 | ; number of zero-byte attempts to disconnect if wait_for_device = false (128 = about 2 sec for default idle_ms) 18 | wait_packets = 128 19 | 20 | [keyboard] 21 | ; these need to be the decimal value of the SDL scancodes. 22 | ; a table exists here: https://github.com/libsdl-org/sdlwiki/blob/main/SDLScancodeLookup.mediawiki 23 | key_up=82 24 | key_left=80 25 | key_down=81 26 | key_right=79 27 | key_select=225 28 | key_select_alt=4 29 | key_start=44 30 | key_start_alt=22 31 | key_opt=226 32 | key_opt_alt=29 33 | key_edit=224 34 | key_edit_alt=27 35 | key_delete=76 36 | key_reset=21 37 | 38 | [gamepad] 39 | ; these need to be the decimal value of the SDL Controller buttons. 40 | ; a table exists here: https://wiki.libsdl.org/SDL_GameControllerButton 41 | gamepad_up=11 42 | gamepad_left=13 43 | gamepad_down=12 44 | gamepad_right=14 45 | gamepad_select=4 46 | gamepad_start=6 47 | gamepad_opt=1 48 | gamepad_edit=0 49 | gamepad_quit=8 50 | gamepad_reset=7 51 | 52 | gamepad_analog_threshold=32766 ;the threshold for analog sticks to trigger cursor movement (working values: 1-32766) 53 | gamepad_analog_invert=false ;NOT IMPLEMENTED YET: invert up/down and left/right axis (true/false) 54 | 55 | ; these need to be the decimal value of the controller axis 56 | ; you can use -1 if you do not wish to map the function to an analog axis 57 | gamepad_analog_axis_updown=1 58 | gamepad_analog_axis_leftright=0 59 | gamepad_analog_axis_start=5 60 | gamepad_analog_axis_select=4 61 | gamepad_analog_axis_opt=-1 62 | gamepad_analog_axis_edit=-1 63 | -------------------------------------------------------------------------------- /slip.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Marcin Borowicz 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* This code is originally by marcinbor85, https://github.com/marcinbor85/slip 26 | It has been simplified a bit as CRC checking etc. is not required in this 27 | program. */ 28 | 29 | #ifndef SLIP_H_ 30 | #define SLIP_H_ 31 | 32 | #include "command.h" 33 | #include 34 | 35 | #define SLIP_SPECIAL_BYTE_END 0xC0 36 | #define SLIP_SPECIAL_BYTE_ESC 0xDB 37 | 38 | #define SLIP_ESCAPED_BYTE_END 0xDC 39 | #define SLIP_ESCAPED_BYTE_ESC 0xDD 40 | 41 | typedef enum { 42 | SLIP_STATE_NORMAL = 0x00, 43 | SLIP_STATE_ESCAPED 44 | } slip_state_t; 45 | 46 | typedef struct { 47 | uint8_t *buf; 48 | uint32_t buf_size; 49 | int (*recv_message)(uint8_t *data, uint32_t size); 50 | } slip_descriptor_s; 51 | 52 | typedef struct { 53 | slip_state_t state; 54 | uint32_t size; 55 | const slip_descriptor_s *descriptor; 56 | } slip_handler_s; 57 | 58 | typedef enum { 59 | SLIP_NO_ERROR = 0x00, 60 | SLIP_ERROR_BUFFER_OVERFLOW, 61 | SLIP_ERROR_UNKNOWN_ESCAPED_BYTE, 62 | SLIP_ERROR_INVALID_PACKET 63 | } slip_error_t; 64 | 65 | slip_error_t slip_init(slip_handler_s *slip, const slip_descriptor_s *descriptor); 66 | slip_error_t slip_read_byte(slip_handler_s *slip, uint8_t byte); 67 | 68 | #endif -------------------------------------------------------------------------------- /write.c: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Jonne Kokkonen 2 | // Released under the MIT licence, https://opensource.org/licenses/MIT 3 | 4 | #include "SDL_timer.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int reset_display(struct sp_port *port) { 12 | SDL_Log("Reset display\n"); 13 | uint8_t buf[2]; 14 | int result; 15 | 16 | buf[0] = 0x45; 17 | buf[1] = 0x52; 18 | 19 | result = sp_blocking_write(port, buf, 2, 5); 20 | if (result != 2) { 21 | SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error resetting M8 display, code %d", 22 | result); 23 | return 0; 24 | } 25 | return 1; 26 | } 27 | 28 | int enable_and_reset_display(struct sp_port *port) { 29 | uint8_t buf[1]; 30 | int result; 31 | 32 | SDL_Log("Enabling and resetting M8 display\n"); 33 | 34 | buf[0] = 0x44; 35 | result = sp_blocking_write(port, buf, 1, 5); 36 | if (result != 1) { 37 | SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error enabling M8 display, code %d", 38 | result); 39 | return 0; 40 | } 41 | 42 | SDL_Delay(5); 43 | result = reset_display(port); 44 | if (result == 1) 45 | return 1; 46 | else 47 | return 0; 48 | } 49 | 50 | int disconnect(struct sp_port *port) { 51 | char buf[1] = {'D'}; 52 | int result; 53 | 54 | SDL_Log("Disconnecting M8\n"); 55 | 56 | result = sp_blocking_write(port, buf, 1, 5); 57 | if (result != 1) { 58 | SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending disconnect, code %d", 59 | result); 60 | return -1; 61 | } 62 | return 1; 63 | } 64 | 65 | int send_msg_controller(struct sp_port *port, uint8_t input) { 66 | char buf[2] = {'C', input}; 67 | size_t nbytes = 2; 68 | int result; 69 | result = sp_blocking_write(port, buf, nbytes, 5); 70 | if (result != nbytes) { 71 | SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending input, code %d", 72 | result); 73 | return -1; 74 | } 75 | return 1; 76 | } 77 | 78 | int send_msg_keyjazz(struct sp_port *port, uint8_t note, uint8_t velocity) { 79 | if (velocity > 0x7F) 80 | velocity = 0x7F; 81 | char buf[3] = {'K', note, velocity}; 82 | size_t nbytes = 3; 83 | int result; 84 | result = sp_blocking_write(port, buf, nbytes, 5); 85 | if (result != nbytes) { 86 | SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "Error sending keyjazz, code %d", 87 | result); 88 | return -1; 89 | } 90 | 91 | return 1; 92 | } 93 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #Set all your object files (the object files of all the .c files in your project, e.g. main.o my_sub_functions.o ) 2 | OBJ = main.o serial.o slip.o command.o write.o render.o ini.o config.o input.o font.o fx_cube.o 3 | 4 | #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 5 | DEPS = serial.h slip.h command.h write.h render.h ini.h config.h input.h fx_cube.h 6 | 7 | #Any special libraries you are using in your project (e.g. -lbcm2835 -lrt `pkg-config --libs gtk+-3.0` ), or leave blank 8 | INCLUDES = $(shell pkg-config --libs sdl2 libserialport) 9 | 10 | 11 | 12 | #Set any compiler flags you want to use (e.g. -I/usr/include/somefolder `pkg-config --cflags gtk+-3.0` ), or leave blank 13 | local_CFLAGS = $(CFLAGS) $(shell pkg-config --cflags sdl2 libserialport) -Wall -O2 -pipe -I. 14 | 15 | #Set the compiler you are using ( gcc for C or g++ for C++ ) 16 | CC = gcc 17 | 18 | #Set the filename extensiton of your C files (e.g. .c or .cpp ) 19 | EXTENSION = .c 20 | 21 | #define a rule that applies to all files ending in the .o suffix, which says that the .o file depends upon the .c version of the file and all the .h files included in the DEPS macro. Compile each object file 22 | %.o: %$(EXTENSION) $(DEPS) 23 | $(CC) -c -o $@ $< $(local_CFLAGS) 24 | 25 | #Combine them into the output file 26 | #Set your desired exe output file name here 27 | m8c: $(OBJ) 28 | $(CC) -o $@ $^ $(local_CFLAGS) $(INCLUDES) 29 | 30 | font.c: inline_font.h 31 | @echo "#include " > $@-tmp1 32 | @cat inline_font.h >> $@-tmp1 33 | @cat inprint2.c > $@-tmp2 34 | @sed '/#include/d' $@-tmp2 >> $@-tmp1 35 | @rm $@-tmp2 36 | @mv $@-tmp1 $@ 37 | @echo "[~cat] inline_font.h inprint2.c > font.c" 38 | # $(CC) -c -o font.o font.c $(local_CFLAGS) 39 | 40 | font.c: inline_font.h 41 | @echo "#include " > $@-tmp1 42 | @cat inline_font.h >> $@-tmp1 43 | @cat inprint2.c > $@-tmp2 44 | @sed '/#include/d' $@-tmp2 >> $@-tmp1 45 | @rm $@-tmp2 46 | @mv $@-tmp1 $@ 47 | @echo "[~cat] inline_font.h inprint2.c > font.c" 48 | # $(CC) -c -o font.o font.c $(CFLAGS) 49 | 50 | #Cleanup 51 | .PHONY: clean 52 | 53 | clean: 54 | rm -f *.o *~ m8c *~ font.c 55 | 56 | # PREFIX is environment variable, but if it is not set, then set default value 57 | ifeq ($(PREFIX),) 58 | PREFIX := /usr/local 59 | endif 60 | 61 | install: m8c 62 | install -d $(DESTDIR)$(PREFIX)/bin/ 63 | install -m 755 m8c $(DESTDIR)$(PREFIX)/bin/ 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jonne Kokkonen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ----------- 24 | 25 | The software contains a SLIP command processing routine by Marcin Borowicz (slip.c, slip.h): 26 | 27 | MIT License 28 | 29 | Copyright (c) 2018 Marcin Borowicz 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in all 39 | copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 47 | SOFTWARE. 48 | 49 | ----------- 50 | 51 | The software contains a bitmap font rendered from Trash80's original work, https://fontstruct.com/fontstructions/show/413734/stealth57. 52 | The font (embedded in inline_font.h) is originally licensed under CC-BY-SA 3.0, https://creativecommons.org/licenses/by-sa/3.0/. 53 | The font is used in this program with the permission of the original author. 54 | 55 | ----------- 56 | 57 | The software contains portions of public domain code: 58 | Bitmap font routine by driedfruit, https://github.com/driedfruit/SDL_inprint (SDL2_inprint.h, inprint2.c) 59 | Serial port code from libserialport's examples (serial.c, serial.h) 60 | 61 | ----------- 62 | 63 | The software contains the Tiny ANSI C ini libary under The MIT License: 64 | rxi: https://github.com/rxi/ini (ini.h, ini.c) -------------------------------------------------------------------------------- /slip.c: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 Marcin Borowicz 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* This code is originally by marcinbor85, https://github.com/marcinbor85/slip 26 | It has been simplified a bit as CRC checking etc. is not required in this 27 | program. */ 28 | 29 | #include "slip.h" 30 | #include "command.h" 31 | 32 | #include 33 | #include 34 | 35 | static void reset_rx(slip_handler_s *slip) { 36 | assert(slip != NULL); 37 | 38 | slip->state = SLIP_STATE_NORMAL; 39 | slip->size = 0; 40 | } 41 | 42 | slip_error_t slip_init(slip_handler_s *slip, 43 | const slip_descriptor_s *descriptor) { 44 | assert(slip != NULL); 45 | assert(descriptor != NULL); 46 | assert(descriptor->buf != NULL); 47 | assert(descriptor->recv_message != NULL); 48 | 49 | slip->descriptor = descriptor; 50 | reset_rx(slip); 51 | 52 | return SLIP_NO_ERROR; 53 | } 54 | 55 | static slip_error_t put_byte_to_buffer(slip_handler_s *slip, uint8_t byte) { 56 | slip_error_t error = SLIP_NO_ERROR; 57 | 58 | assert(slip != NULL); 59 | 60 | if (slip->size >= slip->descriptor->buf_size) { 61 | error = SLIP_ERROR_BUFFER_OVERFLOW; 62 | reset_rx(slip); 63 | } else { 64 | slip->descriptor->buf[slip->size++] = byte; 65 | slip->state = SLIP_STATE_NORMAL; 66 | } 67 | 68 | return error; 69 | } 70 | 71 | slip_error_t slip_read_byte(slip_handler_s *slip, uint8_t byte) { 72 | slip_error_t error = SLIP_NO_ERROR; 73 | 74 | assert(slip != NULL); 75 | 76 | switch (slip->state) { 77 | case SLIP_STATE_NORMAL: 78 | switch (byte) { 79 | case SLIP_SPECIAL_BYTE_END: 80 | if (!slip->descriptor->recv_message(slip->descriptor->buf, slip->size)){ 81 | error = SLIP_ERROR_INVALID_PACKET; 82 | } 83 | reset_rx(slip); 84 | break; 85 | case SLIP_SPECIAL_BYTE_ESC: 86 | slip->state = SLIP_STATE_ESCAPED; 87 | break; 88 | default: 89 | error = put_byte_to_buffer(slip, byte); 90 | break; 91 | } 92 | break; 93 | 94 | case SLIP_STATE_ESCAPED: 95 | switch (byte) { 96 | case SLIP_ESCAPED_BYTE_END: 97 | byte = SLIP_SPECIAL_BYTE_END; 98 | break; 99 | case SLIP_ESCAPED_BYTE_ESC: 100 | byte = SLIP_SPECIAL_BYTE_ESC; 101 | break; 102 | default: 103 | error = SLIP_ERROR_UNKNOWN_ESCAPED_BYTE; 104 | reset_rx(slip); 105 | break; 106 | } 107 | 108 | if (error != SLIP_NO_ERROR) 109 | break; 110 | 111 | error = put_byte_to_buffer(slip, byte); 112 | break; 113 | } 114 | 115 | return error; 116 | } 117 | -------------------------------------------------------------------------------- /fx_cube.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "SDL2_inprint.h" 3 | 4 | #define target_width 320 5 | #define target_height 240 6 | static SDL_Texture *texture_cube; 7 | static SDL_Texture *texture_text; 8 | static SDL_Renderer *fx_renderer; 9 | static SDL_Color line_color; 10 | 11 | const char *text_m8c = "M8C"; 12 | const char *text_disconnected = "DEVICE DISCONNECTED"; 13 | 14 | static const float center_x = (float)target_width / 2; 15 | static const float center_y = (float)target_height / 2; 16 | 17 | static const float default_nodes[8][3] = { 18 | {-1, -1, -1}, {-1, -1, 1}, {-1, 1, -1}, {-1, 1, 1}, 19 | {1, -1, -1}, {1, -1, 1}, {1, 1, -1}, {1, 1, 1}}; 20 | 21 | static int edges[12][2] = {{0, 1}, {1, 3}, {3, 2}, {2, 0}, {4, 5}, {5, 7}, 22 | {7, 6}, {6, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}}; 23 | 24 | static float nodes[8][3]; 25 | 26 | static void scale(float factor0, float factor1, float factor2) { 27 | for (int i = 0; i < 8; i++) { 28 | nodes[i][0] *= factor0; 29 | nodes[i][1] *= factor1; 30 | nodes[i][2] *= factor2; 31 | } 32 | } 33 | 34 | static void rotate_cube(float angle_x, float angle_y) { 35 | float sin_x = SDL_sin(angle_x); 36 | float cos_x = SDL_cos(angle_x); 37 | float sin_y = SDL_sin(angle_y); 38 | float cos_y = SDL_cos(angle_y); 39 | for (int i = 0; i < 8; i++) { 40 | float x = nodes[i][0]; 41 | float y = nodes[i][1]; 42 | float z = nodes[i][2]; 43 | 44 | nodes[i][0] = x * cos_x - z * sin_x; 45 | nodes[i][2] = z * cos_x + x * sin_x; 46 | 47 | z = nodes[i][2]; 48 | 49 | nodes[i][1] = y * cos_y - z * sin_y; 50 | nodes[i][2] = z * cos_y + y * sin_y; 51 | } 52 | } 53 | 54 | void fx_cube_init(SDL_Renderer *target_renderer, SDL_Color foreground_color) { 55 | 56 | fx_renderer = target_renderer; 57 | line_color = foreground_color; 58 | 59 | texture_cube = 60 | SDL_CreateTexture(fx_renderer, SDL_PIXELFORMAT_ARGB8888, 61 | SDL_TEXTUREACCESS_TARGET, target_width, target_height); 62 | texture_text = 63 | SDL_CreateTexture(fx_renderer, SDL_PIXELFORMAT_ARGB8888, 64 | SDL_TEXTUREACCESS_TARGET, target_width, target_height); 65 | 66 | SDL_Texture *og_target = SDL_GetRenderTarget(fx_renderer); 67 | 68 | SDL_SetRenderTarget(fx_renderer, texture_text); 69 | 70 | inprint(fx_renderer, text_disconnected, 168, 230, 0xFFFFFF, 0x000000); 71 | inprint(fx_renderer, text_m8c, 2, 2, 0xFFFFFF, 0x000000); 72 | 73 | SDL_SetRenderTarget(fx_renderer, og_target); 74 | 75 | // Initialize default nodes 76 | SDL_memcpy(nodes, default_nodes, sizeof(default_nodes)); 77 | 78 | scale(50, 50, 50); 79 | rotate_cube(M_PI / 4, SDL_atan(SDL_sqrt(2))); 80 | 81 | SDL_SetTextureBlendMode(texture_cube, SDL_BLENDMODE_BLEND); 82 | SDL_SetTextureBlendMode(texture_text, SDL_BLENDMODE_BLEND); 83 | } 84 | 85 | void fx_cube_destroy() { 86 | SDL_DestroyTexture(texture_cube); 87 | SDL_DestroyTexture(texture_text); 88 | } 89 | 90 | void fx_cube_update() { 91 | SDL_Point points[24]; 92 | int points_counter = 0; 93 | SDL_Texture *og_texture = SDL_GetRenderTarget(fx_renderer); 94 | 95 | SDL_SetRenderTarget(fx_renderer, texture_cube); 96 | SDL_SetRenderDrawColor(fx_renderer, 0, 0, 0, 200); 97 | SDL_RenderClear(fx_renderer); 98 | 99 | int seconds = SDL_GetTicks() / 1000; 100 | float scalefactor = 1 + (SDL_sin(seconds) * 0.01); 101 | 102 | scale(scalefactor, scalefactor, scalefactor); 103 | rotate_cube(M_PI / 180, M_PI / 270); 104 | 105 | for (int i = 0; i < 12; i++) { 106 | float *p1 = nodes[edges[i][0]]; 107 | float *p2 = nodes[edges[i][1]]; 108 | points[points_counter++] = 109 | (SDL_Point){p1[0] + center_x, nodes[edges[i][0]][1] + center_y}; 110 | points[points_counter++] = (SDL_Point){p2[0] + center_x, p2[1] + center_y}; 111 | } 112 | 113 | SDL_RenderCopy(fx_renderer, texture_text, NULL, NULL); 114 | SDL_SetRenderDrawColor(fx_renderer, line_color.r, line_color.g, line_color.b, 115 | line_color.a); 116 | SDL_RenderDrawLines(fx_renderer, points, 24); 117 | 118 | SDL_SetRenderTarget(fx_renderer, og_texture); 119 | SDL_RenderCopy(fx_renderer, texture_cube, NULL, NULL); 120 | } 121 | -------------------------------------------------------------------------------- /command.c: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Jonne Kokkonen 2 | // Released under the MIT licence, https://opensource.org/licenses/MIT 3 | 4 | #include 5 | 6 | #include "command.h" 7 | #include "render.h" 8 | 9 | // Convert 2 little-endian 8bit bytes to a 16bit integer 10 | static uint16_t decodeInt16(uint8_t *data, uint8_t start) { 11 | return data[start] | (uint16_t)data[start + 1] << 8; 12 | } 13 | 14 | enum m8_command_bytes { 15 | draw_rectangle_command = 0xFE, 16 | draw_rectangle_command_datalength = 12, 17 | draw_character_command = 0xFD, 18 | draw_character_command_datalength = 12, 19 | draw_oscilloscope_waveform_command = 0xFC, 20 | draw_oscilloscope_waveform_command_mindatalength = 1 + 3, 21 | draw_oscilloscope_waveform_command_maxdatalength = 1 + 3 + 320, 22 | joypad_keypressedstate_command = 0xFB, 23 | joypad_keypressedstate_command_datalength = 2 24 | }; 25 | 26 | static inline void dump_packet(uint32_t size, uint8_t *recv_buf) { 27 | for (uint16_t a = 0; a < size; a++) { 28 | SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "0x%02X ", recv_buf[a]); 29 | } 30 | SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "\n"); 31 | } 32 | 33 | int process_command(uint8_t *data, uint32_t size) { 34 | 35 | uint8_t recv_buf[size + 1]; 36 | 37 | memcpy(recv_buf, data, size); 38 | recv_buf[size] = 0; 39 | 40 | switch (recv_buf[0]) { 41 | 42 | case draw_rectangle_command: 43 | 44 | if (size != draw_rectangle_command_datalength) { 45 | SDL_LogError( 46 | SDL_LOG_CATEGORY_ERROR, 47 | "Invalid draw rectangle packet: expected length %d, got %d\n", 48 | draw_rectangle_command_datalength, size); 49 | dump_packet(size, recv_buf); 50 | return 0; 51 | break; 52 | } else { 53 | 54 | struct draw_rectangle_command rectcmd = { 55 | {decodeInt16(recv_buf, 1), decodeInt16(recv_buf, 3)}, // position x/y 56 | {decodeInt16(recv_buf, 5), decodeInt16(recv_buf, 7)}, // size w/h 57 | {recv_buf[9], recv_buf[10], recv_buf[11]}}; // color r/g/b 58 | 59 | draw_rectangle(&rectcmd); 60 | return 1; 61 | } 62 | 63 | break; 64 | 65 | case draw_character_command: 66 | 67 | if (size != draw_character_command_datalength) { 68 | SDL_LogError( 69 | SDL_LOG_CATEGORY_ERROR, 70 | "Invalid draw character packet: expected length %d, got %d\n", 71 | draw_character_command_datalength, size); 72 | dump_packet(size, recv_buf); 73 | return 0; 74 | break; 75 | } else { 76 | 77 | struct draw_character_command charcmd = { 78 | recv_buf[1], // char 79 | {decodeInt16(recv_buf, 2), decodeInt16(recv_buf, 4)}, // position x/y 80 | {recv_buf[6], recv_buf[7], recv_buf[8]}, // foreground r/g/b 81 | {recv_buf[9], recv_buf[10], recv_buf[11]}}; // background r/g/b 82 | draw_character(&charcmd); 83 | return 1; 84 | } 85 | 86 | break; 87 | 88 | case draw_oscilloscope_waveform_command: 89 | 90 | if (size < draw_oscilloscope_waveform_command_mindatalength || 91 | size > draw_oscilloscope_waveform_command_maxdatalength) { 92 | SDL_LogError( 93 | SDL_LOG_CATEGORY_ERROR, 94 | "Invalid draw oscilloscope packet: expected length between %d " 95 | "and %d, got " 96 | "%d\n", 97 | draw_oscilloscope_waveform_command_mindatalength, 98 | draw_oscilloscope_waveform_command_maxdatalength, size); 99 | dump_packet(size, recv_buf); 100 | return 0; 101 | break; 102 | } else { 103 | 104 | struct draw_oscilloscope_waveform_command osccmd; 105 | 106 | osccmd.color = 107 | (struct color){recv_buf[1], recv_buf[2], recv_buf[3]}; // color r/g/b 108 | memcpy(osccmd.waveform, &recv_buf[4], size - 4); 109 | 110 | osccmd.waveform_size = size - 4; 111 | 112 | draw_waveform(&osccmd); 113 | return 1; 114 | } 115 | 116 | break; 117 | 118 | case joypad_keypressedstate_command: 119 | /* 120 | if (size != joypad_keypressedstate_command_datalength) { 121 | SDL_LogError(SDL_LOG_CATEGORY_ERROR, 122 | "Invalid joypad keypressed state packet: expected length %d, " 123 | "got %d\n", 124 | joypad_keypressedstate_command_datalength, size); 125 | dump_packet(size, recv_buf); 126 | break; 127 | } */ 128 | 129 | // nothing is done with joypad key pressed packets for now 130 | return 1; 131 | break; 132 | 133 | default: 134 | 135 | SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Invalid packet\n"); 136 | dump_packet(size, recv_buf); 137 | return 0; 138 | break; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /inprint2.c: -------------------------------------------------------------------------------- 1 | // Bitmap font routine originally by driedfruit, https://github.com/driedfruit/SDL_inprint 2 | // Released into public domain. 3 | // Modified to support adding a background to text. 4 | 5 | #include 6 | 7 | #include "inline_font.h" /* Actual font data */ 8 | 9 | #define CHARACTERS_PER_ROW 16 /* I like 16 x 8 fontsets. */ 10 | #define CHARACTERS_PER_COLUMN 8 /* 128 x 1 is another popular format. */ 11 | 12 | static SDL_Renderer *selected_renderer = NULL; 13 | static SDL_Texture *inline_font = NULL; 14 | static SDL_Texture *selected_font = NULL; 15 | static Uint16 selected_font_w, selected_font_h; 16 | 17 | void prepare_inline_font() { 18 | Uint32 *pix_ptr, tmp; 19 | int i, len, j; 20 | SDL_Surface *surface; 21 | Uint32 colors[2]; 22 | 23 | selected_font_w = inline_font_width; 24 | selected_font_h = inline_font_height; 25 | 26 | if (inline_font != NULL) { 27 | selected_font = inline_font; 28 | return; 29 | } 30 | 31 | surface = SDL_CreateRGBSurface(0, inline_font_width, inline_font_height, 32, 32 | #if SDL_BYTEORDER == SDL_BIG_ENDIAN 33 | 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff 34 | #else 35 | 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 36 | #endif 37 | ); 38 | colors[0] = SDL_MapRGBA(surface->format, 0xFF, 0xFF, 0xFF, 0xFF); 39 | colors[1] = SDL_MapRGBA(surface->format, 0x00, 0x00, 0x00, 40 | 0x00 /* or 0xFF, to have bg-color */); 41 | 42 | /* Get pointer to pixels and array length */ 43 | pix_ptr = (Uint32 *)surface->pixels; 44 | len = surface->h * surface->w / 8; 45 | 46 | /* Copy */ 47 | for (i = 0; i < len; i++) { 48 | tmp = (Uint8)inline_font_bits[i]; 49 | for (j = 0; j < 8; j++) { 50 | Uint8 mask = (0x01 << j); 51 | pix_ptr[i * 8 + j] = colors[(tmp & mask) >> j]; 52 | } 53 | } 54 | 55 | inline_font = SDL_CreateTextureFromSurface(selected_renderer, surface); 56 | SDL_FreeSurface(surface); 57 | 58 | selected_font = inline_font; 59 | } 60 | void kill_inline_font(void) { 61 | SDL_DestroyTexture(inline_font); 62 | inline_font = NULL; 63 | } 64 | void inrenderer(SDL_Renderer *renderer) { selected_renderer = renderer; } 65 | void infont(SDL_Texture *font) { 66 | Uint32 format; 67 | int access; 68 | int w, h; 69 | 70 | if (font == NULL) { 71 | prepare_inline_font(); 72 | return; 73 | } 74 | 75 | SDL_QueryTexture(font, &format, &access, &w, &h); 76 | 77 | selected_font = font; 78 | selected_font_w = w; 79 | selected_font_h = h; 80 | } 81 | void incolor1(SDL_Color *color) { 82 | SDL_SetTextureColorMod(selected_font, color->r, color->g, color->b); 83 | } 84 | void incolor(Uint32 fore, 85 | Uint32 unused) /* Color must be in 0x00RRGGBB format ! */ 86 | { 87 | SDL_Color pal[1]; 88 | pal[0].r = (Uint8)((fore & 0x00FF0000) >> 16); 89 | pal[0].g = (Uint8)((fore & 0x0000FF00) >> 8); 90 | pal[0].b = (Uint8)((fore & 0x000000FF)); 91 | SDL_SetTextureColorMod(selected_font, pal[0].r, pal[0].g, pal[0].b); 92 | } 93 | void inprint(SDL_Renderer *dst, const char *str, Uint32 x, Uint32 y, 94 | Uint32 fgcolor, Uint32 bgcolor) { 95 | SDL_Rect s_rect; 96 | SDL_Rect d_rect; 97 | SDL_Rect bg_rect; 98 | 99 | static uint32_t previous_fgcolor; 100 | 101 | d_rect.x = x; 102 | d_rect.y = y; 103 | s_rect.w = selected_font_w / CHARACTERS_PER_ROW; 104 | s_rect.h = selected_font_h / CHARACTERS_PER_COLUMN; 105 | d_rect.w = s_rect.w; 106 | d_rect.h = s_rect.h; 107 | 108 | if (dst == NULL) 109 | dst = selected_renderer; 110 | 111 | for (; *str; str++) { 112 | int id = (int)*str; 113 | #if (CHARACTERS_PER_COLUMN != 1) 114 | int row = id / CHARACTERS_PER_ROW; 115 | int col = id % CHARACTERS_PER_ROW; 116 | s_rect.x = col * s_rect.w; 117 | s_rect.y = row * s_rect.h; 118 | #else 119 | s_rect.x = id * s_rect.w; 120 | s_rect.y = 0; 121 | #endif 122 | if (id == '\n') { 123 | d_rect.x = x; 124 | d_rect.y += s_rect.h; 125 | continue; 126 | } 127 | if (fgcolor != previous_fgcolor) { 128 | incolor(fgcolor, 0); 129 | previous_fgcolor = fgcolor; 130 | } 131 | if (bgcolor != -1) { 132 | SDL_SetRenderDrawColor(selected_renderer, 133 | (Uint8)((bgcolor & 0x00FF0000) >> 16), 134 | (Uint8)((bgcolor & 0x0000FF00) >> 8), 135 | (Uint8)((bgcolor & 0x000000FF)), 0xFF); 136 | bg_rect = d_rect; 137 | bg_rect.w = 6; 138 | SDL_RenderFillRect(dst, &bg_rect); 139 | } 140 | SDL_RenderCopy(dst, selected_font, &s_rect, &d_rect); 141 | d_rect.x += s_rect.w; 142 | } 143 | } 144 | SDL_Texture *get_inline_font(void) { return selected_font; } 145 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: m8c build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build-win: 10 | runs-on: windows-latest 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | include: 15 | - { sys: mingw32, env: i686, win: win32 } 16 | - { sys: mingw64, env: x86_64, win: win64 } 17 | name: ${{ matrix.win }} 18 | defaults: 19 | run: 20 | shell: msys2 {0} 21 | env: 22 | MINGW_ARCH: ${{ matrix.sys }} 23 | steps: 24 | 25 | - name: 'git config' 26 | run: git config --global core.autocrlf input 27 | shell: bash 28 | 29 | - name: 'Checkout' 30 | uses: actions/checkout@v2 31 | with: 32 | fetch-depth: 0 33 | 34 | - name: 'Setup MSYS2' 35 | uses: msys2/setup-msys2@v2 36 | with: 37 | msystem: ${{ matrix.sys }} 38 | update: true 39 | install: mingw-w64-${{ matrix.env }}-toolchain make mingw-w64-${{ matrix.env }}-SDL2 mingw-w64-${{ matrix.env }}-libserialport zip dos2unix 40 | 41 | - name: 'Get current date' 42 | id: date 43 | run: echo "::set-output name=date::$(date +%Y%m%d)" 44 | 45 | - name: 'Build package' 46 | run: | 47 | make 48 | strip -g m8c.exe 49 | if [ ${{ matrix.win }} == "win32" ] 50 | then 51 | cp /mingw32/bin/SDL2.dll . 52 | cp /mingw32/bin/libgcc_s_dw2-1.dll . 53 | cp /mingw32/bin/libserialport-0.dll . 54 | cp /mingw32/bin/libwinpthread-1.dll . 55 | else 56 | cp /mingw64/bin/SDL2.dll . 57 | cp /mingw64/bin/libserialport-0.dll . 58 | fi 59 | unix2dos README.md config.ini.sample LICENSE AUDIOGUIDE.md 60 | - name: 'Upload artifact (win32)' 61 | if: matrix.win == 'win32' 62 | uses: actions/upload-artifact@v2 63 | with: 64 | name: m8c-${{ steps.date.outputs.date }}-${{ matrix.win }} 65 | path: | 66 | m8c.exe 67 | SDL2.dll 68 | libserialport-0.dll 69 | libgcc_s_dw2-1.dll 70 | libwinpthread-1.dll 71 | gamecontrollerdb.txt 72 | LICENSE 73 | README.md 74 | AUDIOGUIDE.md 75 | config.ini.sample 76 | - name: 'Upload artifact (win64)' 77 | if: matrix.win == 'win64' 78 | uses: actions/upload-artifact@v2 79 | with: 80 | name: m8c-${{ steps.date.outputs.date }}-${{ matrix.win }} 81 | path: | 82 | m8c.exe 83 | SDL2.dll 84 | libserialport-0.dll 85 | gamecontrollerdb.txt 86 | LICENSE 87 | README.md 88 | AUDIOGUIDE.md 89 | config.ini.sample 90 | 91 | build-linux: 92 | runs-on: ubuntu-latest 93 | name: linux-x86_64 94 | steps: 95 | - name: 'Install dependencies' 96 | run: | 97 | sudo apt-get update 98 | sudo apt-get install --fix-missing build-essential libsdl2-dev libserialport-dev zip 99 | - name: 'Checkout' 100 | uses: actions/checkout@v2 101 | 102 | - name: 'Get current date' 103 | id: date 104 | run: echo "::set-output name=date::$(date +%Y%m%d)" 105 | 106 | - name: 'Build package' 107 | run: | 108 | make 109 | - name: 'Upload artifact' 110 | uses: actions/upload-artifact@v2 111 | with: 112 | name: m8c-${{ steps.date.outputs.date }}-linux 113 | path: | 114 | LICENSE 115 | README.md 116 | AUDIOGUIDE.md 117 | m8c 118 | gamecontrollerdb.txt 119 | config.ini.sample 120 | 121 | build-macos: 122 | runs-on: macos-latest 123 | 124 | steps: 125 | - name: 'Install dependencies' 126 | run: brew install sdl2 libserialport pkg-config 127 | 128 | - name: 'Checkout' 129 | uses: actions/checkout@v2 130 | 131 | - name: 'Get current date' 132 | id: date 133 | run: echo "::set-output name=date::$(date +%Y%m%d)" 134 | 135 | - name: 'Build package' 136 | run: | 137 | make 138 | chmod 755 m8c 139 | cd macos/m8c.app/Contents/ 140 | mkdir MacOS 141 | cd MacOS 142 | cp ../../../../m8c . 143 | cp /usr/local/opt/sdl2/lib/libSDL2* . 144 | cp /usr/local/opt/libserialport/lib/libserialport.* . 145 | install_name_tool -change /usr/local/opt/libserialport/lib/libserialport.0.dylib @executable_path/libserialport.0.dylib m8c 146 | install_name_tool -change /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib @executable_path/libSDL2-2.0.0.dylib m8c 147 | cd ../../../../ 148 | cp -r macos/m8c.app . 149 | zip -r m8c.zip m8c.app LICENSE README.md AUDIOGUIDE.md config.ini.sample gamecontrollerdb.txt 150 | - name: 'Upload artifact' 151 | uses: actions/upload-artifact@v2 152 | with: 153 | name: m8c-${{ steps.date.outputs.date }}-macos 154 | path: m8c.zip 155 | 156 | -------------------------------------------------------------------------------- /serial.c: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Jonne Kokkonen 2 | // Released under the MIT licence, https://opensource.org/licenses/MIT 3 | 4 | // Contains portions of code from libserialport's examples released to the 5 | // public domain 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "serial.h" 14 | 15 | // Helper function for error handling 16 | static int check(enum sp_return result); 17 | 18 | static int detect_m8_serial_device(struct sp_port *port) { 19 | // Check the connection method - we want USB serial devices 20 | enum sp_transport transport = sp_get_port_transport(port); 21 | 22 | if (transport == SP_TRANSPORT_USB) { 23 | // Get the USB vendor and product IDs. 24 | int usb_vid, usb_pid; 25 | sp_get_port_usb_vid_pid(port, &usb_vid, &usb_pid); 26 | 27 | if (usb_vid == 0x16C0 && usb_pid == 0x048A) 28 | return 1; 29 | } 30 | 31 | return 0; 32 | } 33 | 34 | // Checks for connected devices and whether the specified device still exists 35 | int check_serial_port(struct sp_port *m8_port) { 36 | 37 | int device_found = 0; 38 | 39 | /* A pointer to a null-terminated array of pointers to 40 | * struct sp_port, which will contain the ports found.*/ 41 | struct sp_port **port_list; 42 | 43 | /* Call sp_list_ports() to get the ports. The port_list 44 | * pointer will be updated to refer to the array created. */ 45 | enum sp_return result = sp_list_ports(&port_list); 46 | 47 | if (result != SP_OK) { 48 | SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "sp_list_ports() failed!\n"); 49 | abort(); 50 | } 51 | 52 | /* Iterate through the ports. When port_list[i] is NULL 53 | * this indicates the end of the list. */ 54 | for (int i = 0; port_list[i] != NULL; i++) { 55 | struct sp_port *port = port_list[i]; 56 | 57 | if (detect_m8_serial_device(port)) { 58 | if (strcmp(sp_get_port_name(port), sp_get_port_name(m8_port)) == 0) 59 | device_found = 1; 60 | } 61 | } 62 | 63 | sp_free_port_list(port_list); 64 | return device_found; 65 | 66 | } 67 | 68 | struct sp_port *init_serial(int verbose) { 69 | /* A pointer to a null-terminated array of pointers to 70 | * struct sp_port, which will contain the ports found.*/ 71 | struct sp_port *m8_port = NULL; 72 | struct sp_port **port_list; 73 | 74 | if (verbose) 75 | SDL_Log("Looking for USB serial devices.\n"); 76 | 77 | /* Call sp_list_ports() to get the ports. The port_list 78 | * pointer will be updated to refer to the array created. */ 79 | enum sp_return result = sp_list_ports(&port_list); 80 | 81 | if (result != SP_OK) { 82 | SDL_LogError(SDL_LOG_CATEGORY_SYSTEM, "sp_list_ports() failed!\n"); 83 | abort(); 84 | } 85 | 86 | /* Iterate through the ports. When port_list[i] is NULL 87 | * this indicates the end of the list. */ 88 | for (int i = 0; port_list[i] != NULL; i++) { 89 | struct sp_port *port = port_list[i]; 90 | 91 | if (detect_m8_serial_device(port)) { 92 | SDL_Log("Found M8 in %s.\n", sp_get_port_name(port)); 93 | sp_copy_port(port, &m8_port); 94 | } 95 | } 96 | 97 | sp_free_port_list(port_list); 98 | 99 | if (m8_port != NULL) { 100 | // Open the serial port and configure it 101 | SDL_Log("Opening port.\n"); 102 | enum sp_return result; 103 | 104 | result = sp_open(m8_port, SP_MODE_READ_WRITE); 105 | if (check(result) != SP_OK) 106 | return NULL; 107 | 108 | result = sp_set_baudrate(m8_port, 115200); 109 | if (check(result) != SP_OK) 110 | return NULL; 111 | 112 | result = sp_set_bits(m8_port, 8); 113 | if (check(result) != SP_OK) 114 | return NULL; 115 | 116 | result = sp_set_parity(m8_port, SP_PARITY_NONE); 117 | if (check(result) != SP_OK) 118 | return NULL; 119 | 120 | result = sp_set_stopbits(m8_port, 1); 121 | if (check(result) != SP_OK) 122 | return NULL; 123 | 124 | result = sp_set_flowcontrol(m8_port, SP_FLOWCONTROL_NONE); 125 | if (check(result) != SP_OK) 126 | return NULL; 127 | } else { 128 | if (verbose) 129 | SDL_LogCritical(SDL_LOG_CATEGORY_SYSTEM, "Cannot find a M8.\n"); 130 | } 131 | 132 | return (m8_port); 133 | } 134 | 135 | // Helper function for error handling. 136 | static int check(enum sp_return result) { 137 | 138 | char *error_message; 139 | 140 | switch (result) { 141 | case SP_ERR_ARG: 142 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Invalid argument.\n"); 143 | break; 144 | case SP_ERR_FAIL: 145 | error_message = sp_last_error_message(); 146 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Failed: %s\n", 147 | error_message); 148 | sp_free_error_message(error_message); 149 | break; 150 | case SP_ERR_SUPP: 151 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Not supported.\n"); 152 | break; 153 | case SP_ERR_MEM: 154 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, 155 | "Error: Couldn't allocate memory.\n"); 156 | break; 157 | case SP_OK: 158 | default: 159 | break; 160 | } 161 | return result; 162 | } 163 | -------------------------------------------------------------------------------- /AUDIOGUIDE.md: -------------------------------------------------------------------------------- 1 | # Audio setup for M8 headless 2 | 3 | ## Windows 4 | 5 | * Right click Sound in Taskbar 6 | * Open Sound Settings 7 | * Select M8 (or whatever your Teensy Interface is named) as Input 8 | * Select Device properties 9 | * Select Additional Device Properties 10 | * Select Listen tab 11 | * Check Listen to this device and then select output of your choice (whatever source plays your speakers/headphone uses) 12 | 13 | ## Linux / Raspberry Pi 14 | 15 | ### Pulseaudio 16 | 17 | If you have a desktop installation, chances are you're using Pulseaudio which has a module for making an internal loopback. 18 | 19 | run the following command in terminal: 20 | 21 | ``` 22 | pacmd load-module module-loopback latency_msec=1 23 | ``` 24 | 25 | after that, you should hear audio through your default input device. If not, check Input devices in Pulseaudio Volume Control 26 | ``` 27 | pavucontrol 28 | ``` 29 | 30 | If the tools are missing, install pavucontrol (Debian based systems): 31 | ``` 32 | apt install pavucontrol 33 | ``` 34 | 35 | ### JACK 36 | 37 | It is possible to route the M8 USB audio to another audio interface in Linux without a DAW, using JACK Audio Connection kit and a few other command line tools. 38 | 39 | Please note that this is not an optimal solution for getting audio from the headless M8, but as far as I know the easiest way to use the USB audio feature on Linux. The best parameters for running the applications can vary a lot depending on your configuration, and however you do it, there will be some latency. 40 | 41 | It is possible to use the integrated audio of the Pi for this, but it has quite a poor quality and likely will have sound popping issues while running this. 42 | 43 | These instructions were written for Raspberry PI OS desktop using a Raspberry Pi 3 B+, but should work for other Debian/Ubuntu flavors as well. 44 | 45 | Open Terminal and run the commands below to setup the system. 46 | 47 | ----- 48 | 49 | #### First time setup 50 | 51 | #### Install JACK Audio Connection Kit 52 | 53 | The jackd2 package will provide the basic tools that make the audio patching possible. 54 | 55 | ``` 56 | sudo apt install jackd2 57 | ``` 58 | 59 | #### Add your user to the audio group 60 | 61 | This is required to get real time priority for the JACK process. 62 | 63 | ``` 64 | sudo usermod -a -G audio $USER 65 | ``` 66 | 67 | You need to log out completely or reboot for this change to take effect. 68 | 69 | ----- 70 | 71 | #### Find your audio interface ALSA device id 72 | 73 | Use the ```aplay -l``` command to list the audio devices present in the system. 74 | 75 | ``` 76 | pi@raspberrypi:~ $ aplay -l 77 | **** List of PLAYBACK Hardware Devices **** 78 | card 0: Headphones [bcm2835 Headphones], device 0: bcm2835 Headphones [bcm2835 Headphones] 79 | Subdevices: 8/8 80 | Subdevice #0: subdevice #0 81 | Subdevice #1: subdevice #1 82 | Subdevice #2: subdevice #2 83 | Subdevice #3: subdevice #3 84 | Subdevice #4: subdevice #4 85 | Subdevice #5: subdevice #5 86 | Subdevice #6: subdevice #6 87 | Subdevice #7: subdevice #7 88 | card 1: M8 [M8], device 0: USB Audio [USB Audio] 89 | Subdevices: 1/1 90 | Subdevice #0: subdevice #0 91 | card 2: vc4hdmi [vc4-hdmi], device 0: MAI PCM vc4-hdmi-hifi-0 [MAI PCM vc4-hdmi-hifi-0] 92 | Subdevices: 1/1 93 | Subdevice #0: subdevice #0 94 | card 3: v10 [AudioQuest DragonFly Red v1.0], device 0: USB Audio [USB Audio] 95 | Subdevices: 1/1 96 | Subdevice #0: subdevice #0 97 | 98 | ``` 99 | 100 | Take note of the card number you wish to use. In my case, I want to use card 3: v10. 101 | 102 | After these steps have been taken care of, we can try to route some audio. 103 | 104 | #### Start JACK server and route audio 105 | ``` 106 | jackd -d alsa -d hw:M8 -r44100 -p512 & 107 | ``` 108 | 109 | This will start the JACK server using ALSA backend driver (-d alsa), open the M8 USB audio interface (-d hw:M8) with the sample rate of 44100hz (-r44100) and buffer period size of 512 frames (-p512). The program will run in background (the & character in the end specifies this). 110 | If you don't see your terminal prompt, just press enter and it should appear. 111 | 112 | Next, we will create a virtual port for routing audio to another interface. We need to specify the audio interface id where we want to output audio here. 113 | 114 | ```alsa_out -j m8out -d hw: