#include "synth.h" #include #include #include namespace { // Helpers for the C API stuff constexpr int TRUE = 1; constexpr int FALSE = 0; static auto logger = log4cxx::Logger::getLogger("synth"); static auto fluidLogger = log4cxx::Logger::getLogger("synth.fluid"); } Synth::Synth() { try { settings = new_fluid_settings(); if (settings == nullptr) { throw new std::runtime_error("Unable to create fluid settings"); } synth = new_fluid_synth(settings); if (synth == nullptr) { throw new std::runtime_error("Unable to create fluid synth"); } #ifdef OUPUT_TO_FILE fluid_settings_setstr(settings, "audio.driver", "file"); fluid_settings_setstr(settings, "audio.file.name", "output.raw"); fluid_settings_setstr(settings, "audio.file.format", "s16"); fluid_settings_setstr(settings, "audio.file.type", "raw"); #endif fluid_settings_setnum(settings, "synth.sample-rate", 44100); fluid_settings_setnum(settings, "synth.gain", 1.0); adriver = new_fluid_audio_driver(settings, synth); if (adriver == nullptr) { throw new std::runtime_error("Unable to create audio driver"); } } catch (std::runtime_error& e) { // Do a mem-safe cleanup and rethrow. // TODO JMT: Do these handle receiving nullptr for deletes? delete_fluid_audio_driver(adriver); delete_fluid_synth(synth); delete_fluid_settings(settings); throw; } } Synth::~Synth() { // TODO JMT: Do I need to manually clear all of the soundfonts and programs I have handles for? delete_fluid_audio_driver(adriver); delete_fluid_synth(synth); delete_fluid_settings(settings); } bool Synth::loadSoundfont(const std::string& filepath) { int soundfontId = fluid_synth_sfload(synth, filepath.c_str(), 1); if(soundfontId == FLUID_FAILED) { return false; } fluid_sfont_t* soundfontHandle = fluid_synth_get_sfont_by_id(synth, soundfontId); const std::string soundfontName{fluid_sfont_get_name(soundfontHandle)}; Synth::Soundfont soundfont{soundfontName, soundfontId}; soundfonts.push_back(soundfont); fluid_preset_t* preset = nullptr; fluid_sfont_iteration_start(soundfontHandle); while ((preset = fluid_sfont_iteration_next(soundfontHandle)) != nullptr) { const std::string presetName{fluid_preset_get_name(preset)}; const int bankNumber = fluid_preset_get_banknum(preset); const int programNumber = fluid_preset_get_num(preset); Synth::Program program {presetName, soundfontId, bankNumber, programNumber}; programs.push_back(program); } return true; } bool Synth::loadProgram(const Program& program) { // TODO JMT: fix this fluid_synth_program_select(synth, 0, program.soundfontId, program.bankNumber, program.programNumber); fluid_synth_program_select(synth, 1, program.soundfontId, program.bankNumber, program.programNumber); return true; } bool Synth::setTuning(const TuningFrequencies& frequencies) { // fluidsynth wants tunings in MIDI cents, which effectively means MIDI key * 100. // For example: normally note 0 is 0.0, 1 is 100.0, 60 is 6000.0, etc. constexpr double MIDI_CENT_SCALE = 100.0; std::array pitchCents; std::transform( frequencies.begin(), frequencies.end(), pitchCents.begin(), [] (double frequency) { // Remap 0 Hz to MIDI 0, otherwise we get -inf which causes some interpolations between // notes to result in zero sound. return frequency == 0.0 ? 0.0 : frequencyToMidiKey(frequency) * MIDI_CENT_SCALE; } ); std::stringstream ss; for (auto p : pitchCents) { ss << p << ", "; } LOG4CXX_DEBUG(logger, "Tuning pitches [" << ss.str() << "]"); auto result = fluid_synth_activate_key_tuning(synth, 0, 0, "default", pitchCents.data(), FALSE); return (result == FLUID_OK) ? activateTuning() : false; } bool Synth::resetTuning() { auto result = fluid_synth_activate_key_tuning(synth, 0, 0, "default", nullptr, 1); return (result == FLUID_OK) ? activateTuning() : false; } const std::vector& Synth::getPrograms() const { return programs; } fluid_synth_t* Synth::getSynth() { return synth; } bool Synth::activateTuning() { int channelIndex = 0; int result; do { result = fluid_synth_activate_tuning(synth, channelIndex++, 0, 0, TRUE); } while (result == FLUID_OK && channelIndex < NUM_MIDI_CHANNELS); LOG4CXX_DEBUG(logger, "Tuning activation result: " << result); return result == FLUID_OK; } // Fluid logging redirector. void fluid_log_function(int level, const char* message, void* data) { switch (level) { case FLUID_PANIC: LOG4CXX_FATAL(fluidLogger, message); break; case FLUID_ERR: LOG4CXX_ERROR(fluidLogger, message); break; case FLUID_WARN: LOG4CXX_WARN(fluidLogger, message); break; case FLUID_INFO: LOG4CXX_INFO(fluidLogger, message); break; case FLUID_DBG: LOG4CXX_DEBUG(fluidLogger, message); break; } } void redirect_fluid_logs() { fluid_set_log_function(FLUID_PANIC, fluid_log_function, NULL); fluid_set_log_function(FLUID_ERR, fluid_log_function, NULL); fluid_set_log_function(FLUID_WARN, fluid_log_function, NULL); fluid_set_log_function(FLUID_INFO, fluid_log_function, NULL); fluid_set_log_function(FLUID_DBG, fluid_log_function, NULL); }