diff options
author | kcgen <kcgen@users.noreply.github.com> | 2022-01-14 21:31:25 +0300 |
---|---|---|
committer | kcgen <kcgen@users.noreply.github.com> | 2022-06-02 08:54:09 +0300 |
commit | c58b14397a6106a189b0bd9b8f71d9500f0cf332 (patch) | |
tree | 859218c89bc5ea5619a438f751cb0b5598c29962 | |
parent | 6f98d75bded8a52148dd48f2bcbbca714af9e024 (diff) |
Support layered soundfontskc/mutli-sf2-1
-rw-r--r-- | src/midi/midi_fluidsynth.cpp | 125 | ||||
-rw-r--r-- | src/midi/midi_fluidsynth.h | 4 |
2 files changed, 99 insertions, 30 deletions
diff --git a/src/midi/midi_fluidsynth.cpp b/src/midi/midi_fluidsynth.cpp index fc6c60e24..41dad91a8 100644 --- a/src/midi/midi_fluidsynth.cpp +++ b/src/midi/midi_fluidsynth.cpp @@ -25,7 +25,7 @@ #if C_FLUIDSYNTH #include <cassert> -#include <deque> +#include <iterator> #include <string> #include <tuple> @@ -56,6 +56,8 @@ static void init_fluid_dosbox_settings(Section_prop &secprop) "absolute or relative path, or the name of an .sf2 inside\n" "the 'soundfonts' directory within your DOSBox configuration\n" "directory.\n" + "Multiple soundfonts can be loaded if comma-separated.\n" + "For example: piano.sf2, brass.sf2, drums.sf2\n" "An optional percentage will scale the SoundFont's volume.\n" "For example: 'soundfont.sf2 50' will attenuate it by 50 percent.\n" "The scaling percentage can range from 1 to 500."); @@ -189,8 +191,11 @@ static std::deque<std::string> get_data_dirs() #endif -static std::string find_sf_file(const std::string &name) +// Return the file path to the given soundfont name. +// If the soundfont isn't found, then an empty string is returned. +static std::string find_sf_file(std::string name) { + trim(name); const std::string sf_path = CROSS_ResolveHome(name); if (path_exists(sf_path)) return sf_path; @@ -201,14 +206,51 @@ static std::string find_sf_file(const std::string &name) return sf; } } + LOG_MSG("MIDI: Failed to find '%s' soundfont, will skip it.", name.c_str()); return ""; } +// Return a deque of existing soundfont paths from the comma-separated list of +// names +static std::deque<std::string> find_sf_files(const std::string &names) +{ + const auto separator = ','; + size_t start = 0; + auto end = names.find(separator); + + std::deque<std::string> sf_files; + while (end != std::string::npos) { + const auto name = find_sf_file(names.substr(start, end - start)); + if (!name.empty()) + sf_files.emplace_back(name); + + start = end + 1; + end = names.find(separator, start); + } + const auto name = find_sf_file(names.substr(start, end)); + if (!name.empty()) + sf_files.emplace_back(name); + return sf_files; +} + MidiHandlerFluidsynth::MidiHandlerFluidsynth() : soft_limiter("FSYNTH"), keep_rendering(false) {} +// Constructs a comma-separated string from the deque of soundfont paths +static std::string soundfonts_to_csv(const std::deque<std::string> &soundfonts) +{ + std::string csv; + for (auto it = soundfonts.begin(); it != soundfonts.end(); ++it) { + csv += *it; + if (std::next(it) != soundfonts.end()) { + csv += ", "; + } + } + return csv; +} + bool MidiHandlerFluidsynth::Open([[maybe_unused]] const char *conf) { Close(); @@ -252,18 +294,30 @@ bool MidiHandlerFluidsynth::Open([[maybe_unused]] const char *conf) // Load the requested SoundFont or quit if none provided auto *section = static_cast<Section_prop *>(control->GetSection("fluidsynth")); const auto sf_spec = parse_sf_pref(section->Get_string("soundfont"), 100); - const auto soundfont = find_sf_file(std::get<std::string>(sf_spec)); - auto scale_by_percent = std::get<int>(sf_spec); - if (!soundfont.empty() && fluid_synth_sfcount(fluid_synth.get()) == 0) { + // Round up the set of soundfonts that actually exist + const auto soundfonts_pref = std::get<std::string>(sf_spec); + const auto soundfonts = find_sf_files(soundfonts_pref); + + // Confirm fluidsynth is empty before starting + int num_loaded = fluid_synth_sfcount(fluid_synth.get()); + assert(num_loaded == 0); // fluidsynth should always be empty at this point + + // Try loading the soundfonts + for (const auto &soundfont : soundfonts) fluid_synth_sfload(fluid_synth.get(), soundfont.data(), true); - } - if (fluid_synth_sfcount(fluid_synth.get()) == 0) { - LOG_MSG("MIDI: FluidSynth failed to load '%s', check the path.", - soundfont.c_str()); + + // Did we load the soundfonts? + num_loaded = fluid_synth_sfcount(fluid_synth.get()); + const auto loaded_names = soundfonts_to_csv(soundfonts); + if (!num_loaded || num_loaded != static_cast<int>(soundfonts.size())) { + LOG_MSG("MIDI: FluidSynth failed to load '%s' soundfont(s), check the path(s).", + loaded_names.c_str()); return false; } + // Check if the user provided a scaling parameter for the collection of soundfonts + auto scale_by_percent = std::get<int>(sf_spec); if (scale_by_percent < 1 || scale_by_percent > 500) { LOG_MSG("MIDI: FluidSynth invalid scaling of %d%% provided; resetting to 100%%", scale_by_percent); @@ -274,10 +328,11 @@ bool MidiHandlerFluidsynth::Open([[maybe_unused]] const char *conf) // Let the user know that the SoundFont was loaded if (scale_by_percent == 100) - LOG_MSG("MIDI: Using SoundFont '%s'", soundfont.c_str()); + LOG_MSG("MIDI: Loaded %d soundfont(s): %s", num_loaded, + loaded_names.c_str()); else - LOG_MSG("MIDI: Using SoundFont '%s' with voices scaled by %d%%", - soundfont.c_str(), scale_by_percent); + LOG_MSG("MIDI: Loaded %d Soundfont(s): %s with voices scaled by %d%%", + num_loaded, loaded_names.c_str(), scale_by_percent); constexpr int fx_group = -1; // applies setting to all groups @@ -303,13 +358,16 @@ bool MidiHandlerFluidsynth::Open([[maybe_unused]] const char *conf) const auto chorus = split(section->Get_string("chorus")); bool chorus_enabled = !chorus.empty() && chorus[0] != "off"; - // does the soundfont have known-issues with chorus? - const auto is_problematic_font = find_in_case_insensitive("FluidR3", soundfont) || - find_in_case_insensitive("zdoom", soundfont); - if (chorus_enabled && chorus[0] == "auto" && is_problematic_font) { - chorus_enabled = false; - LOG_INFO("MIDI: Chorus auto-disabled due to known issues with the %s soundfont", - soundfont.c_str()); + // do any of the loaded soundfonts have known-issues with chorus? + for (const auto &soundfont : soundfonts) { + const auto is_problematic_font = + find_in_case_insensitive("FluidR3", soundfont) || + find_in_case_insensitive("zdoom", soundfont); + if (chorus_enabled && chorus[0] == "auto" && is_problematic_font) { + chorus_enabled = false; + LOG_INFO("MIDI: Chorus auto-disabled due to known issues with the %s soundfont", + soundfont.c_str()); + } } // default chorus settings @@ -402,7 +460,7 @@ bool MidiHandlerFluidsynth::Open([[maybe_unused]] const char *conf) settings = std::move(fluid_settings); synth = std::move(fluid_synth); channel = std::move(mixer_channel); - selected_font = soundfont; + selected_fonts = soundfonts; // Start rendering audio keep_rendering = true; @@ -450,7 +508,7 @@ void MidiHandlerFluidsynth::Close() settings.reset(); soft_limiter.Reset(); last_played_frame = 0; - selected_font = ""; + selected_fonts = {}; is_open = false; } @@ -577,11 +635,19 @@ std::string format_sf2_line(size_t width, const std::string &name, const std::st return line; } +bool MidiHandlerFluidsynth::IsSelected(const std::string &needle) +{ + return (std::find(selected_fonts.begin(), selected_fonts.end(), needle) != + selected_fonts.end()); +} + MIDI_RC MidiHandlerFluidsynth::ListAll(Program *caller) { auto *section = static_cast<Section_prop *>(control->GetSection("fluidsynth")); const auto sf_spec = parse_sf_pref(section->Get_string("soundfont"), 100); - const auto sf_name = std::get<std::string>(sf_spec); + const auto sf_names = std::get<std::string>(sf_spec); + const auto soundfonts = find_sf_files(sf_names); + const size_t term_width = INT10_GetTextColumns(); auto write_line = [caller](bool highlight, const std::string &line) { @@ -593,11 +659,13 @@ MIDI_RC MidiHandlerFluidsynth::ListAll(Program *caller) caller->WriteOut(" %s\n", line.c_str()); }; - // If selected soundfont exists in the current working directory, - // then print it. - const std::string sf_path = CROSS_ResolveHome(sf_name); - if (path_exists(sf_path)) { - write_line((sf_path == selected_font), sf_name); + // Print the selected soundfont(s) if they exist in the current working + // directory + for (const auto &sf_name : soundfonts) { + const std::string sf_path = CROSS_ResolveHome(sf_name); + if (path_exists(sf_path)) { + write_line(IsSelected(sf_path), sf_name); + } } // Go through all soundfont directories and list all .sf2 files. @@ -625,8 +693,7 @@ MIDI_RC MidiHandlerFluidsynth::ListAll(Program *caller) const auto line = format_sf2_line(term_width - 2, dir_entry_name, font_path); - const bool highlight = is_open && - (selected_font == font_path); + const bool highlight = is_open && IsSelected(font_path); write_line(highlight, line); diff --git a/src/midi/midi_fluidsynth.h b/src/midi/midi_fluidsynth.h index b9d7dd42f..2b5f43cb3 100644 --- a/src/midi/midi_fluidsynth.h +++ b/src/midi/midi_fluidsynth.h @@ -28,6 +28,7 @@ #if C_FLUIDSYNTH #include <atomic> +#include <deque> #include <memory> #include <vector> #include <fluidsynth.h> @@ -53,6 +54,7 @@ private: void MixerCallBack(uint16_t requested_frames); void SetMixerLevel(const AudioFrame &levels) noexcept; uint16_t GetRemainingFrames(); + bool IsSelected(const std::string &needle); void Render(); using fluid_settings_ptr_t = @@ -62,7 +64,7 @@ private: fluid_settings_ptr_t settings{nullptr, &delete_fluid_settings}; fsynth_ptr_t synth{nullptr, &delete_fluid_synth}; mixer_channel_t channel = nullptr; - std::string selected_font = ""; + std::deque<std::string> selected_fonts = {}; std::vector<int16_t> play_buffer = {}; static constexpr auto num_buffers = 8; |