looper/backends/playback/fluidsynth/fluidsynth_backend.cpp
Zachary Hall 6920d3c0ff
Some checks failed
Build / download-system-deps (push) Successful in 1m15s
Build / build-gentoo (push) Successful in 1m34s
Build / get-source-code (push) Successful in 4m5s
Build / build-deb (push) Failing after 2m37s
Build / build-appimage (push) Successful in 1m26s
Build / build-android (push) Failing after 16s
Build / build-windows (push) Failing after 4m11s
Use internal log function for Fluidsynth
2024-12-29 09:19:49 -08:00

270 lines
9.6 KiB
C++

#include "fluidsynth_backend.hpp"
#include <algorithm>
#include <ipc/common.pb.h>
#include <exception>
#include <filesystem>
#include "file_backend.hpp"
#include <stddef.h>
#include <string.h>
#include <file_backend.hpp>
#include <util.hpp>
#include <filesystem>
#include <limits.h>
#include <log.hpp>
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<Property> 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<std::string> try_paths = std::vector<std::string>({
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<std::string> 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<double> 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<int> 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;
}
}