2018-02-27 08:25:20 +08:00
|
|
|
//
|
|
|
|
// Created by Alex Birch on 10/09/2017.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include <iostream>
|
2019-07-30 06:46:38 +08:00
|
|
|
#include <iterator>
|
2019-07-07 07:22:47 +08:00
|
|
|
#include <fluidsynth.h>
|
2018-02-27 08:25:20 +08:00
|
|
|
#include "FluidSynthModel.h"
|
2019-06-24 02:40:36 +08:00
|
|
|
#include "MidiConstants.h"
|
2019-07-07 07:22:47 +08:00
|
|
|
#include "Util.h"
|
2018-02-27 08:25:20 +08:00
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
2019-07-30 06:46:38 +08:00
|
|
|
const map<fluid_midi_control_change, String> FluidSynthModel::controllerToParam{
|
|
|
|
{SOUND_CTRL2, "filterResonance"}, // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance)
|
|
|
|
{SOUND_CTRL3, "release"}, // MIDI CC 72 Release time
|
|
|
|
{SOUND_CTRL4, "attack"}, // MIDI CC 73 Attack time
|
|
|
|
{SOUND_CTRL5, "filterCutOff"}, // MIDI CC 74 Brightness (cutoff frequency, FILTERFC)
|
|
|
|
{SOUND_CTRL6, "decay"}, // MIDI CC 75 Decay Time
|
|
|
|
{SOUND_CTRL10, "sustain"}}; // MIDI CC 79 undefined
|
|
|
|
|
|
|
|
const map<String, fluid_midi_control_change> FluidSynthModel::paramToController{[]{
|
|
|
|
map<String, fluid_midi_control_change> map;
|
|
|
|
transform(
|
|
|
|
controllerToParam.begin(),
|
|
|
|
controllerToParam.end(),
|
|
|
|
inserter(map, map.begin()),
|
|
|
|
[](const pair<fluid_midi_control_change, String>& pair) {
|
|
|
|
return make_pair(pair.second, pair.first);
|
|
|
|
});
|
|
|
|
return map;
|
|
|
|
}()};
|
|
|
|
|
2019-07-07 07:22:47 +08:00
|
|
|
FluidSynthModel::FluidSynthModel(
|
2019-07-11 06:13:58 +08:00
|
|
|
AudioProcessorValueTreeState& valueTreeState
|
2019-07-07 07:22:47 +08:00
|
|
|
)
|
|
|
|
: valueTreeState{valueTreeState}
|
|
|
|
, settings{nullptr, nullptr}
|
2019-07-29 06:02:22 +08:00
|
|
|
, synth{nullptr, nullptr}
|
2019-07-07 07:22:47 +08:00
|
|
|
, currentSampleRate{44100}
|
2019-07-28 06:04:20 +08:00
|
|
|
, sfont_id{-1}
|
2019-07-29 06:02:22 +08:00
|
|
|
, channel{0}
|
2019-07-08 00:35:31 +08:00
|
|
|
{
|
2019-07-16 04:28:35 +08:00
|
|
|
valueTreeState.addParameterListener("bank", this);
|
|
|
|
valueTreeState.addParameterListener("preset", this);
|
2019-07-30 06:46:38 +08:00
|
|
|
for (const auto &[param, controller]: paramToController) {
|
|
|
|
valueTreeState.addParameterListener(param, this);
|
|
|
|
}
|
2019-07-11 06:52:15 +08:00
|
|
|
valueTreeState.state.addListener(this);
|
2019-07-08 00:35:31 +08:00
|
|
|
}
|
2018-04-10 07:51:21 +08:00
|
|
|
|
2019-07-08 00:35:31 +08:00
|
|
|
FluidSynthModel::~FluidSynthModel() {
|
2019-07-30 06:46:38 +08:00
|
|
|
for (const auto &[param, controller]: paramToController) {
|
|
|
|
valueTreeState.removeParameterListener(param, this);
|
|
|
|
}
|
2019-07-16 04:28:35 +08:00
|
|
|
valueTreeState.removeParameterListener("bank", this);
|
|
|
|
valueTreeState.removeParameterListener("preset", this);
|
2019-07-11 06:52:15 +08:00
|
|
|
valueTreeState.state.removeListener(this);
|
2018-02-27 08:25:20 +08:00
|
|
|
}
|
|
|
|
|
2018-04-10 08:20:23 +08:00
|
|
|
void FluidSynthModel::initialise() {
|
2018-04-18 05:10:01 +08:00
|
|
|
// deactivate all audio drivers in fluidsynth to avoid FL Studio deadlock when initialising CoreAudio
|
|
|
|
// after all: we only use fluidsynth to render blocks of audio. it doesn't output to audio driver.
|
2019-07-07 07:22:47 +08:00
|
|
|
const char *DRV[] {NULL};
|
2018-04-18 05:10:01 +08:00
|
|
|
fluid_audio_driver_register(DRV);
|
2019-07-07 07:22:47 +08:00
|
|
|
|
2019-08-02 04:51:16 +08:00
|
|
|
settings = { new_fluid_settings(), delete_fluid_settings };
|
|
|
|
|
2018-02-27 08:25:20 +08:00
|
|
|
// https://sourceforge.net/p/fluidsynth/wiki/FluidSettings/
|
2019-07-02 04:15:33 +08:00
|
|
|
#if JUCE_DEBUG
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_settings_setint(settings.get(), "synth.verbose", 1);
|
2019-07-02 04:15:33 +08:00
|
|
|
#endif
|
2018-02-27 08:25:20 +08:00
|
|
|
|
2019-07-07 07:22:47 +08:00
|
|
|
synth = { new_fluid_synth(settings.get()), delete_fluid_synth };
|
|
|
|
fluid_synth_set_sample_rate(synth.get(), currentSampleRate);
|
2018-02-27 08:25:20 +08:00
|
|
|
|
2019-07-30 06:46:38 +08:00
|
|
|
// I can't hear a damned thing
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_synth_set_gain(synth.get(), 2.0);
|
2019-07-01 02:25:02 +08:00
|
|
|
|
2019-07-30 06:46:38 +08:00
|
|
|
// note: fluid_chan.c#fluid_channel_init_ctrl()
|
2019-07-31 04:52:44 +08:00
|
|
|
// > Just like panning, a value of 64 indicates no change for sound ctrls
|
|
|
|
// --
|
|
|
|
// so, advice is to leave everything at 64
|
|
|
|
// and yet, I'm finding that default modulators start at MIN,
|
|
|
|
// i.e. we are forced to start at 0 and climb from there
|
|
|
|
// --
|
2019-08-11 06:18:43 +08:00
|
|
|
// let's zero out every audio param that we manage
|
|
|
|
for (const auto &[controller, param]: controllerToParam) {
|
|
|
|
setControllerValue(static_cast<int>(controller), 0);
|
2019-07-01 02:25:02 +08:00
|
|
|
}
|
2019-06-30 05:10:28 +08:00
|
|
|
|
2019-07-30 06:46:38 +08:00
|
|
|
// http://www.synthfont.com/SoundFont_NRPNs.PDF
|
2019-07-28 06:04:20 +08:00
|
|
|
float env_amount{20000.0f};
|
2019-06-24 02:40:36 +08:00
|
|
|
|
2019-07-07 07:22:47 +08:00
|
|
|
unique_ptr<fluid_mod_t, decltype(&delete_fluid_mod)> mod{new_fluid_mod(), delete_fluid_mod};
|
|
|
|
fluid_mod_set_source1(mod.get(),
|
2019-06-24 02:40:36 +08:00
|
|
|
static_cast<int>(SOUND_CTRL2), // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance)
|
|
|
|
FLUID_MOD_CC
|
|
|
|
| FLUID_MOD_UNIPOLAR
|
|
|
|
| FLUID_MOD_CONCAVE
|
2019-06-30 05:10:28 +08:00
|
|
|
| FLUID_MOD_POSITIVE);
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_mod_set_source2(mod.get(), 0, 0);
|
|
|
|
fluid_mod_set_dest(mod.get(), GEN_FILTERQ);
|
|
|
|
fluid_mod_set_amount(mod.get(), FLUID_PEAK_ATTENUATION);
|
|
|
|
fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
|
2019-06-24 02:40:36 +08:00
|
|
|
|
2019-07-07 07:22:47 +08:00
|
|
|
mod = {new_fluid_mod(), delete_fluid_mod};
|
|
|
|
fluid_mod_set_source1(mod.get(),
|
2019-06-24 02:40:36 +08:00
|
|
|
static_cast<int>(SOUND_CTRL3), // MIDI CC 72 Release time
|
|
|
|
FLUID_MOD_CC
|
2019-06-30 05:10:28 +08:00
|
|
|
| FLUID_MOD_UNIPOLAR
|
|
|
|
| FLUID_MOD_LINEAR
|
2019-06-24 02:40:36 +08:00
|
|
|
| FLUID_MOD_POSITIVE);
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_mod_set_source2(mod.get(), 0, 0);
|
|
|
|
fluid_mod_set_dest(mod.get(), GEN_VOLENVRELEASE);
|
|
|
|
fluid_mod_set_amount(mod.get(), env_amount);
|
|
|
|
fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
|
2019-06-24 02:40:36 +08:00
|
|
|
|
2019-07-07 07:22:47 +08:00
|
|
|
mod = {new_fluid_mod(), delete_fluid_mod};
|
|
|
|
fluid_mod_set_source1(mod.get(),
|
2019-06-24 02:40:36 +08:00
|
|
|
static_cast<int>(SOUND_CTRL4), // MIDI CC 73 Attack time
|
|
|
|
FLUID_MOD_CC
|
2019-06-30 05:10:28 +08:00
|
|
|
| FLUID_MOD_UNIPOLAR
|
|
|
|
| FLUID_MOD_LINEAR
|
2019-06-24 02:40:36 +08:00
|
|
|
| FLUID_MOD_POSITIVE);
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_mod_set_source2(mod.get(), 0, 0);
|
|
|
|
fluid_mod_set_dest(mod.get(), GEN_VOLENVATTACK);
|
|
|
|
fluid_mod_set_amount(mod.get(), env_amount);
|
|
|
|
fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
|
2019-06-23 05:59:28 +08:00
|
|
|
|
2019-07-01 02:25:02 +08:00
|
|
|
// soundfont spec says that if cutoff is >20kHz and resonance Q is 0, then no filtering occurs
|
2019-07-07 07:22:47 +08:00
|
|
|
mod = {new_fluid_mod(), delete_fluid_mod};
|
|
|
|
fluid_mod_set_source1(mod.get(),
|
2019-06-24 02:40:36 +08:00
|
|
|
static_cast<int>(SOUND_CTRL5), // MIDI CC 74 Brightness (cutoff frequency, FILTERFC)
|
|
|
|
FLUID_MOD_CC
|
2019-06-30 05:10:28 +08:00
|
|
|
| FLUID_MOD_LINEAR
|
2019-06-24 02:40:36 +08:00
|
|
|
| FLUID_MOD_UNIPOLAR
|
|
|
|
| FLUID_MOD_POSITIVE);
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_mod_set_source2(mod.get(), 0, 0);
|
|
|
|
fluid_mod_set_dest(mod.get(), GEN_FILTERFC);
|
|
|
|
fluid_mod_set_amount(mod.get(), -2400.0f);
|
|
|
|
fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
|
2019-06-23 05:59:28 +08:00
|
|
|
|
2019-07-07 07:22:47 +08:00
|
|
|
mod = {new_fluid_mod(), delete_fluid_mod};
|
|
|
|
fluid_mod_set_source1(mod.get(),
|
2019-06-24 02:40:36 +08:00
|
|
|
static_cast<int>(SOUND_CTRL6), // MIDI CC 75 Decay Time
|
|
|
|
FLUID_MOD_CC
|
2019-06-30 05:10:28 +08:00
|
|
|
| FLUID_MOD_UNIPOLAR
|
|
|
|
| FLUID_MOD_LINEAR
|
2019-06-24 02:40:36 +08:00
|
|
|
| FLUID_MOD_POSITIVE);
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_mod_set_source2(mod.get(), 0, 0);
|
|
|
|
fluid_mod_set_dest(mod.get(), GEN_VOLENVDECAY);
|
|
|
|
fluid_mod_set_amount(mod.get(), env_amount);
|
|
|
|
fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
|
2019-07-01 02:25:02 +08:00
|
|
|
|
2019-07-07 07:22:47 +08:00
|
|
|
mod = {new_fluid_mod(), delete_fluid_mod};
|
|
|
|
fluid_mod_set_source1(mod.get(),
|
2019-07-01 02:25:02 +08:00
|
|
|
static_cast<int>(SOUND_CTRL10), // MIDI CC 79 undefined
|
|
|
|
FLUID_MOD_CC
|
|
|
|
| FLUID_MOD_UNIPOLAR
|
2019-07-01 04:40:24 +08:00
|
|
|
| FLUID_MOD_CONCAVE
|
2019-07-01 02:25:02 +08:00
|
|
|
| FLUID_MOD_POSITIVE);
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_mod_set_source2(mod.get(), 0, 0);
|
|
|
|
fluid_mod_set_dest(mod.get(), GEN_VOLENVSUSTAIN);
|
2019-07-01 02:25:02 +08:00
|
|
|
// fluice_voice.c#fluid_voice_update_param()
|
|
|
|
// clamps the range to between 0 and 1000, so we'll copy that
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_mod_set_amount(mod.get(), 1000.0f);
|
|
|
|
fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
|
2018-02-27 08:25:20 +08:00
|
|
|
}
|
|
|
|
|
2019-07-31 04:12:49 +08:00
|
|
|
const StringArray FluidSynthModel::programChangeParams{"bank", "preset"};
|
2019-07-16 04:28:35 +08:00
|
|
|
void FluidSynthModel::parameterChanged(const String& parameterID, float newValue) {
|
2019-07-31 04:12:49 +08:00
|
|
|
if (programChangeParams.contains(parameterID)) {
|
2019-07-16 05:12:07 +08:00
|
|
|
int bank, preset;
|
|
|
|
{
|
2019-07-30 06:46:38 +08:00
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter("bank")};
|
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
2019-07-16 05:12:07 +08:00
|
|
|
bank = castParam->get();
|
|
|
|
}
|
|
|
|
{
|
2019-07-30 06:46:38 +08:00
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
2019-07-16 05:12:07 +08:00
|
|
|
preset = castParam->get();
|
|
|
|
}
|
2019-07-21 02:56:12 +08:00
|
|
|
int bankOffset{fluid_synth_get_bank_offset(synth.get(), sfont_id)};
|
2019-07-16 05:12:07 +08:00
|
|
|
fluid_synth_program_select(
|
|
|
|
synth.get(),
|
|
|
|
channel,
|
|
|
|
sfont_id,
|
2019-07-21 02:56:12 +08:00
|
|
|
static_cast<unsigned int>(bankOffset + bank),
|
2019-07-16 05:12:07 +08:00
|
|
|
static_cast<unsigned int>(preset));
|
2019-07-30 06:46:38 +08:00
|
|
|
} else if (
|
|
|
|
// https://stackoverflow.com/a/55482091/5257399
|
|
|
|
auto it{paramToController.find(parameterID)};
|
|
|
|
it != end(paramToController)) {
|
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter(parameterID)};
|
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
|
|
|
int value{castParam->get()};
|
|
|
|
int controllerNumber{static_cast<int>(it->second)};
|
|
|
|
|
|
|
|
fluid_synth_cc(
|
|
|
|
synth.get(),
|
|
|
|
channel,
|
|
|
|
controllerNumber,
|
|
|
|
value);
|
2019-07-16 04:28:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-08 00:35:31 +08:00
|
|
|
void FluidSynthModel::valueTreePropertyChanged(ValueTree& treeWhosePropertyHasChanged,
|
|
|
|
const Identifier& property) {
|
2019-07-11 06:13:58 +08:00
|
|
|
if (treeWhosePropertyHasChanged.getType() == StringRef("soundFont")) {
|
|
|
|
if (property == StringRef("path")) {
|
2019-08-01 06:16:31 +08:00
|
|
|
String soundFontPath = treeWhosePropertyHasChanged.getProperty("path", "");
|
2019-07-08 00:35:31 +08:00
|
|
|
if (soundFontPath.isNotEmpty()) {
|
2019-07-28 06:04:20 +08:00
|
|
|
unloadAndLoadFont(soundFontPath);
|
2019-07-08 00:35:31 +08:00
|
|
|
}
|
|
|
|
}
|
2019-07-11 06:13:58 +08:00
|
|
|
}
|
2019-07-08 00:35:31 +08:00
|
|
|
}
|
|
|
|
|
2019-07-01 04:40:24 +08:00
|
|
|
void FluidSynthModel::setControllerValue(int controller, int value) {
|
2019-07-29 06:02:22 +08:00
|
|
|
fluid_synth_cc(
|
|
|
|
synth.get(),
|
|
|
|
channel,
|
|
|
|
controller,
|
|
|
|
value);
|
2019-07-01 04:40:24 +08:00
|
|
|
}
|
|
|
|
|
2018-02-27 08:25:20 +08:00
|
|
|
int FluidSynthModel::getChannel() {
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
|
2018-04-11 06:29:32 +08:00
|
|
|
void FluidSynthModel::unloadAndLoadFont(const String &absPath) {
|
2018-03-06 06:38:51 +08:00
|
|
|
// in the base case, there is no font loaded
|
2019-07-07 07:22:47 +08:00
|
|
|
if (fluid_synth_sfcount(synth.get()) > 0) {
|
2019-07-28 06:04:20 +08:00
|
|
|
// if -1 is returned, that indicates failure
|
|
|
|
// not really sure how to handle "fail to unload"
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_synth_sfunload(synth.get(), sfont_id, 1);
|
2019-07-28 06:04:20 +08:00
|
|
|
sfont_id = -1;
|
2018-03-06 06:38:51 +08:00
|
|
|
}
|
2018-02-27 08:25:20 +08:00
|
|
|
loadFont(absPath);
|
|
|
|
}
|
|
|
|
|
2018-04-11 06:29:32 +08:00
|
|
|
void FluidSynthModel::loadFont(const String &absPath) {
|
2019-07-28 06:04:20 +08:00
|
|
|
if (!absPath.isEmpty()) {
|
|
|
|
sfont_id = fluid_synth_sfload(synth.get(), absPath.toStdString().c_str(), 1);
|
|
|
|
// if -1 is returned, that indicates failure
|
|
|
|
}
|
|
|
|
// refresh regardless of success, if only to clear the table
|
2019-07-16 04:28:35 +08:00
|
|
|
refreshBanks();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FluidSynthModel::refreshBanks() {
|
2019-07-13 07:16:35 +08:00
|
|
|
ValueTree banks{"banks"};
|
2019-07-28 06:04:20 +08:00
|
|
|
fluid_sfont_t* sfont{
|
|
|
|
sfont_id == -1
|
|
|
|
? nullptr
|
|
|
|
: fluid_synth_get_sfont_by_id(synth.get(), sfont_id)
|
|
|
|
};
|
|
|
|
if (sfont) {
|
2019-07-21 01:49:39 +08:00
|
|
|
int greatestEncounteredBank{-1};
|
|
|
|
ValueTree bank;
|
2019-07-13 07:16:35 +08:00
|
|
|
|
|
|
|
fluid_sfont_iteration_start(sfont);
|
|
|
|
for(fluid_preset_t* preset {fluid_sfont_iteration_next(sfont)};
|
|
|
|
preset != nullptr;
|
|
|
|
preset = fluid_sfont_iteration_next(sfont)) {
|
2019-07-21 01:49:39 +08:00
|
|
|
int bankNum{fluid_preset_get_banknum(preset)};
|
|
|
|
if (bankNum > greatestEncounteredBank) {
|
|
|
|
if (greatestEncounteredBank > -1) {
|
|
|
|
banks.appendChild(bank, nullptr);
|
|
|
|
}
|
|
|
|
bank = { "bank", {
|
|
|
|
{ "num", bankNum }
|
|
|
|
} };
|
|
|
|
greatestEncounteredBank = bankNum;
|
2019-07-13 07:16:35 +08:00
|
|
|
}
|
2019-07-21 01:49:39 +08:00
|
|
|
bank.appendChild({ "preset", {
|
|
|
|
{ "num", fluid_preset_get_num(preset) },
|
|
|
|
{ "name", String{fluid_preset_get_name(preset)} }
|
|
|
|
}, {} }, nullptr);
|
|
|
|
}
|
|
|
|
if (greatestEncounteredBank > -1) {
|
|
|
|
banks.appendChild(bank, nullptr);
|
2019-07-13 07:16:35 +08:00
|
|
|
}
|
|
|
|
}
|
2019-07-15 00:45:08 +08:00
|
|
|
valueTreeState.state.getChildWithName("banks").copyPropertiesAndChildrenFrom(banks, nullptr);
|
2019-07-15 02:31:05 +08:00
|
|
|
valueTreeState.state.getChildWithName("banks").sendPropertyChangeMessage("synthetic");
|
2019-07-21 01:49:39 +08:00
|
|
|
|
|
|
|
#if JUCE_DEBUG
|
2019-07-28 06:04:20 +08:00
|
|
|
// unique_ptr<XmlElement> xml{valueTreeState.state.createXml()};
|
|
|
|
// Logger::outputDebugString(xml->createDocument("",false,false));
|
2019-07-21 01:49:39 +08:00
|
|
|
#endif
|
2019-07-16 04:28:35 +08:00
|
|
|
}
|
|
|
|
|
2018-04-16 04:32:26 +08:00
|
|
|
void FluidSynthModel::setSampleRate(float sampleRate) {
|
|
|
|
currentSampleRate = sampleRate;
|
2019-07-07 07:22:47 +08:00
|
|
|
// https://stackoverflow.com/a/40856043/5257399
|
|
|
|
// test if a smart pointer is null
|
|
|
|
if (!synth) {
|
2018-04-16 04:32:26 +08:00
|
|
|
// don't worry; we'll do this in initialise phase regardless
|
|
|
|
return;
|
|
|
|
}
|
2019-07-07 07:22:47 +08:00
|
|
|
fluid_synth_set_sample_rate(synth.get(), sampleRate);
|
2018-06-18 00:53:32 +08:00
|
|
|
}
|
2019-07-29 05:22:25 +08:00
|
|
|
|
|
|
|
void FluidSynthModel::processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiMessages) {
|
|
|
|
MidiBuffer processedMidi;
|
|
|
|
int time;
|
|
|
|
MidiMessage m;
|
|
|
|
|
|
|
|
for (MidiBuffer::Iterator i{midiMessages}; i.getNextEvent(m, time);) {
|
|
|
|
DEBUG_PRINT(m.getDescription());
|
|
|
|
|
|
|
|
if (m.isNoteOn()) {
|
|
|
|
fluid_synth_noteon(
|
|
|
|
synth.get(),
|
|
|
|
channel,
|
|
|
|
m.getNoteNumber(),
|
|
|
|
m.getVelocity());
|
|
|
|
} else if (m.isNoteOff()) {
|
|
|
|
fluid_synth_noteoff(
|
|
|
|
synth.get(),
|
|
|
|
channel,
|
|
|
|
m.getNoteNumber());
|
|
|
|
} else if (m.isController()) {
|
|
|
|
fluid_synth_cc(
|
|
|
|
synth.get(),
|
|
|
|
channel,
|
|
|
|
m.getControllerNumber(),
|
|
|
|
m.getControllerValue());
|
2019-07-30 06:46:38 +08:00
|
|
|
|
|
|
|
fluid_midi_control_change controllerNum{static_cast<fluid_midi_control_change>(m.getControllerNumber())};
|
|
|
|
if (auto it{controllerToParam.find(controllerNum)};
|
|
|
|
it != end(controllerToParam)) {
|
|
|
|
String parameterID{it->second};
|
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter(parameterID)};
|
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
|
|
|
*castParam = m.getControllerValue();
|
2019-07-29 05:22:25 +08:00
|
|
|
}
|
|
|
|
} else if (m.isProgramChange()) {
|
2019-08-11 06:18:43 +08:00
|
|
|
#if JUCE_DEBUG
|
|
|
|
String debug{"MIDI program change: "};
|
|
|
|
debug << m.getProgramChangeNumber();
|
|
|
|
Logger::outputDebugString(debug);
|
|
|
|
#endif
|
2019-07-29 05:22:25 +08:00
|
|
|
int result{fluid_synth_program_change(
|
|
|
|
synth.get(),
|
|
|
|
channel,
|
|
|
|
m.getProgramChangeNumber())};
|
|
|
|
if (result == FLUID_OK) {
|
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
2019-07-29 05:51:51 +08:00
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
2019-07-29 05:22:25 +08:00
|
|
|
*castParam = m.getProgramChangeNumber();
|
|
|
|
}
|
|
|
|
} else if (m.isPitchWheel()) {
|
|
|
|
fluid_synth_pitch_bend(
|
|
|
|
synth.get(),
|
|
|
|
channel,
|
|
|
|
m.getPitchWheelValue());
|
|
|
|
} else if (m.isChannelPressure()) {
|
|
|
|
fluid_synth_channel_pressure(
|
|
|
|
synth.get(),
|
|
|
|
channel,
|
|
|
|
m.getChannelPressureValue());
|
|
|
|
} else if (m.isAftertouch()) {
|
|
|
|
fluid_synth_key_pressure(
|
|
|
|
synth.get(),
|
|
|
|
channel,
|
|
|
|
m.getNoteNumber(),
|
|
|
|
m.getAfterTouchValue());
|
|
|
|
// } else if (m.isMetaEvent()) {
|
|
|
|
// fluid_midi_event_t *midi_event{new_fluid_midi_event()};
|
|
|
|
// fluid_midi_event_set_type(midi_event, static_cast<int>(MIDI_SYSTEM_RESET));
|
|
|
|
// fluid_synth_handle_midi_event(synth.get(), midi_event);
|
|
|
|
// delete_fluid_midi_event(midi_event);
|
|
|
|
} else if (m.isSysEx()) {
|
|
|
|
fluid_synth_sysex(
|
|
|
|
synth.get(),
|
|
|
|
reinterpret_cast<const char*>(m.getSysExData()),
|
|
|
|
m.getSysExDataSize(),
|
|
|
|
nullptr, // no response pointer because we have no interest in handling response currently
|
|
|
|
nullptr, // no response_len pointer because we have no interest in handling response currently
|
|
|
|
nullptr, // no handled pointer because we have no interest in handling response currently
|
|
|
|
static_cast<int>(false));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// fluid_synth_get_cc(fluidSynth, 0, 73, &pval);
|
|
|
|
// Logger::outputDebugString ( juce::String::formatted("hey: %d\n", pval) );
|
|
|
|
|
|
|
|
fluid_synth_process(
|
|
|
|
synth.get(),
|
|
|
|
buffer.getNumSamples(),
|
|
|
|
0,
|
|
|
|
nullptr,
|
|
|
|
buffer.getNumChannels(),
|
|
|
|
buffer.getArrayOfWritePointers());
|
|
|
|
}
|
2019-07-29 05:51:51 +08:00
|
|
|
|
|
|
|
int FluidSynthModel::getNumPrograms()
|
|
|
|
{
|
|
|
|
return 128; // NB: some hosts don't cope very well if you tell them there are 0 programs,
|
|
|
|
// so this should be at least 1, even if you're not really implementing programs.
|
|
|
|
}
|
|
|
|
|
|
|
|
int FluidSynthModel::getCurrentProgram()
|
|
|
|
{
|
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
|
|
|
return castParam->get();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FluidSynthModel::setCurrentProgram(int index)
|
|
|
|
{
|
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
2019-07-31 04:36:54 +08:00
|
|
|
// setCurrentProgram() gets invoked from non-message thread.
|
|
|
|
// AudioParameterInt#operator= will activate any listeners of audio parameter "preset".
|
|
|
|
// This includes TableComponent, who will update its UI.
|
|
|
|
// we need to lock the message thread whilst it does that UI update.
|
|
|
|
const MessageManagerLock mmLock;
|
2019-07-29 05:51:51 +08:00
|
|
|
*castParam = index;
|
|
|
|
}
|
|
|
|
|
|
|
|
const String FluidSynthModel::getProgramName(int index)
|
|
|
|
{
|
2019-07-31 04:36:54 +08:00
|
|
|
fluid_sfont_t* sfont{
|
|
|
|
sfont_id == -1
|
|
|
|
? nullptr
|
|
|
|
: fluid_synth_get_sfont_by_id(synth.get(), sfont_id)
|
|
|
|
};
|
|
|
|
if (!sfont) {
|
|
|
|
String presetName{"Preset "};
|
|
|
|
return presetName << index;
|
|
|
|
}
|
|
|
|
RangedAudioParameter *param{valueTreeState.getParameter("bank")};
|
|
|
|
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
|
|
|
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
|
|
|
int bank{castParam->get()};
|
2019-07-29 06:02:22 +08:00
|
|
|
|
2019-07-31 04:36:54 +08:00
|
|
|
fluid_preset_t *preset{fluid_sfont_get_preset(
|
|
|
|
sfont,
|
|
|
|
bank,
|
|
|
|
index)};
|
|
|
|
if (!preset) {
|
|
|
|
String presetName{"Preset "};
|
|
|
|
return presetName << index;
|
|
|
|
}
|
|
|
|
return {fluid_preset_get_name(preset)};
|
2019-07-29 05:51:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void FluidSynthModel::changeProgramName(int index, const String& newName)
|
|
|
|
{
|
|
|
|
// no-op; we don't support modifying the soundfont, so let's not support modification of preset names.
|
|
|
|
}
|