shithub: m8c

Download patch

ref: d963ae7f4ac11387959d6fd70d8abb6f1efb7b42
parent: 8917f16e1c96c573cf13e2887f61ebd13d8d87e3
parent: 0f9fece872802caf790a34387fdb2b761ae55ffb
author: Jonne Kokkonen <jonne.kokkonen@gmail.com>
date: Wed Mar 8 17:34:42 EST 2023

Merge pull request #99 from laamaa/audio

Experimental USB Audio monitoring support

--- 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 = main.o serial.o slip.o command.o render.o ini.o config.o input.o font.o fx_cube.o usb.o
+OBJ = main.o serial.o slip.o command.o render.o ini.o config.o input.o font.o fx_cube.o usb.o audio.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 = serial.h slip.h command.h render.h ini.h config.h input.h fx_cube.h
+DEPS = serial.h slip.h command.h render.h ini.h config.h input.h fx_cube.h audio.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 sdl2 libserialport)
--- a/README.md
+++ b/README.md
@@ -10,9 +10,7 @@
 
 *m8c* is a client for Dirtywave M8 tracker's headless mode. The application should be cross-platform ready and can be built in Linux, Windows (with MSYS2/MINGW64) and Mac OS.
 
-Please note that routing the headless M8 USB audio isn't in the scope of this program -- if this is needed, it can be achieved with tools like Pipewire, Pulseaudio, Jack w/ alsa\_in and alsa\_out just to name a few. The file AUDIOGUIDE.md contains some examples for routing the audio.
-
-If you want to route audio with the headless client you could try https://github.com/booss/rm8 which is a great native client with audio support (among other user features)!
+Experimental audio routing support can be enabled by setting the config value "audio_enabled" to "true". The audio buffer size can also be tweaked from the config file for possible lower latencies.
 
 Many thanks to:
 
--- /dev/null
+++ b/audio.c
@@ -1,0 +1,85 @@
+// Copyright 2021 Jonne Kokkonen
+// Released under the MIT licence, https://opensource.org/licenses/MIT
+
+#include "audio.h"
+#include "SDL_audio.h"
+#include <SDL.h>
+#include <stdint.h>
+
+static SDL_AudioDeviceID devid_in = 0;
+static SDL_AudioDeviceID devid_out = 0;
+
+void audio_cb_in(void *userdata, uint8_t *stream, int len) {
+  SDL_QueueAudio(devid_out, stream, len);
+}
+
+int audio_init(int audio_buffer_size) {
+
+  int i = 0;
+  int m8_found = 0;
+  int devcount_in = 0; // audio input device count
+
+  devcount_in = SDL_GetNumAudioDevices(SDL_TRUE);
+
+  if (devcount_in < 1) {
+    SDL_Log("No audio capture devices, SDL Error: %s", SDL_GetError());
+    return 0;
+  } else {
+    for (i = 0; i < devcount_in; i++) {
+      // Check if input device exists before doing anything else
+      if (strcmp(SDL_GetAudioDeviceName(i, SDL_TRUE), "M8 Analog Stereo") ==
+          0) {
+        SDL_Log("M8 Audio Input device found");
+        m8_found = 1;
+      }
+    }
+    if (m8_found == 0) {
+      // forget about it
+      return 0;
+    }
+
+    SDL_AudioSpec want_in, have_in, want_out, have_out;
+
+    // Open output device first to avoid possible Directsound errors
+    SDL_zero(want_out);
+    want_out.freq = 44100;
+    want_out.format = AUDIO_S16;
+    want_out.channels = 2;
+    want_out.samples = audio_buffer_size;
+    devid_out = SDL_OpenAudioDevice(NULL, 0, &want_out, &have_out,
+                                    SDL_AUDIO_ALLOW_ANY_CHANGE);
+    if (devid_out == 0) {
+      SDL_Log("Failed to open output: %s", SDL_GetError());
+      return 0;
+    }
+    SDL_Log("Using default audio output device: %s",SDL_GetAudioDeviceName(0, 0));
+
+    SDL_zero(want_in);
+    want_in.freq = 44100;
+    want_in.format = AUDIO_S16;
+    want_in.channels = 2;
+    want_in.samples = audio_buffer_size;
+    want_in.callback = audio_cb_in;
+    devid_in = SDL_OpenAudioDevice("M8 Analog Stereo", SDL_TRUE, &want_in,
+                                   &have_in, SDL_AUDIO_ALLOW_ANY_CHANGE);
+    if (devid_in == 0) {
+      SDL_Log("Failed to open M8 audio device, SDL Error: %s", SDL_GetError());
+      return 0;
+    }
+  }
+
+  // Start audio processing
+  SDL_Log("Opening audio devices");
+  SDL_PauseAudioDevice(devid_in, 0);
+  SDL_PauseAudioDevice(devid_out, 0);
+
+  return 1;
+}
+
+void audio_destroy() {
+  SDL_Log("Closing audio devices");
+  SDL_PauseAudioDevice(devid_in, 1);
+  SDL_PauseAudioDevice(devid_out, 1);
+  SDL_CloseAudioDevice(devid_in);
+  SDL_CloseAudioDevice(devid_out);
+}
--- /dev/null
+++ b/audio.h
@@ -1,0 +1,13 @@
+// Copyright 2021 Jonne Kokkonen
+// Released under the MIT licence, https://opensource.org/licenses/MIT
+
+#ifndef AUDIO_H
+#define AUDIO_H
+
+#include <stdint.h>
+
+void audio_cb_in(void *userdata, uint8_t *stream, int len);
+int audio_init(int audio_buffer_size);
+void audio_destroy();
+
+#endif
--- a/config.c
+++ b/config.c
@@ -2,9 +2,11 @@
 // Released under the MIT licence, https://opensource.org/licenses/MIT
 
 #include "config.h"
