foxygit / doom Log in
commit 9301c9dbd937c910b98f2dd5178350aed573f3cd
Author:     Michael Day <contact@michaelcday.com>
AuthorDate: Sat Mar 11 14:50:11 2023 -0500
Commit:     Michael Day <contact@michaelcday.com>
CommitDate: Tue Mar 21 13:54:59 2023 -0400

    Introduce native FluidSynth support
---
 configure.ac    |  22 ++++-
 src/Makefile.am |   1 +
 src/i_flmusic.c | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/i_sound.c   |   3 +
 src/i_sound.h   |   1 +
 5 files changed, 291 insertions(+), 2 deletions(-)

diff --git a/configure.ac b/configure.ac
index 09175ee3..233aab93 100644
--- a/configure.ac
+++ b/configure.ac
@@ -138,10 +138,28 @@ AS_IF([test "x$with_libpng" != xno], [
     ])
 ])

+# Check for fluidsynth.
+AC_ARG_WITH([fluidsynth],
+AS_HELP_STRING([--without-fluidsynth],
+    [Build without fluidsynth @<:@default=check@:>@]),
+[],
+[
+    [with_fluidsynth=check]
+])
+AS_IF([test "x$with_fluidsynth" != xno], [
+    PKG_CHECK_MODULES(FLUIDSYNTH, fluidsynth >= 2.2.0, [
+        AC_DEFINE([HAVE_FLUIDSYNTH], [1], [fluidsynth installed])
+    ], [
+        AS_IF([test "x$with_fluidsynth" != xcheck], [AC_MSG_FAILURE(
+            [--with-fluidsynth was given, but test for fluidsynth failed])
+        ])
+    ])
+])
+
 # TODO: We currently link everything against libraries that don't need it.
 # Use the specific library CFLAGS/LIBS variables instead of setting them here.
-CFLAGS="$CFLAGS $SDL_CFLAGS ${SAMPLERATE_CFLAGS:-} ${PNG_CFLAGS:-}"
-LDFLAGS="$LDFLAGS $SDL_LIBS ${SAMPLERATE_LIBS:-} ${PNG_LIBS:-}"
+CFLAGS="$CFLAGS $SDL_CFLAGS ${SAMPLERATE_CFLAGS:-} ${PNG_CFLAGS:-} ${FLUIDSYNTH_CFLAGS:-}"
+LDFLAGS="$LDFLAGS $SDL_LIBS ${SAMPLERATE_LIBS:-} ${PNG_LIBS:-} ${FLUIDSYNTH_LIBS:-}"
 case "$host" in
   *-*-mingw* | *-*-cygwin* | *-*-msvc* )
     LDFLAGS="$LDFLAGS -lwinmm"
diff --git a/src/Makefile.am b/src/Makefile.am
index 18b2455f..06f4827e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -68,6 +68,7 @@ deh_str.c            deh_str.h             \
 gusconf.c            gusconf.h             \
 i_cdmus.c            i_cdmus.h             \
 i_endoom.c           i_endoom.h            \
+i_flmusic.c                                \
 i_glob.c             i_glob.h              \
 i_input.c            i_input.h             \
 i_joystick.c         i_joystick.h          \
