/*
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;
float **left_outputs, **right_outputs;
};
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;
}
MODULE_CREATE_FUNCTION(fluidsynth)
{
int result = 0;
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);
m->left_outputs = NULL;
m->right_outputs = NULL;
}
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);
m->left_outputs = malloc(sizeof(float *) * (m->output_pairs + 2));
m->right_outputs = malloc(sizeof(float *) * (m->output_pairs + 2));
}
m->module.process_event = fluidsynth_process_event;
m->module.process_block = fluidsynth_process_block;
m->module.aux_offset = 2 * m->output_pairs;
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);
m->synth = new_fluid_synth(m->settings);
fluid_synth_set_reverb_on(m->synth, cbox_config_get_int(cfg_section, "reverb", 1));
fluid_synth_set_chorus_on(m->synth, cbox_config_get_int(cfg_section, "chorus", 1));
m->bank_name = NULL;
m->sfid = -1;
if (bankname)
{
m->bank_name = g_strdup(bankname);
g_message("Loading soundfont %s", bankname);
result = fluid_synth_sfload(m->synth, bankname, 1);
if (result == FLUID_FAILED)
{
g_set_error(error, CBOX_FLUIDSYNTH_ERROR, CBOX_FLUIDSYNTH_ERROR_FAILED, "Failed to load the default bank %s: %s", bankname, fluid_synth_error(m->synth));
return NULL;
}
m->sfid = result;
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
{
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);
}
}
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)
{
int result = fluid_synth_sfload(m->synth, bank_name, 1);
if (result == FLUID_FAILED)
{
g_set_error(error, CBOX_FLUIDSYNTH_ERROR, CBOX_FLUIDSYNTH_ERROR_FAILED, "Failed to load the bank %s: %s", bank_name, fluid_synth_error(m->synth));
return FALSE;
}
g_message("Soundfont %s loaded at ID %d", bank_name, result);
m->sfid = result;
}
else
m->sfid = -1;
if (old_sfid != -1)
{
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 (m->output_pairs)
{
free(m->left_outputs);
free(m->right_outputs);
}
free(m->bank_name);
delete_fluid_settings(m->settings);
delete_fluid_synth(m->synth);
}
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