+#include "SDL_stdinc.h"
 #include "ini.h"
 #include <SDL.h>
 #include <assert.h>
+#include <stdio.h>
 
 /* Case insensitive string compare from ini.h library */
 static int strcmpci(const char *a, const char *b) {
@@ -26,7 +28,10 @@
   c.init_use_gpu = 1;    // default to use hardware acceleration
   c.idle_ms = 10;        // default to high performance
   c.wait_for_device = 1; // default to exit if device disconnected
-  c.wait_packets = 1024;   // default zero-byte attempts to disconnect (about 2 sec for default idle_ms)
+  c.wait_packets = 1024; // default zero-byte attempts to disconnect (about 2
+                         // sec for default idle_ms)
+  c.audio_enabled = 0;   // route M8 audio to default output
+  c.audio_buffer_size = 1024; // requested audio buffer size in samples
 
   c.key_up = SDL_SCANCODE_UP;
   c.key_left = SDL_SCANCODE_LEFT;
@@ -77,7 +82,7 @@
 
   SDL_Log("Writing config file to %s", config_path);
 
-  const unsigned int INI_LINE_COUNT = 40;
+  const unsigned int INI_LINE_COUNT = 43;
   const unsigned int LINELEN = 50;
 
   // Entries for the config file
@@ -91,7 +96,13 @@
   snprintf(ini_values[initPointer++], LINELEN, "idle_ms=%d\n", conf->idle_ms);
   snprintf(ini_values[initPointer++], LINELEN, "wait_for_device=%s\n",
            conf->wait_for_device ? "true" : "false");
-  snprintf(ini_values[initPointer++], LINELEN, "wait_packets=%d\n", conf->wait_packets);
+  snprintf(ini_values[initPointer++], LINELEN, "wait_packets=%d\n",
+           conf->wait_packets);
+  snprintf(ini_values[initPointer++], LINELEN, "[audio]\n");
+  snprintf(ini_values[initPointer++], LINELEN, "audio_enabled=%s\n",
+           conf->audio_enabled ? "true" : "false");
+  snprintf(ini_values[initPointer++], LINELEN, "audio_buffer_size=%d\n",
+           conf->audio_buffer_size);
   snprintf(ini_values[initPointer++], LINELEN, "[keyboard]\n");
   snprintf(ini_values[initPointer++], LINELEN, "key_up=%d\n", conf->key_up);
   snprintf(ini_values[initPointer++], LINELEN, "key_left=%d\n", conf->key_left);
@@ -190,6 +201,7 @@
     return;
   }
 
+  read_audio_config(ini, conf);
   read_graphics_config(ini, conf);
   read_key_config(ini, conf);
   read_gamepad_config(ini, conf);
@@ -199,6 +211,24 @@
 
   // Write any new default options after loading
   write_config(conf);
+}
+
+void read_audio_config(ini_t *ini, config_params_s *conf) {
+  const char *param_audio_enabled = ini_get(ini, "audio", "audio_enabled");
+  const char *param_audio_buffer_size =
+      ini_get(ini, "audio", "audio_buffer_size");
+
+  if (param_audio_enabled != NULL) {
+    if (strcmpci(param_audio_enabled, "true") == 0) {
+      conf->audio_enabled = 1;
+    } else {
+      conf->audio_enabled = 0;
+    }
+  }
+
+  if (param_audio_buffer_size != NULL) {
+    conf->audio_buffer_size = SDL_atoi(param_audio_buffer_size);
+  }
 }
 
 void read_graphics_config(ini_t *ini, config_params_s *conf) {
--- a/config.h
+++ b/config.h
@@ -13,6 +13,8 @@
   int idle_ms;
   int wait_for_device;
   int wait_packets;
+  int audio_enabled;
+  int audio_buffer_size;
 
   int key_up;
   int key_left;
@@ -53,9 +55,10 @@
 
 
 config_params_s init_config();
-void read_config();
+void read_config(config_params_s *conf);
+void read_audio_config(ini_t *config, config_params_s *conf);
 void read_graphics_config(ini_t *config, config_params_s *conf);
 void read_key_config(ini_t *config, config_params_s *conf);
 void read_gamepad_config(ini_t *config, config_params_s *conf);
 
-#endif
\ No newline at end of file
+#endif
--- a/main.c
+++ b/main.c
@@ -8,6 +8,7 @@
 #include <SDL.h>
 #include <signal.h>
 
+#include "audio.h"
 #include "command.h"
 #include "config.h"
 #include "input.h"
@@ -79,6 +80,13 @@
   // initial scan for (existing) game controllers
   initialize_game_controllers();
 
+  if (conf.audio_enabled == 1) {
+      if (audio_init(conf.audio_buffer_size) == 0){
+        SDL_Log("Cannot initialize audio, exiting.");
+        run = QUIT;
+      }
+  }
+
 #ifdef DEBUG_MSG
   SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
 #endif
@@ -249,6 +257,9 @@
 
   // exit, clean up
   SDL_Log("Shutting down\n");
+  if (conf.audio_enabled == 1){
+    audio_destroy();
+  }
   close_game_controllers();
   close_renderer();
   close_serial_port();
--