restore ADSR and filter from save. shorten switch statements into map lookups.

This commit is contained in:
Alex Birch 2019-07-29 23:46:38 +01:00
parent 6f80200219
commit 79c023d466
No known key found for this signature in database
GPG Key ID: 305EB1F98D44ACBA
3 changed files with 85 additions and 94 deletions

View File

@ -3,6 +3,7 @@
// //
#include <iostream> #include <iostream>
#include <iterator>
#include <fluidsynth.h> #include <fluidsynth.h>
#include "FluidSynthModel.h" #include "FluidSynthModel.h"
#include "MidiConstants.h" #include "MidiConstants.h"
@ -10,6 +11,26 @@
using namespace std; using namespace std;
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;
}()};
FluidSynthModel::FluidSynthModel( FluidSynthModel::FluidSynthModel(
AudioProcessorValueTreeState& valueTreeState AudioProcessorValueTreeState& valueTreeState
) )
@ -22,10 +43,16 @@ FluidSynthModel::FluidSynthModel(
{ {
valueTreeState.addParameterListener("bank", this); valueTreeState.addParameterListener("bank", this);
valueTreeState.addParameterListener("preset", this); valueTreeState.addParameterListener("preset", this);
for (const auto &[param, controller]: paramToController) {
valueTreeState.addParameterListener(param, this);
}
valueTreeState.state.addListener(this); valueTreeState.state.addListener(this);
} }
FluidSynthModel::~FluidSynthModel() { FluidSynthModel::~FluidSynthModel() {
for (const auto &[param, controller]: paramToController) {
valueTreeState.removeParameterListener(param, this);
}
valueTreeState.removeParameterListener("bank", this); valueTreeState.removeParameterListener("bank", this);
valueTreeState.removeParameterListener("preset", this); valueTreeState.removeParameterListener("preset", this);
valueTreeState.state.removeListener(this); valueTreeState.state.removeListener(this);
@ -47,43 +74,25 @@ void FluidSynthModel::initialise() {
synth = { new_fluid_synth(settings.get()), delete_fluid_synth }; synth = { new_fluid_synth(settings.get()), delete_fluid_synth };
fluid_synth_set_sample_rate(synth.get(), currentSampleRate); fluid_synth_set_sample_rate(synth.get(), currentSampleRate);
// valueTreeState.getParameter("soundFontPath")->getValue();
// RangedAudioParameter *param {valueTreeState.getParameter("release")};
// jassert(dynamic_cast<AudioParameterInt*> (param) != nullptr);
// AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*> (param)};
// *castParam = m.getControllerValue();
ValueTree soundFont{valueTreeState.state.getChildWithName("soundFont")}; ValueTree soundFont{valueTreeState.state.getChildWithName("soundFont")};
String path{soundFont.getProperty("path", "")}; String path{soundFont.getProperty("path", "")};
loadFont(path); loadFont(path);
// I can't hear a damned thing
fluid_synth_set_gain(synth.get(), 2.0); fluid_synth_set_gain(synth.get(), 2.0);
fluid_midi_control_change controllers[]{SOUND_CTRL2, SOUND_CTRL3, SOUND_CTRL4, SOUND_CTRL5, SOUND_CTRL6, SOUND_CTRL10};
for(fluid_midi_control_change controller : controllers) {
setControllerValue(static_cast<int>(controller), 0);
}
// fluid_synth_bank_select(synth, 0, 3);
// fluid_handle_inst
// driver = new_fluid_audio_driver(settings, synth);
// changePreset(128, 13);
// float env_amount(12000.0f);
// http://www.synthfont.com/SoundFont_NRPNs.PDF
float env_amount{20000.0f};
// float env_amount(24000.0f);
// note: fluid_chan.c#fluid_channel_init_ctrl() // note: fluid_chan.c#fluid_channel_init_ctrl()
// all SOUND_CTRL are inited with value of 64, not zero. // all SOUND_CTRL are inited with value of 64, not zero.
// "Just like panning, a value of 64 indicates no change for sound ctrls" // "Just like panning, a value of 64 indicates no change for sound ctrls"
// and yet, I'm finding that default modulators start at MIN, so we are forced to start at 0 and climb from there
for (const auto &[controller, param]: controllerToParam) {
setControllerValue(static_cast<int>(controller), 0);
}
// http://www.synthfont.com/SoundFont_NRPNs.PDF
float env_amount{20000.0f};
unique_ptr<fluid_mod_t, decltype(&delete_fluid_mod)> mod{new_fluid_mod(), delete_fluid_mod}; unique_ptr<fluid_mod_t, decltype(&delete_fluid_mod)> mod{new_fluid_mod(), delete_fluid_mod};
//
fluid_mod_set_source1(mod.get(), fluid_mod_set_source1(mod.get(),
static_cast<int>(SOUND_CTRL2), // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance) static_cast<int>(SOUND_CTRL2), // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance)
FLUID_MOD_CC FLUID_MOD_CC
@ -104,7 +113,6 @@ void FluidSynthModel::initialise() {
| FLUID_MOD_POSITIVE); | FLUID_MOD_POSITIVE);
fluid_mod_set_source2(mod.get(), 0, 0); fluid_mod_set_source2(mod.get(), 0, 0);
fluid_mod_set_dest(mod.get(), GEN_VOLENVRELEASE); fluid_mod_set_dest(mod.get(), GEN_VOLENVRELEASE);
// fluid_mod_set_amount(mod.get(), 15200.0f);
fluid_mod_set_amount(mod.get(), env_amount); fluid_mod_set_amount(mod.get(), env_amount);
fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
@ -164,15 +172,15 @@ void FluidSynthModel::parameterChanged(const String& parameterID, float newValue
if (parameterID == "bank") { if (parameterID == "bank") {
int bank, preset; int bank, preset;
{ {
RangedAudioParameter *param {valueTreeState.getParameter("bank")}; RangedAudioParameter *param{valueTreeState.getParameter("bank")};
jassert(dynamic_cast<AudioParameterInt*> (param) != nullptr); jassert(dynamic_cast<AudioParameterInt*> (param) != nullptr);
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*> (param)}; AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
bank = castParam->get(); bank = castParam->get();
} }
{ {
RangedAudioParameter *param {valueTreeState.getParameter("preset")}; RangedAudioParameter *param{valueTreeState.getParameter("preset")};
jassert(dynamic_cast<AudioParameterInt*> (param) != nullptr); jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*> (param)}; AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
preset = castParam->get(); preset = castParam->get();
} }
int bankOffset{fluid_synth_get_bank_offset(synth.get(), sfont_id)}; int bankOffset{fluid_synth_get_bank_offset(synth.get(), sfont_id)};
@ -185,15 +193,15 @@ void FluidSynthModel::parameterChanged(const String& parameterID, float newValue
} else if (parameterID == "preset") { } else if (parameterID == "preset") {
int bank, preset; int bank, preset;
{ {
RangedAudioParameter *param {valueTreeState.getParameter("bank")}; RangedAudioParameter *param{valueTreeState.getParameter("bank")};
jassert(dynamic_cast<AudioParameterInt*> (param) != nullptr); jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*> (param)}; AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
bank = castParam->get(); bank = castParam->get();
} }
{ {
RangedAudioParameter *param {valueTreeState.getParameter("preset")}; RangedAudioParameter *param{valueTreeState.getParameter("preset")};
jassert(dynamic_cast<AudioParameterInt*> (param) != nullptr); jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*> (param)}; AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
preset = castParam->get(); preset = castParam->get();
} }
int bankOffset{fluid_synth_get_bank_offset(synth.get(), sfont_id)}; int bankOffset{fluid_synth_get_bank_offset(synth.get(), sfont_id)};
@ -203,6 +211,21 @@ void FluidSynthModel::parameterChanged(const String& parameterID, float newValue
sfont_id, sfont_id,
static_cast<unsigned int>(bankOffset + bank), static_cast<unsigned int>(bankOffset + bank),
static_cast<unsigned int>(preset)); static_cast<unsigned int>(preset));
} 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);
} }
} }
@ -330,53 +353,14 @@ void FluidSynthModel::processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiM
m.getControllerNumber(), m.getControllerNumber(),
m.getControllerValue()); m.getControllerValue());
switch(static_cast<fluid_midi_control_change>(m.getControllerNumber())) { fluid_midi_control_change controllerNum{static_cast<fluid_midi_control_change>(m.getControllerNumber())};
case SOUND_CTRL2: { // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance) if (auto it{controllerToParam.find(controllerNum)};
// valueTreeState.state.setProperty({"filterResonance"}, m.getControllerValue(), nullptr); it != end(controllerToParam)) {
RangedAudioParameter *param{valueTreeState.getParameter("filterResonance")}; String parameterID{it->second};
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr); RangedAudioParameter *param{valueTreeState.getParameter(parameterID)};
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*>(param)}; jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
*castParam = m.getControllerValue(); AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
break; *castParam = m.getControllerValue();
}
case SOUND_CTRL3: { // MIDI CC 72 Release time
RangedAudioParameter *param{valueTreeState.getParameter("release")};
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*>(param)};
*castParam = m.getControllerValue();
break;
}
case SOUND_CTRL4: { // MIDI CC 73 Attack time
RangedAudioParameter *param{valueTreeState.getParameter("release")};
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*>(param)};
*castParam = m.getControllerValue();
break;
}
case SOUND_CTRL5: { // MIDI CC 74 Brightness (cutoff frequency, FILTERFC)
RangedAudioParameter *param{valueTreeState.getParameter("filterCutOff")};
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*>(param)};
*castParam = m.getControllerValue();
break;
}
case SOUND_CTRL6: { // MIDI CC 75 Decay Time
RangedAudioParameter *param{valueTreeState.getParameter("decay")};
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*>(param)};
*castParam = m.getControllerValue();
break;
}
case SOUND_CTRL10: { // MIDI CC 79 undefined
RangedAudioParameter *param{valueTreeState.getParameter("sustain")};
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
*castParam = m.getControllerValue();
break;
}
default: {
break;
}
} }
} else if (m.isProgramChange()) { } else if (m.isProgramChange()) {
int result{fluid_synth_program_change( int result{fluid_synth_program_change(

View File

@ -7,6 +7,8 @@
#include "../JuceLibraryCode/JuceHeader.h" #include "../JuceLibraryCode/JuceHeader.h"
#include <fluidsynth.h> #include <fluidsynth.h>
#include <memory> #include <memory>
#include <map>
#include "MidiConstants.h"
using namespace std; using namespace std;
@ -53,6 +55,12 @@ public:
void changeProgramName(int index, const String& newName); void changeProgramName(int index, const String& newName);
private: private:
// static const StringArray controllerParams;
// there's no bimap in the standard library!
static const map<fluid_midi_control_change, String> controllerToParam;
static const map<String, fluid_midi_control_change> paramToController;
void refreshBanks(); void refreshBanks();
AudioProcessorValueTreeState& valueTreeState; AudioProcessorValueTreeState& valueTreeState;

View File

@ -14,15 +14,14 @@ using SliderAttachment = AudioProcessorValueTreeState::SliderAttachment;
std::function<void()> SlidersComponent::makeSliderListener(Slider& slider, int controller) { std::function<void()> SlidersComponent::makeSliderListener(Slider& slider, int controller) {
return [this, controller, &slider]{ return [this, controller, &slider]{
// RangedAudioParameter *param{valueTreeState.getParameter("release")};
// jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
// AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
// int value{castParam->get()};
RangedAudioParameter *param {valueTreeState.getParameter("release")}; // String s{"slider "};
jassert(dynamic_cast<AudioParameterInt*> (param) != nullptr); // s << slider.getComponentID() << ", controller " << controller << ", value " << slider.getValue() << ", xmlReleaseValue " << value;
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*> (param)}; // DEBUG_PRINT(s);
int value {castParam->get()};
String s{"slider "};
s << slider.getComponentID() << ", controller " << controller << ", value " << slider.getValue() << ", xmlReleaseValue " << value;
DEBUG_PRINT(s);
// slider.setValue(slider.getValue(), NotificationType::dontSendNotification); // slider.setValue(slider.getValue(), NotificationType::dontSendNotification);
fluidSynthModel.setControllerValue(controller, slider.getValue()); fluidSynthModel.setControllerValue(controller, slider.getValue());
}; };