diff --git a/src/i_flmusic.c b/src/i_flmusic.c
new file mode 100644
index 00000000..cd43bd04
--- /dev/null
+++ b/src/i_flmusic.c
@@ -0,0 +1,266 @@
+//
+// Copyright(C) 2022 Roman Fomin
+// Copyright(C) 2023 Michael Day
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//      FluidSynth backend
+
+#include "config.h"
+
+#if HAVE_FLUIDSYNTH
+
+#include "fluidsynth.h"
+
+#if (FLUIDSYNTH_VERSION_MAJOR < 2 ||                                           \
+     (FLUIDSYNTH_VERSION_MAJOR == 2 && FLUIDSYNTH_VERSION_MINOR < 2))
+
+typedef int fluid_int_t;
+typedef long fluid_long_long_t;
+
+#else
+
+typedef fluid_long_long_t fluid_int_t;
+
+#endif
+
+#include "SDL_mixer.h"
+
+#include "doomtype.h"
+#include "i_system.h"
+#include "i_sound.h"
+#include "m_misc.h"
+#include "memio.h"
+#include "mus2mid.h"
+#include "w_wad.h"
+#include "z_zone.h"
+#include "i_glob.h"
+
+boolean mus_chorus = true;
+boolean mus_reverb = true;
+
+static fluid_synth_t *synth = NULL;
+static fluid_settings_t *settings = NULL;
+static fluid_player_t *player = NULL;
+
+#define SOUNDFONTS_INITIAL_SIZE 16
+
+static void FL_Mix_Callback(void *udata, Uint8 *stream, int len)
+{
+    int result;
+
+    result = fluid_synth_write_s16(synth, len / 4, stream, 0, 2, stream, 1, 2);
+
+    if (result != FLUID_OK)
+    {
+        fprintf(stderr, "FL_Mix_Callback: Error generating FluidSynth audio");
+    }
+}
+
+static boolean I_FL_InitMusic(void)
+{
+    int sf_id;
+
+    if (strlen(fluidsynth_sf_path) == 0)
+    {
+        return false;
+    }
+
+    // quiet FluidSynth
+    fluid_set_log_function(FLUID_ERR, NULL, NULL);
+    fluid_set_log_function(FLUID_WARN, NULL, NULL);
+
+    settings = new_fluid_settings();
+
+    fluid_settings_setnum(settings, "synth.sample-rate", snd_samplerate);
+
+    fluid_settings_setint(settings, "synth.chorus.active", mus_chorus);
+    fluid_settings_setint(settings, "synth.reverb.active", mus_reverb);
+
+    if (mus_reverb)
+    {
+        fluid_settings_setnum(settings, "synth.reverb.room-size", 0.6);
+        fluid_settings_setnum(settings, "synth.reverb.damp", 0.4);
+        fluid_settings_setnum(settings, "synth.reverb.width", 4);
+        fluid_settings_setnum(settings, "synth.reverb.level", 0.15);
+    }
+
+    if (mus_chorus)
+    {
+        fluid_settings_setnum(settings, "synth.chorus.level", 0.35);
+        fluid_settings_setnum(settings, "synth.chorus.depth", 5);
+    }
+
+    synth = new_fluid_synth(settings);
+
+    sf_id = fluid_synth_sfload(synth, fluidsynth_sf_path, true);
+    if (sf_id == FLUID_FAILED)
+    {
+        // deleting the synth also deletes sfloader
+        delete_fluid_synth(synth);
+        delete_fluid_settings(settings);
+        fprintf(stderr,
+                "I_FL_InitMusic: Error loading FluidSynth soundfont: '%s'\n",
+                fluidsynth_sf_path);
+        return false;
+    }
+
+    printf("I_FL_InitMusic: Using '%s'\n", fluidsynth_sf_path);
+
+    return true;
+}
+
+static void I_FL_SetMusicVolume(int volume)
+{
+    // FluidSynth's default is 0.2. Make 1.0 the maximum.
+    // 0 -- 0.2 -- 10.0
+    fluid_synth_set_gain(synth, (float) volume / 127);
+}
+
+static void I_FL_PauseSong(void)
+{
+    fluid_player_stop(player);
+}
+
+static void I_FL_ResumeSong(void)
+{
+    fluid_player_play(player);
+}
+
+static void I_FL_PlaySong(void *handle, boolean looping)
+{
+    fluid_player_set_loop(player, looping ? -1 : 1);
+    fluid_player_play(player);
+}
+
+static void I_FL_StopSong(void)
+{
+    if (player)
+    {
+        fluid_player_stop(player);
+    }
+}
+
+// Determine whether memory block is a .mid file
+
+static boolean IsMid(byte *mem, int len)
+{
+    return len > 4 && !memcmp(mem, "MThd", 4);
+}
+
+static void *I_FL_RegisterSong(void *data, int len)
+{
+    int result = 0;
+
+    player = new_fluid_player(synth);
+
+    if (IsMid(data, len))
+    {
+        result = fluid_player_add_mem(player, data, len);
+    }
+    else
+    {
+        // Assume a MUS file and try to convert
+        MEMFILE *instream;
+        MEMFILE *outstream;
+        void *outbuf;
+        size_t outbuf_len;
+
+        instream = mem_fopen_read(data, len);
+        outstream = mem_fopen_write();
+
+        if (mus2mid(instream, outstream) == 0)
+        {
+            mem_get_buf(outstream, &outbuf, &outbuf_len);
+            result = fluid_player_add_mem(player, outbuf, outbuf_len);
+        }
+
+        mem_fclose(instream);
+        mem_fclose(outstream);
+    }
+
+    if (result != FLUID_OK)
+    {
+        fprintf(stderr,
+                "I_FL_RegisterSong: FluidSynth failed to load in-memory song");
+        return NULL;
+    }
+
+    Mix_HookMusic(FL_Mix_Callback, NULL);
+    return player;
+}
+
+static void I_FL_UnRegisterSong(void *handle)
+{
+    if (player)
+    {
+        fluid_synth_program_reset(synth);
+        fluid_synth_system_reset(synth);
+
+        Mix_HookMusic(NULL, NULL);
+
+        delete_fluid_player(player);
+        player = NULL;
+    }
+}
+
+static void I_FL_ShutdownMusic(void)
+{
+    I_FL_StopSong();
+    I_FL_UnRegisterSong(NULL);
+
+    if (synth)
+    {
+        delete_fluid_synth(synth);
+        synth = NULL;
+    }
+
+    if (settings)
+    {
+        delete_fluid_settings(settings);
+        settings = NULL;
+    }
+}
+
+static boolean I_FL_MusicIsPlaying(void)
+{
+    if (player == NULL)
+    {
+        return false;
+    }
+
+    return (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING);
+}
+
+static snddevice_t music_fl_devices[] =
+{
+    SNDDEVICE_GENMIDI,
+};
+
+music_module_t music_fl_module =
+{
+    music_fl_devices,
+    arrlen(music_fl_devices),
+    I_FL_InitMusic,
+    I_FL_ShutdownMusic,
+    I_FL_SetMusicVolume,
+    I_FL_PauseSong,
+    I_FL_ResumeSong,
+    I_FL_RegisterSong,
+    I_FL_UnRegisterSong,
+    I_FL_PlaySong,
+    I_FL_StopSong,
+    I_FL_MusicIsPlaying,
+    NULL // Poll
+};
+
+#endif
diff --git a/src/i_sound.c b/src/i_sound.c
index 7d2819bd..6232772b 100644
--- a/src/i_sound.c
+++ b/src/i_sound.c
@@ -94,6 +94,9 @@ static music_module_t *music_modules[] =
 #ifdef _WIN32
     &music_win_module,
 #endif
+#if HAVE_FLUIDSYNTH
+    &music_fl_module,
+#endif // HAVE_FLUIDSYNTH
 #ifndef DISABLE_SDL2MIXER
     &music_sdl_module,
 #endif // DISABLE_SDL2MIXER
diff --git a/src/i_sound.h b/src/i_sound.h
index a056a47c..d4bc9404 100644
--- a/src/i_sound.h
+++ b/src/i_sound.h
@@ -259,6 +259,7 @@ extern music_module_t music_sdl_module;
 extern music_module_t music_opl_module;
 extern music_module_t music_pack_module;
 extern music_module_t music_win_module;
+extern music_module_t music_fl_module;

 // For OPL module: