174 lines
5.6 KiB
C++
174 lines
5.6 KiB
C++
#include "synth.h"
|
|
|
|
#include <stdexcept>
|
|
#include <sstream>
|
|
#include <log4cxx/logger.h>
|
|
|
|
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<double, NUM_MIDI_KEYS> 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::Program>& 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);
|
|
} |