diff --git a/Source/FluidSynthModel.cpp b/Source/FluidSynthModel.cpp index d902d02..d25af90 100644 --- a/Source/FluidSynthModel.cpp +++ b/Source/FluidSynthModel.cpp @@ -3,6 +3,7 @@ // #include +#include #include #include "FluidSynthModel.h" #include "MidiConstants.h" @@ -10,6 +11,26 @@ using namespace std; +const map 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 FluidSynthModel::paramToController{[]{ + map map; + transform( + controllerToParam.begin(), + controllerToParam.end(), + inserter(map, map.begin()), + [](const pair& pair) { + return make_pair(pair.second, pair.first); + }); + return map; +}()}; + FluidSynthModel::FluidSynthModel( AudioProcessorValueTreeState& valueTreeState ) @@ -22,10 +43,16 @@ FluidSynthModel::FluidSynthModel( { valueTreeState.addParameterListener("bank", this); valueTreeState.addParameterListener("preset", this); + for (const auto &[param, controller]: paramToController) { + valueTreeState.addParameterListener(param, this); + } valueTreeState.state.addListener(this); } FluidSynthModel::~FluidSynthModel() { + for (const auto &[param, controller]: paramToController) { + valueTreeState.removeParameterListener(param, this); + } valueTreeState.removeParameterListener("bank", this); valueTreeState.removeParameterListener("preset", this); valueTreeState.state.removeListener(this); @@ -46,44 +73,26 @@ void FluidSynthModel::initialise() { synth = { new_fluid_synth(settings.get()), delete_fluid_synth }; fluid_synth_set_sample_rate(synth.get(), currentSampleRate); - - // valueTreeState.getParameter("soundFontPath")->getValue(); - // RangedAudioParameter *param {valueTreeState.getParameter("release")}; - // jassert(dynamic_cast (param) != nullptr); - // AudioParameterInt* castParam {dynamic_cast (param)}; - // *castParam = m.getControllerValue(); ValueTree soundFont{valueTreeState.state.getChildWithName("soundFont")}; String path{soundFont.getProperty("path", "")}; loadFont(path); + // I can't hear a damned thing 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(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() // all SOUND_CTRL are inited with value of 64, not zero. // "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(controller), 0); + } + + // http://www.synthfont.com/SoundFont_NRPNs.PDF + float env_amount{20000.0f}; unique_ptr mod{new_fluid_mod(), delete_fluid_mod}; -// fluid_mod_set_source1(mod.get(), static_cast(SOUND_CTRL2), // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance) FLUID_MOD_CC @@ -104,7 +113,6 @@ void FluidSynthModel::initialise() { | FLUID_MOD_POSITIVE); fluid_mod_set_source2(mod.get(), 0, 0); 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_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") { int bank, preset; { - RangedAudioParameter *param {valueTreeState.getParameter("bank")}; + RangedAudioParameter *param{valueTreeState.getParameter("bank")}; jassert(dynamic_cast (param) != nullptr); - AudioParameterInt* castParam {dynamic_cast (param)}; + AudioParameterInt* castParam{dynamic_cast(param)}; bank = castParam->get(); } { - RangedAudioParameter *param {valueTreeState.getParameter("preset")}; - jassert(dynamic_cast (param) != nullptr); - AudioParameterInt* castParam {dynamic_cast (param)}; + RangedAudioParameter *param{valueTreeState.getParameter("preset")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; preset = castParam->get(); } 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") { int bank, preset; { - RangedAudioParameter *param {valueTreeState.getParameter("bank")}; - jassert(dynamic_cast (param) != nullptr); - AudioParameterInt* castParam {dynamic_cast (param)}; + RangedAudioParameter *param{valueTreeState.getParameter("bank")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; bank = castParam->get(); } { - RangedAudioParameter *param {valueTreeState.getParameter("preset")}; - jassert(dynamic_cast (param) != nullptr); - AudioParameterInt* castParam {dynamic_cast (param)}; + RangedAudioParameter *param{valueTreeState.getParameter("preset")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; preset = castParam->get(); } 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, static_cast(bankOffset + bank), static_cast(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(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + int value{castParam->get()}; + int controllerNumber{static_cast(it->second)}; + + fluid_synth_cc( + synth.get(), + channel, + controllerNumber, + value); } } @@ -329,54 +352,15 @@ void FluidSynthModel::processBlock(AudioBuffer& buffer, MidiBuffer& midiM channel, m.getControllerNumber(), m.getControllerValue()); - - switch(static_cast(m.getControllerNumber())) { - case SOUND_CTRL2: { // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance) - // valueTreeState.state.setProperty({"filterResonance"}, m.getControllerValue(), nullptr); - RangedAudioParameter *param{valueTreeState.getParameter("filterResonance")}; - jassert(dynamic_cast(param) != nullptr); - AudioParameterInt* castParam {dynamic_cast(param)}; - *castParam = m.getControllerValue(); - break; - } - case SOUND_CTRL3: { // MIDI CC 72 Release time - RangedAudioParameter *param{valueTreeState.getParameter("release")}; - jassert(dynamic_cast(param) != nullptr); - AudioParameterInt* castParam {dynamic_cast(param)}; - *castParam = m.getControllerValue(); - break; - } - case SOUND_CTRL4: { // MIDI CC 73 Attack time - RangedAudioParameter *param{valueTreeState.getParameter("release")}; - jassert(dynamic_cast(param) != nullptr); - AudioParameterInt* castParam {dynamic_cast(param)}; - *castParam = m.getControllerValue(); - break; - } - case SOUND_CTRL5: { // MIDI CC 74 Brightness (cutoff frequency, FILTERFC) - RangedAudioParameter *param{valueTreeState.getParameter("filterCutOff")}; - jassert(dynamic_cast(param) != nullptr); - AudioParameterInt* castParam {dynamic_cast(param)}; - *castParam = m.getControllerValue(); - break; - } - case SOUND_CTRL6: { // MIDI CC 75 Decay Time - RangedAudioParameter *param{valueTreeState.getParameter("decay")}; - jassert(dynamic_cast(param) != nullptr); - AudioParameterInt* castParam {dynamic_cast(param)}; - *castParam = m.getControllerValue(); - break; - } - case SOUND_CTRL10: { // MIDI CC 79 undefined - RangedAudioParameter *param{valueTreeState.getParameter("sustain")}; - jassert(dynamic_cast(param) != nullptr); - AudioParameterInt* castParam{dynamic_cast(param)}; - *castParam = m.getControllerValue(); - break; - } - default: { - break; - } + + fluid_midi_control_change controllerNum{static_cast(m.getControllerNumber())}; + if (auto it{controllerToParam.find(controllerNum)}; + it != end(controllerToParam)) { + String parameterID{it->second}; + RangedAudioParameter *param{valueTreeState.getParameter(parameterID)}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + *castParam = m.getControllerValue(); } } else if (m.isProgramChange()) { int result{fluid_synth_program_change( diff --git a/Source/FluidSynthModel.h b/Source/FluidSynthModel.h index f416a48..d545c67 100644 --- a/Source/FluidSynthModel.h +++ b/Source/FluidSynthModel.h @@ -7,6 +7,8 @@ #include "../JuceLibraryCode/JuceHeader.h" #include #include +#include +#include "MidiConstants.h" using namespace std; @@ -53,6 +55,12 @@ public: void changeProgramName(int index, const String& newName); private: + // static const StringArray controllerParams; + + // there's no bimap in the standard library! + static const map controllerToParam; + static const map paramToController; + void refreshBanks(); AudioProcessorValueTreeState& valueTreeState; diff --git a/Source/SlidersComponent.cpp b/Source/SlidersComponent.cpp index 03e1a43..ba6f81c 100644 --- a/Source/SlidersComponent.cpp +++ b/Source/SlidersComponent.cpp @@ -14,15 +14,14 @@ using SliderAttachment = AudioProcessorValueTreeState::SliderAttachment; std::function SlidersComponent::makeSliderListener(Slider& slider, int controller) { return [this, controller, &slider]{ + // RangedAudioParameter *param{valueTreeState.getParameter("release")}; + // jassert(dynamic_cast(param) != nullptr); + // AudioParameterInt* castParam{dynamic_cast(param)}; + // int value{castParam->get()}; - RangedAudioParameter *param {valueTreeState.getParameter("release")}; - jassert(dynamic_cast (param) != nullptr); - AudioParameterInt* castParam {dynamic_cast (param)}; - int value {castParam->get()}; - - String s{"slider "}; - s << slider.getComponentID() << ", controller " << controller << ", value " << slider.getValue() << ", xmlReleaseValue " << value; - DEBUG_PRINT(s); + // String s{"slider "}; + // s << slider.getComponentID() << ", controller " << controller << ", value " << slider.getValue() << ", xmlReleaseValue " << value; + // DEBUG_PRINT(s); // slider.setValue(slider.getValue(), NotificationType::dontSendNotification); fluidSynthModel.setControllerValue(controller, slider.getValue()); };