/* Calf Box, an open source musical instrument. Copyright (C) 2010 Krzysztof Foltman This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #if USE_FLUIDSYNTH #include "config-api.h" #include "dspmath.h" #include "module.h" #include #include #include #include #include #include #include #include #include #if FLUIDSYNTH_VERSION_MAJOR < 2 typedef unsigned int cbox_fluidsynth_id_t; #define fluid_sfont_iteration_setup() fluid_preset_t sfont_iterator; #define fluid_sfont_iteration_start(sfont) (sfont)->iteration_start(sfont) #define fluid_sfont_iteration_next(sfont) (sfont)->iteration_next(sfont, &sfont_iterator) ? &sfont_iterator : NULL #define fluid_preset_get_num(preset) (preset)->get_num(preset) #define fluid_preset_get_banknum(preset) (preset)->get_banknum(preset) #define fluid_preset_get_name(preset) (preset)->get_name(preset) #else typedef int cbox_fluidsynth_id_t; #define fluid_sfont_iteration_setup() #endif #define CBOX_FLUIDSYNTH_ERROR cbox_fluidsynth_error_quark() enum CboxFluidsynthError { CBOX_FLUIDSYNTH_ERROR_FAILED, }; GQuark cbox_fluidsynth_error_quark(void) { return g_quark_from_string("cbox-fluidsynth-error-quark"); } static void fluidsynth_process_block(struct cbox_module *module, cbox_sample_t **inputs, cbox_sample_t **outputs); static void fluidsynth_process_event(struct cbox_module *module, const uint8_t *data, uint32_t len); static gboolean fluidsynth_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error); static void fluidsynth_destroyfunc(struct cbox_module *module); struct fluidsynth_module { struct cbox_module module; fluid_settings_t *settings; fluid_synth_t *synth; char *bank_name; int sfid; int output_pairs; int is_multi; #if OLD_FLUIDSYNTH float **left_outputs, **right_outputs; #endif GString *error_log; }; static gboolean select_patch_by_name(struct fluidsynth_module *m, int channel, const gchar *preset, GError **error) { fluid_sfont_t* sfont = fluid_synth_get_sfont(m->synth, 0); fluid_preset_t* tmp; fluid_sfont_iteration_setup(); fluid_sfont_iteration_start(sfont); while((tmp = fluid_sfont_iteration_next(sfont)) != NULL) { // trailing spaces are common in some SF2s const char *pname = fluid_preset_get_name(tmp); int len = strlen(pname); while (len > 0 && pname[len - 1] == ' ') len--; if (!strncmp(pname, preset, len) && preset[len] == '\0') { fluid_synth_program_select(m->synth, channel, m->sfid, fluid_preset_get_banknum(tmp), fluid_preset_get_num(tmp)); return TRUE; } } g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Preset not found: %s", preset); return FALSE; } static void cbox_fluidsynth_log_write(int level, const char *message, void *data) { struct fluidsynth_module *m = data; if (!m->error_log) m->error_log = g_string_new(message); else g_string_append(m->error_log, message); g_string_append_c(m->error_log, '\n'); } static gboolean load_soundfont(struct fluidsynth_module *m, const char *bank_name, GError **error) { g_message("Loading soundfont %s", bank_name); int result = fluid_synth_sfload(m->synth, bank_name, 1); if (result == FLUID_FAILED) { char *error_msg = g_string_free(m->error_log, FALSE); m->error_log = NULL; g_set_error(error, CBOX_FLUIDSYNTH_ERROR, CBOX_FLUIDSYNTH_ERROR_FAILED, "Failed to load the SF2 bank %s: %s", bank_name, error_msg); g_free(error_msg); return FALSE; } m->bank_name = g_strdup(bank_name); m->sfid = result; return TRUE; } MODULE_CREATE_FUNCTION(fluidsynth) { int i; const char *bankname = cbox_config_get_string(cfg_section, "sf2"); static int inited = 0; if (!inited) { inited = 1; } struct fluidsynth_module *m = malloc(sizeof(struct fluidsynth_module)); int pairs = cbox_config_get_int(cfg_section, "output_pairs", 0); m->output_pairs = pairs ? pairs : 1; m->is_multi = pairs > 0; if (m->output_pairs < 1 || m->output_pairs > 16) { free(m); g_set_error(error, CBOX_FLUIDSYNTH_ERROR, CBOX_FLUIDSYNTH_ERROR_FAILED, "Invalid number of output pairs (found %d, supported range 1-16)", m->output_pairs); return NULL; } if (pairs == 0) { CALL_MODULE_INIT(m, 0, 2 * m->output_pairs, fluidsynth); #if OLD_FLUIDSYNTH m->left_outputs = NULL; m->right_outputs = NULL; #endif } else { g_message("Multichannel mode enabled, %d output pairs, 2 effects", m->output_pairs); CALL_MODULE_INIT(m, 0, 2 * m->output_pairs + 4, fluidsynth); #if OLD_FLUIDSYNTH m->left_outputs = malloc(sizeof(float *) * (m->output_pairs + 2)); m->right_outputs = malloc(sizeof(float *) * (m->output_pairs + 2)); #endif } m->module.process_event = fluidsynth_process_event; m->module.process_block = fluidsynth_process_block; m->module.aux_offset = 2 * m->output_pairs; const char *audio_drivers[] = { NULL }; fluid_audio_driver_register(audio_drivers); m->settings = new_fluid_settings(); fluid_settings_setnum(m->settings, "synth.sample-rate", m->module.srate); fluid_settings_setint(m->settings, "synth.audio-channels", m->output_pairs); fluid_settings_setint(m->settings, "synth.audio-groups", m->output_pairs); fluid_settings_setint(m->settings, "synth.reverb.active", cbox_config_get_int(cfg_section, "reverb", 1)); fluid_settings_setint(m->settings, "synth.chorus.active", cbox_config_get_int(cfg_section, "chorus", 1)); m->synth = new_fluid_synth(m->settings); fluid_set_log_function(FLUID_PANIC, cbox_fluidsynth_log_write, m); fluid_set_log_function(FLUID_ERR, cbox_fluidsynth_log_write, m); fluid_set_log_function(FLUID_WARN, cbox_fluidsynth_log_write, m); //fluid_synth_add_sfloader(m->synth, new_fluid_defsfloader(m->settings)); m->error_log = NULL; m->bank_name = NULL; m->sfid = -1; if (bankname) { if (!load_soundfont(m, bankname, error)) { CBOX_DELETE(&m->module); return NULL; } g_message("Soundfont %s loaded", bankname); } if (bankname) { for (i = 0; i < 16; i++) { gchar *key = g_strdup_printf("channel%d", i + 1); gchar *preset = cbox_config_get_string(cfg_section, key); fluid_synth_sfont_select(m->synth, i, m->sfid); if (preset) { if (!select_patch_by_name(m, i, preset, error)) { CBOX_DELETE(&m->module); return NULL; } } g_free(key); } } return &m->module; } void fluidsynth_process_block(struct cbox_module *module, cbox_sample_t **inputs, cbox_sample_t **outputs) { struct fluidsynth_module *m = (struct fluidsynth_module *)module; if (!m->is_multi) fluid_synth_write_float(m->synth, CBOX_BLOCK_SIZE, outputs[0], 0, 1, outputs[1], 0, 1); else { #if OLD_FLUIDSYNTH for (int i = 0; i < 2 + m->output_pairs; i++) { m->left_outputs[i] = outputs[2 * i]; m->right_outputs[i] = outputs[2 * i + 1]; } fluid_synth_nwrite_float(m->synth, CBOX_BLOCK_SIZE, m->left_outputs, m->right_outputs, m->left_outputs + m->output_pairs, m->right_outputs + m->output_pairs); #else float *fx_outputs[4]; for (int i = 0; i < 4; i++) fx_outputs[i] = outputs[2 * m->output_pairs + i]; for (int i = 0; i < 2 * (2 + m->output_pairs); i++) zerobf(outputs[i]); fluid_synth_process(m->synth, CBOX_BLOCK_SIZE, 4, fx_outputs, 2 * m->output_pairs, outputs); #endif } } void fluidsynth_process_event(struct cbox_module *module, const uint8_t *data, uint32_t len) { struct fluidsynth_module *m = (struct fluidsynth_module *)module; if (len > 0) { int cmd = data[0] >> 4; int chn = data[0] & 15; switch(cmd) { case 8: fluid_synth_noteoff(m->synth, chn, data[1]); break; case 9: fluid_synth_noteon(m->synth, chn, data[1], data[2]); break; case 10: // polyphonic pressure not handled break; case 11: fluid_synth_cc(m->synth, chn, data[1], data[2]); break; case 12: fluid_synth_program_change(m->synth, chn, data[1]); break; case 13: fluid_synth_channel_pressure(m->synth, chn, data[1]); break; case 14: fluid_synth_pitch_bend(m->synth, chn, data[1] + 128 * data[2]); break; } } } gboolean fluidsynth_process_load_patch(struct fluidsynth_module *m, const char *bank_name, GError **error) { if (bank_name && !*bank_name) bank_name = NULL; int old_sfid = m->sfid; char *old_bank_name = m->bank_name; if (bank_name) { if (!load_soundfont(m, bank_name, error)) return FALSE; g_message("Soundfont %s loaded at ID %d", bank_name, m->sfid); } else m->sfid = -1; if (old_sfid != -1) { g_free(old_bank_name); fluid_synth_sfunload(m->synth, old_sfid, 1); } if (m->sfid != -1) { for (int i = 0; i < 16; i++) fluid_synth_sfont_select(m->synth, i, m->sfid); } m->bank_name = bank_name ? g_strdup(bank_name) : NULL; return TRUE; } gboolean fluidsynth_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error) { struct fluidsynth_module *m = (struct fluidsynth_module *)ct->user_data; if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, "")) { if (!cbox_check_fb_channel(fb, cmd->command, error)) return FALSE; if (!cbox_execute_on(fb, NULL, "/polyphony", "i", error, fluid_synth_get_polyphony(m->synth))) return FALSE; if (!cbox_execute_on(fb, NULL, "/soundfont", "s", error, m->bank_name ? m->bank_name : "")) return FALSE; for (int i = 0; i < 16; i++) { cbox_fluidsynth_id_t sfont_id, bank_num, preset_num; fluid_synth_get_program(m->synth, i, &sfont_id, &bank_num, &preset_num); fluid_preset_t *preset = fluid_synth_get_channel_preset(m->synth, i); if (!cbox_execute_on(fb, NULL, "/patch", "iis", error, 1 + i, preset_num + 128 * bank_num, preset ? fluid_preset_get_name(preset) : "(unknown)")) return FALSE; } return CBOX_OBJECT_DEFAULT_STATUS(&m->module, fb, error); } else if (!strcmp(cmd->command, "/patches") && !strcmp(cmd->arg_types, "")) { if (!cbox_check_fb_channel(fb, cmd->command, error)) return FALSE; if (m->sfid == -1) return TRUE; fluid_sfont_t* sfont = fluid_synth_get_sfont(m->synth, 0); fluid_preset_t *tmp; fluid_sfont_iteration_setup(); fluid_sfont_iteration_start(sfont); while((tmp = fluid_sfont_iteration_next(sfont)) != NULL) { const char *pname = fluid_preset_get_name(tmp); if (!cbox_execute_on(fb, NULL, "/patch", "is", error, (int)(fluid_preset_get_num(tmp) + 128 * fluid_preset_get_banknum(tmp)), pname)) return FALSE; } return TRUE; } else if (!strcmp(cmd->command, "/set_patch") && !strcmp(cmd->arg_types, "ii")) { if (m->sfid == -1) { g_set_error(error, CBOX_FLUIDSYNTH_ERROR, CBOX_FLUIDSYNTH_ERROR_FAILED, "No soundfont loaded"); return FALSE; } int channel = CBOX_ARG_I(cmd, 0); if (channel < 1 || channel > 16) { g_set_error(error, CBOX_FLUIDSYNTH_ERROR, CBOX_FLUIDSYNTH_ERROR_FAILED, "Invalid channel %d", channel); return FALSE; } int value = CBOX_ARG_I(cmd, 1); return fluid_synth_program_select(m->synth, channel - 1, m->sfid, value >> 7, value & 127) == FLUID_OK; } else if (!strcmp(cmd->command, "/polyphony") && !strcmp(cmd->arg_types, "i")) { int polyphony = CBOX_ARG_I(cmd, 0); if (polyphony < 2 || polyphony > 256) { g_set_error(error, CBOX_FLUIDSYNTH_ERROR, CBOX_FLUIDSYNTH_ERROR_FAILED, "Invalid polyphony %d (must be between 2 and 256)", polyphony); return FALSE; } return fluid_synth_set_polyphony(m->synth, polyphony) == FLUID_OK; } else if (!strcmp(cmd->command, "/load_soundfont") && !strcmp(cmd->arg_types, "s")) { return fluidsynth_process_load_patch(m, CBOX_ARG_S(cmd, 0), error); } else return cbox_object_default_process_cmd(ct, fb, cmd, error); return TRUE; } void fluidsynth_destroyfunc(struct cbox_module *module) { struct fluidsynth_module *m = (struct fluidsynth_module *)module; #if OLD_FLUIDSYNTH if (m->output_pairs) { free(m->left_outputs); free(m->right_outputs); } #endif free(m->bank_name); delete_fluid_synth(m->synth); delete_fluid_settings(m->settings); } struct cbox_module_livecontroller_metadata fluidsynth_controllers[] = { { -1, cmlc_continuouscc, 1, "Modulation", NULL}, { -1, cmlc_continuouscc, 7, "Volume", NULL}, { -1, cmlc_continuouscc, 10, "Pan", NULL}, { -1, cmlc_continuouscc, 91, "Reverb", NULL}, { -1, cmlc_continuouscc, 93, "Chorus", NULL}, { -1, cmlc_onoffcc, 64, "Hold", NULL}, { -1, cmlc_onoffcc, 66, "Sostenuto", NULL}, }; struct cbox_module_keyrange_metadata fluidsynth_keyranges[] = { { 1, 0, 127, "Channel 1" }, { 2, 0, 127, "Channel 2" }, { 3, 0, 127, "Channel 3" }, { 4, 0, 127, "Channel 4" }, { 5, 0, 127, "Channel 5" }, { 6, 0, 127, "Channel 6" }, { 7, 0, 127, "Channel 7" }, { 8, 0, 127, "Channel 8" }, { 9, 0, 127, "Channel 9" }, { 10, 0, 127, "Channel 10" }, { 11, 0, 127, "Channel 11" }, { 12, 0, 127, "Channel 12" }, { 13, 0, 127, "Channel 13" }, { 14, 0, 127, "Channel 14" }, { 15, 0, 127, "Channel 15" }, { 16, 0, 127, "Channel 16" }, }; DEFINE_MODULE(fluidsynth, 0, 2) #endif