From b886e6a5859fdd3e4dbfb883c72c4d97fdeda97b Mon Sep 17 00:00:00 2001 From: Zachary Hall Date: Wed, 11 Dec 2024 12:56:17 -0800 Subject: [PATCH] Add Fluidsynth backend and stop using SDL Mixer X for that. --- CMakeLists.txt | 7 +- .../fluidsynth/fluidsynth_backend.cpp | 136 ++++++++++++++++-- .../fluidsynth/fluidsynth_backend.hpp | 103 ++++++++++++- backends/ui/imgui/main.cpp | 67 +++++++++ backends/ui/imgui/main.h | 2 + subprojects/fmt | 2 +- subprojects/googletest | 2 +- util.hpp | 9 +- 8 files changed, 306 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dcd5594..526fcfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,14 +17,14 @@ set(SDL_MIXER_X_SHARED OFF CACHE BOOL "") set(DOWNLOAD_AUDIO_CODECS_DEPENDENCY OFF CACHE BOOL "") set(AUDIO_CODECS_BUILD_LOCAL_SDL2 OFF CACHE BOOL "" FORCE) set(MIXERX_LGPL ON CACHE BOOL "" FORCE) -set(USE_MIDI ON CACHE BOOL "" FORCE) +set(USE_MIDI OFF CACHE BOOL "" FORCE) set(USE_MIDI_NATIVE_ALT OFF CACHE BOOL "" FORCE) set(USE_MIDI_NATIVE OFF CACHE BOOL "" FORCE) set(USE_MIDI_TIMIDITY OFF CACHE BOOL "" FORCE) set(USE_MIDI_FLUIDLITE OFF CACHE BOOL "" FORCE) set(USE_MIDI_OPNMIDI OFF CACHE BOOL "" FORCE) set(USE_MIDI_ADLMIDI OFF CACHE BOOL "" FORCE) -set(USE_MIDI_FLUIDSYNTH ON CACHE BOOL "" FORCE) +set(USE_MIDI_FLUIDSYNTH OFF CACHE BOOL "" FORCE) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) set(BUILD_FB2K OFF CACHE BOOL "" FORCE) @@ -406,7 +406,7 @@ else() target_include_directories(SDL2::SDL2 INTERFACE /boot/system/develop/headers/SDL2 ${sdl2_INCLUDE_DIRS}) target_link_libraries(SDL2::SDL2 INTERFACE ${sdl2_LIBRARIES}) else() - find_package(SDL2 REQUIRED) + find_package(SDL2 REQUIRED) endif() endif() if (NOT BUILD_SDL_IMAGE) @@ -524,6 +524,7 @@ endif() playback_backend_subdir(NAME "VGMSTREAM" READABLE_NAME "VgmStream" SUBDIR backends/playback/vgmstream) playback_backend_subdir(NAME "SDL_MIXER_X" READABLE_NAME "SDL Mixer X" SUBDIR backends/playback/sdl_mixer_x) playback_backend_subdir(NAME "ZSM" READABLE_NAME "ZSM" SUBDIR backends/playback/zsm) +playback_backend_subdir(NAME "FLUIDSYNTH" READABLE_NAME "Fluidsynth" SUBDIR backends/playback/fluidsynth) execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen_ui_backend_inc.py ${CMAKE_CURRENT_BINARY_DIR} --ui ${ENABLED_UIS} --playback ${ENABLED_PLAYBACK_BACKENDS}) prefix_all(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ main.cpp daemon_backend.cpp daemon_backend.hpp proxy_backend.cpp proxy_backend.hpp) list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/backend_glue.cpp) diff --git a/backends/playback/fluidsynth/fluidsynth_backend.cpp b/backends/playback/fluidsynth/fluidsynth_backend.cpp index c33d3d9..2266312 100644 --- a/backends/playback/fluidsynth/fluidsynth_backend.cpp +++ b/backends/playback/fluidsynth/fluidsynth_backend.cpp @@ -8,6 +8,9 @@ #include #include #include +#include +#include +namespace fs = std::filesystem; void FluidSynthBackend::fluidsynth_get_property_list_wrapper(void *udata, const char *name, int type) { ((FluidSynthBackend*)udata)->fluidsynth_get_property_list(name, type); } @@ -16,15 +19,31 @@ void FluidSynthBackend::fluidsynth_get_property_list(const char *name, int type) property.set_path(fmt::format("fluidsynth/{0}", name)); property.set_name(name); switch (type) { + case FLUID_NO_TYPE: case FLUID_NUM_TYPE: { property.set_type(PropertyType::Double); + double min, max; + if (fluid_settings_getnum_range(settings, name, &min, &max) == FLUID_OK) { + auto *range = property.mutable_hint()->mutable_range(); + range->set_min(min); + range->set_max(max); + } } break; case FLUID_INT_TYPE: { property.set_type(PropertyType::Int); + int min, max; + if (fluid_settings_getint_range(settings, name, &min, &max) == FLUID_OK) { + auto *range = property.mutable_hint()->mutable_range(); + range->set_min(min); + range->set_max(max); + } } break; case FLUID_STR_TYPE: { property.set_type(PropertyType::String); } break; + default: { + throw std::exception(); + } break; } property.set_id(PropertyId::BackendSpecific); fluidsynth_properties.push_back(property); @@ -38,31 +57,126 @@ void FluidSynthBackend::load(const char *filename) { spec.format = AUDIO_F32SYS; spec.samples = 100; spec.channels = 2; - spec.freq = 48000; + spec.freq = 96000; spec.size = 100 * 2 * sizeof(int16_t); - file = open_file(filename); - this->settings = new_fluid_settings(); + File *file = open_file(filename); + settings = new_fluid_settings(); + fluid_settings_setnum(settings, "synth.sample-rate", 96000.0); fluid_settings_foreach(settings, (void*)this, &fluidsynth_get_property_list_wrapper); + synth = new_fluid_synth(settings); + player = new_fluid_player(synth); + fs::path fpath(filename); + fpath = fpath.parent_path() / fpath.stem(); + + std::string fpath_str = fpath.string(); + std::vector try_paths = std::vector({ + fpath_str + ".sf2", fpath_str + ".dls" + }); + bool found = false; + for (auto &path : try_paths) { + const char *path_c = path.c_str(); + if (fluid_is_soundfont(path_c)) { + fluid_synth_sfload(synth, path_c, 1); + found = true; + break; + } + } + if (!found) { + WARNING.writeln("Could not find a valid companion file to use as a sound font. Make sure the extension is all-lowercase, if you're on a case sensitive filesystem (Such as on Linux)."); + throw std::exception(); + } + file_data = malloc(file->get_len()); + file_len = file->read(file_data, 1, file->get_len()); + delete file; + file_data = realloc(file_data, file_len); + fluid_player_add_mem(player, file_data, file_len); } extern SDL_AudioSpec obtained; void FluidSynthBackend::switch_stream(int idx) { + fluid_player_play(player); } void FluidSynthBackend::cleanup() { - delete file; - file = nullptr; + delete_fluid_player(player); + player = nullptr; + delete_fluid_synth(synth); + synth = nullptr; + delete_fluid_settings(settings); + settings = nullptr; + file_len = 0; + free(file_data); + file_data = nullptr; } size_t FluidSynthBackend::render(void *buf, size_t maxlen) { - size_t sample_type_len = 2; - maxlen /= sample_type_len; - maxlen *= sample_type_len; - return copied; + size_t sample_type_len = sizeof(float); + maxlen /= sample_type_len * 2; + maxlen *= sample_type_len * 2; + if (fluid_synth_write_float(synth, maxlen / 2 / sample_type_len, buf, 0, 2, buf, 1, 2) == FLUID_FAILED) { + return 0; + } + return maxlen; +} +bool FluidSynthBackend::is_fluidsynth_setting(std::string path) { + const char *prefix = "fluidsynth/"; + if (path.length() < strlen(prefix)) return false; + return path.substr(0, strlen(prefix)) == std::string(prefix); } void FluidSynthBackend::seek(double position) { - + fluid_player_seek(player, position); } double FluidSynthBackend::get_position() { - return position; + return fluid_player_get_current_tick(player); } int FluidSynthBackend::get_stream_idx() { return 0; +} +void FluidSynthBackend::set_fluidsynth_property_str(std::string path, std::string val) { + fluid_settings_setstr(settings, path.c_str(), val.c_str()); +} +void FluidSynthBackend::set_fluidsynth_property_num(std::string path, double val) { + fluid_settings_setnum(settings, path.c_str(), val); +} +void FluidSynthBackend::set_fluidsynth_property_int(std::string path, int val) { + fluid_settings_setint(settings, path.c_str(), val); +} +std::optional FluidSynthBackend::get_fluidsynth_property_str(std::string path) { + char *tmp; + if (fluid_settings_dupstr(settings, path.c_str(), &tmp) == FLUID_OK) { + std::string output = tmp; + free((void*)tmp); + return output; + } else { + return {}; + } +} +std::optional FluidSynthBackend::get_fluidsynth_property_num(std::string path) { + double output = NAN; + if (fluid_settings_getnum(settings, path.c_str(), &output) == FLUID_OK) return output; + else return {}; +} +std::optional FluidSynthBackend::get_fluidsynth_property_int(std::string path) { + int output = 0; + if (fluid_settings_getint(settings, path.c_str(), &output) == FLUID_OK) return output; + else return {}; +} +void FluidSynthBackend::reset_fluidsynth_property(std::string path) { + switch (fluid_settings_get_type(settings, path.c_str())) { + case FLUID_INT_TYPE: { + int output; + if (fluid_settings_getint_default(settings, path.c_str(), &output) == FLUID_OK) { + fluid_settings_setint(settings, path.c_str(), output); + } + } break; + case FLUID_STR_TYPE: { + char *val; + if (fluid_settings_getstr_default(settings, path.c_str(), &val) == FLUID_OK) { + fluid_settings_setstr(settings, path.c_str(), val); + } + } break; + case FLUID_NUM_TYPE: { + double val; + if (fluid_settings_getnum_default(settings, path.c_str(), &val) == FLUID_OK) { + fluid_settings_setnum(settings, path.c_str(), val); + } + } break; + } } \ No newline at end of file diff --git a/backends/playback/fluidsynth/fluidsynth_backend.hpp b/backends/playback/fluidsynth/fluidsynth_backend.hpp index 5da04f1..bf88f5d 100644 --- a/backends/playback/fluidsynth/fluidsynth_backend.hpp +++ b/backends/playback/fluidsynth/fluidsynth_backend.hpp @@ -13,18 +13,111 @@ #include "file_backend.hpp" #include class FluidSynthBackend : public PlaybackBackend { - File *file; + void *file_data; + size_t file_len; static void fluidsynth_get_property_list_wrapper(void *udata, const char *name, int type); std::vector fluidsynth_properties; fluid_settings_t *settings; + fluid_synth_t *synth; + fluid_player_t *player; void fluidsynth_get_property_list(const char *name, int type); public: void set_fluidsynth_property_str(std::string path, std::string val); void set_fluidsynth_property_num(std::string path, double val); void set_fluidsynth_property_int(std::string path, int val); - std::string get_fluidsynth_property_str(std::string path); - double get_fluidsynth_property_num(std::string path); - int get_fluidsynth_property_int(std::string path); + std::optional get_fluidsynth_property_str(std::string path); + std::optional get_fluidsynth_property_num(std::string path); + std::optional get_fluidsynth_property_int(std::string path); + void reset_fluidsynth_property(std::string path); + bool is_fluidsynth_setting(std::string path); + inline std::optional get_property_string(google::protobuf::Any value) { + try { + StringProperty * property = resolve_any(value); + std::string output = property->value(); + delete property; + return output; + } catch (std::exception e) { + } + return {}; + } + inline std::optional get_property_double(google::protobuf::Any value) { + try { + DoubleProperty *property = resolve_any(value); + double output = property->value(); + delete property; + return output; + } catch (std::exception e) { + } + return {}; + } + inline std::optional get_property_int(google::protobuf::Any value) { + try { + IntProperty *property = resolve_any(value); + int output = property->value(); + delete property; + return output; + } catch (std::exception e) { + } + return {}; + } + inline bool set(std::string path, google::protobuf::Any value) override { + if (!is_fluidsynth_setting(path)) { + return PlaybackBackend::set(path, value); + } + std::string fluidsynth_path = path.substr(strlen("fluidsynth/")); + auto maybe_num = get_property_double(value); + if (maybe_num.has_value()) { + set_fluidsynth_property_num(fluidsynth_path, maybe_num.value()); + return true; + } + auto maybe_int = get_property_int(value); + if (maybe_int.has_value()) { + set_fluidsynth_property_int(fluidsynth_path, maybe_int.value()); + return true; + } + auto maybe_string = get_property_string(value); + if (maybe_string.has_value()) { + set_fluidsynth_property_str(fluidsynth_path, maybe_string.value()); + return true; + } + return false; + } + inline std::optional get(std::string path) override { + if (!is_fluidsynth_setting(path)) return PlaybackBackend::get(path); + std::string fluidsynth_path = path.substr(strlen("fluidsynth/")); + google::protobuf::Any output; + switch (fluid_settings_get_type(settings, fluidsynth_path.c_str())) { + case FLUID_INT_TYPE: { + IntProperty prop; + auto val = get_fluidsynth_property_int(fluidsynth_path); + if (!val.has_value()) return {}; + prop.set_value(val.value()); + output.PackFrom(prop); + } break; + case FLUID_STR_TYPE: { + StringProperty prop; + auto val = get_fluidsynth_property_str(fluidsynth_path); + if (!val.has_value()) return {}; + prop.set_value(val.value()); + output.PackFrom(prop); + } break; + case FLUID_NUM_TYPE: { + DoubleProperty prop; + auto val = get_fluidsynth_property_num(fluidsynth_path); + if (!val.has_value()) return {}; + prop.set_value(val.value()); + output.PackFrom(prop); + } break; + default: return {}; + } + return output; + } + inline std::optional reset(std::string path) override { + if (!is_fluidsynth_setting(path)) return PlaybackBackend::reset(path); + std::string fluidsynth_path = path.substr(strlen("fluidsynth/")); + reset_fluidsynth_property(fluidsynth_path); + return get(path); + } inline std::string get_id() override { return "fluidsynth"; } @@ -40,7 +133,7 @@ class FluidSynthBackend : public PlaybackBackend { size_t render(void *buf, size_t maxlen) override; double get_position() override; inline double get_length() override { - return length; + return 0.0; } inline ~FluidSynthBackend() override { } }; diff --git a/backends/ui/imgui/main.cpp b/backends/ui/imgui/main.cpp index 08d430d..8a04cbd 100644 --- a/backends/ui/imgui/main.cpp +++ b/backends/ui/imgui/main.cpp @@ -7,6 +7,7 @@ #include "imgui/imgui.h" #include "ui_backend.hpp" #include "thirdparty/CLI11.hpp" +#include "imgui/misc/cpp/imgui_stdlib.h" #include using namespace Looper::Options; void MainLoop::Init() { @@ -312,6 +313,58 @@ void MainLoop::GuiFunction() { ImGui::PushID(property.path().c_str()); bool valid = false; switch (property.type()) { + case PropertyType::String: { + std::string value = ""; + if (string_properties.contains(property.path())) { + value = string_properties[property.path()]; + } else { + auto value_to_resolve = playback->get_property(property.path()); + if (value_to_resolve.has_value()) { + value = resolve_value(value_to_resolve.value()); + } + string_properties[property.path()] = value; + } + ImGui::InputText(property.path().c_str(), &value); + string_properties[property.path()] = value; + valid = true; + } break; + case PropertyType::Int: { + std::optional min; + std::optional max; + int value = 0; + if (int_properties.contains(property.path())) { + value = int_properties[property.path()]; + } else { + auto value_to_resolve = playback->get_property(property.path()); + if (value_to_resolve.has_value()) { + value = resolve_value(value_to_resolve.value()); + } + int_properties[property.path()] = value; + } + if (property.has_hint() && property.hint().has_range()) { + auto range = property.hint().range(); + if (range.has_min() && range.has_max()) { + if (ImGui::SliderInt(property.path().c_str(), &value, (int)range.min(), (int)range.max())) { + int_properties[property.path()] = value; + } + valid = true; + } else { + if (range.has_min()) min = range.min(); + if (range.has_max()) max = range.max(); + } + } + if (!valid) { + ImGui::InputInt(property.path().c_str(), &value); + if (min.has_value() && value < min) { + value = min.value(); + } + if (max.has_value() && value > max) { + value = max.value(); + } + int_properties[property.path()] = value; + valid = true; + } + } break; case PropertyType::Double: { std::optional min; std::optional max; @@ -371,6 +424,20 @@ void MainLoop::GuiFunction() { ImGui::SameLine(); if (ImGui::Button("Set")) { switch (property.type()) { + case PropertyType::String: { + StringProperty property_s; + property_s.set_value(string_properties[property.path()]); + google::protobuf::Any value; + value.PackFrom(property_s); + playback->set_property(property.path(), value); + } break; + case PropertyType::Int: { + IntProperty property_i; + property_i.set_value(int_properties[property.path()]); + google::protobuf::Any value; + value.PackFrom(property_i); + playback->set_property(property.path(), value); + } break; case PropertyType::Double: { DoubleProperty property_d; property_d.set_value(double_properties[property.path()]); diff --git a/backends/ui/imgui/main.h b/backends/ui/imgui/main.h index 5f68451..8a0d2bd 100644 --- a/backends/ui/imgui/main.h +++ b/backends/ui/imgui/main.h @@ -55,6 +55,8 @@ class MainLoop : public RendererBackend { std::atomic_bool exit_flag; std::map boolean_properties; std::map double_properties; + std::map int_properties; + std::map string_properties; std::vector properties; std::vector streams; public: diff --git a/subprojects/fmt b/subprojects/fmt index 720da57..0379bf3 160000 --- a/subprojects/fmt +++ b/subprojects/fmt @@ -1 +1 @@ -Subproject commit 720da57baba83b3b1829e20133575e57aa1a8a4f +Subproject commit 0379bf3a5d52d8542aec1874677c9df5ff9ba5f9 diff --git a/subprojects/googletest b/subprojects/googletest index d144031..6dae7eb 160000 --- a/subprojects/googletest +++ b/subprojects/googletest @@ -1 +1 @@ -Subproject commit d144031940543e15423a25ae5a8a74141044862f +Subproject commit 6dae7eb4a5c3a169f3e298392bff4680224aa94a diff --git a/util.hpp b/util.hpp index 2eadba1..6bc3f10 100644 --- a/util.hpp +++ b/util.hpp @@ -519,6 +519,13 @@ inline std::string resolve_value(google::protobuf::Any value) { return output; } template<> +inline int resolve_value(google::protobuf::Any value) { + IntProperty *property = resolve_any(value); + int output = property->value(); + delete property; + return output; +} +template<> inline DynPtr resolve_value(google::protobuf::Any value) { BytesProperty *property = resolve_any(value); std::string output = property->value(); @@ -527,7 +534,7 @@ inline DynPtr resolve_value(google::protobuf::Any value) { } template<> -inline double resolve_value(google::protobuf::Any value) { +inline double resolve_value(google::protobuf::Any value) { DoubleProperty *property = resolve_any(value); double output = property->value(); delete property;