#include "fluidsynth_backend.hpp" #include #include #include #include #include "file_backend.hpp" #include #include #include #include #include #include #include namespace fs = std::filesystem; using Looper::Log::LogStream; static void log_fn(int level, const char *msg, void *data) { LogStream *stream; switch (level) { case FLUID_ERR: case FLUID_PANIC: stream = &ERROR; break; case FLUID_WARN: stream = &WARNING; break; default: case FLUID_INFO: stream = &INFO; break; case FLUID_DBG: stream = &DEBUG; break; } stream->writeln(msg); } void FluidSynthBackend::fluidsynth_get_property_list_wrapper(void *udata, const char *name, int type) { ((FluidSynthBackend*)udata)->fluidsynth_get_property_list(name, type); } void FluidSynthBackend::fluidsynth_get_property_list(const char *name, int type) { Property property; 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); } static bool log_fn_registered = false; std::vector FluidSynthBackend::get_property_list() { return fluidsynth_properties; } void FluidSynthBackend::load(const char *filename) { memset(&spec, 0, sizeof(spec)); if (!log_fn_registered) { fluid_set_log_function(FLUID_PANIC, &log_fn, nullptr); fluid_set_log_function(FLUID_ERR, &log_fn, nullptr); fluid_set_log_function(FLUID_WARN, &log_fn, nullptr); fluid_set_log_function(FLUID_INFO, &log_fn, nullptr); fluid_set_log_function(FLUID_DBG, &log_fn, nullptr); log_fn_registered = true; } current_file = filename; spec.format = AUDIO_F32SYS; spec.samples = 100; spec.channels = 2; spec.freq = 96000; spec.size = 100 * 2 * sizeof(int16_t); File *file = open_file(filename); settings = new_fluid_settings(); fluid_settings_setnum(settings, "synth.sample-rate", 96000.0); fluid_settings_setnum(settings, "synth.gain", 0.5); 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); fluid_player_set_loop(player, -1); fluid_player_play(player); size_t prev_pos = 0; size_t cur_pos = 0; size_t samples = 0; sample_positions.push_back({0, 0}); float fakebuf; fluid_player_seek(player, 0); while (prev_pos <= cur_pos) { size_t samples_to_add = std::floor((96000.0 * 60.0) / fluid_player_get_bpm(player)); fluid_synth_write_float(synth, samples_to_add, &fakebuf, 0, 0, &fakebuf, 0, 0); samples += samples_to_add; prev_pos = cur_pos; while (prev_pos == cur_pos) { cur_pos = fluid_player_get_current_tick(player); if (prev_pos == cur_pos) { fluid_synth_write_float(synth, 1, &fakebuf, 0, 0, &fakebuf, 0, 0); samples++; } } sample_positions.push_back({samples, cur_pos}); } this->length = ((double)samples) / 96000.0; } extern SDL_AudioSpec obtained; void FluidSynthBackend::switch_stream(int idx) { fluid_player_seek(player, 0); open = true; } void FluidSynthBackend::cleanup() { 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; open = false; } size_t FluidSynthBackend::render(void *buf, size_t maxlen) { size_t sample_type_len = sizeof(float); maxlen /= sample_type_len * 2; position += ((double)maxlen) / 96000.0; 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; } if (position >= length) { int tick = fluid_player_get_current_tick(player); int prev_sample = 0; int cur_sample = 0; for (auto &pair : sample_positions) { prev_sample = cur_sample; cur_sample = pair.first; if (pair.second > tick) { position = ((double)prev_sample) / 96000.0; break; } else if (pair.second == tick) { position = ((double)cur_sample) / 96000.0; break; } } } 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) { size_t tick = 0; size_t sample = position * 96000; size_t prev_sample = 0; size_t next_sample = 0; for (auto &pair : sample_positions) { prev_sample = next_sample; next_sample = pair.first; if (next_sample > sample) { tick = pair.second - 1; this->position = ((double)prev_sample) / 96000.0; break; } else if (next_sample == sample) { tick = pair.second; this->position = ((double)next_sample) / 96000.0; prev_sample = next_sample; break; } } fluid_player_seek(player, tick); float fakebuf; if (sample > prev_sample) fluid_synth_write_float(synth, sample - prev_sample, &fakebuf, 0, 0, &fakebuf, 0, 0); this->position = position; } double FluidSynthBackend::get_position() { return position; } 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; } }