From d4a060b7691ce2dda16d36781bab317679c4e204 Mon Sep 17 00:00:00 2001 From: Alex Birch Date: Sun, 28 Jul 2019 22:22:25 +0100 Subject: [PATCH] move message-routing, midi-rendering concerns into FluidSynthModel --- Source/FluidSynthModel.cpp | 130 ++++++++++++++++++++++++++++++++ Source/FluidSynthModel.h | 2 + Source/PluginProcessor.cpp | 148 ++----------------------------------- 3 files changed, 140 insertions(+), 140 deletions(-) diff --git a/Source/FluidSynthModel.cpp b/Source/FluidSynthModel.cpp index 20fd318..052bf28 100644 --- a/Source/FluidSynthModel.cpp +++ b/Source/FluidSynthModel.cpp @@ -610,3 +610,133 @@ void FluidSynthModel::setSampleRate(float sampleRate) { } fluid_synth_set_sample_rate(synth.get(), sampleRate); } + +void FluidSynthModel::processBlock(AudioBuffer& 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()); + + 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; + } + } + } else if (m.isProgramChange()) { + int result{fluid_synth_program_change( + synth.get(), + channel, + m.getProgramChangeNumber())}; + if (result == FLUID_OK) { + RangedAudioParameter *param{valueTreeState.getParameter("preset")}; + jassert(dynamic_cast (param) != nullptr); + AudioParameterInt* castParam {dynamic_cast (param)}; + *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(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(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(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()); +} diff --git a/Source/FluidSynthModel.h b/Source/FluidSynthModel.h index 3978721..c0df803 100644 --- a/Source/FluidSynthModel.h +++ b/Source/FluidSynthModel.h @@ -39,6 +39,8 @@ public: // void onFileNameChanged(const String &absPath, int bank, int preset); void setControllerValue(int controller, int value); + void processBlock(AudioBuffer& buffer, MidiBuffer& midiMessages); + //============================================================================== /** Used to receive callbacks when a button is clicked. diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 94b270c..3b6aaee 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -101,14 +101,14 @@ void JuicySFAudioProcessor::initialiseSynth() { // fluidSynth = fluidSynthModel.getSynth(); - const int numVoices = 8; + // const int numVoices = 8; // Add some voices... - for (int i = numVoices; --i >= 0;) - synth.addVoice(new SoundfontSynthVoice(fluidSynthModel.getSynth())); + // for (int i = numVoices; --i >= 0;) + // synth.addVoice(new SoundfontSynthVoice(fluidSynthModel.getSynth())); // ..and give the synth a sound to play - synth.addSound(new SoundfontSynthSound()); + // synth.addSound(new SoundfontSynthSound()); } //============================================================================== @@ -208,148 +208,16 @@ AudioProcessor::BusesProperties JuicySFAudioProcessor::getBusesProperties() { void JuicySFAudioProcessor::processBlock(AudioBuffer& buffer, MidiBuffer& midiMessages) { jassert (!isUsingDoublePrecision()); - const int numSamples = buffer.getNumSamples(); + const int numSamples{buffer.getNumSamples()}; // Now pass any incoming midi messages to our keyboard state object, and let it // add messages to the buffer if the user is clicking on the on-screen keys - keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true); + keyboardState.processNextMidiBuffer(midiMessages, 0, numSamples, true); - MidiBuffer processedMidi; - int time; - MidiMessage m; - - // TODO: factor into a MidiMessageCollector - for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);) { - DEBUG_PRINT ( m.getDescription() ); - - // explicitly not handling note_on/off, or pitch_bend, because these are (for better or worse) - // responsibilities of SoundfontSynthVoice. - // well, by that logic maybe I should move program change onto Voice. but it doesn't feel like a per-voice concern. - if (m.isController()) { - // shared_ptr midi_event{ - // new_fluid_midi_event(), - // [](fluid_midi_event_t *event) { - // delete_fluid_midi_event(midi_event); - // }}; - fluid_midi_event_t *midi_event(new_fluid_midi_event()); - fluid_midi_event_set_type(midi_event, static_cast(CONTROL_CHANGE)); - fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel()); - fluid_midi_event_set_control(midi_event, m.getControllerNumber()); - fluid_midi_event_set_value(midi_event, m.getControllerValue()); - fluid_synth_handle_midi_event(fluidSynthModel.getSynth().get(), midi_event); - delete_fluid_midi_event(midi_event); - - 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; - } - } - - // sharedParams->acceptMidiControlEvent(m.getControllerNumber(), m.getControllerValue()); - - // AudioProcessorEditor* editor{getActiveEditor()}; - // jassert(dynamic_cast (editor) != nullptr); - // ExposesComponents* exposesComponents{dynamic_cast(editor)}; - // exposesComponents->getSliders().acceptMidiControlEvent(m.getControllerNumber(), m.getControllerValue()); - } else if (m.isProgramChange()) { - fluid_midi_event_t *midi_event(new_fluid_midi_event()); - fluid_midi_event_set_type(midi_event, static_cast(PROGRAM_CHANGE)); - fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel()); - fluid_midi_event_set_program(midi_event, m.getProgramChangeNumber()); - fluid_synth_handle_midi_event(fluidSynthModel.getSynth().get(), midi_event); - delete_fluid_midi_event(midi_event); - } else if (m.isPitchWheel()) { - fluid_midi_event_t *midi_event(new_fluid_midi_event()); - fluid_midi_event_set_type(midi_event, static_cast(PITCH_BEND)); - fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel()); - fluid_midi_event_set_pitch(midi_event, m.getPitchWheelValue()); - fluid_synth_handle_midi_event(fluidSynthModel.getSynth().get(), midi_event); - delete_fluid_midi_event(midi_event); - } else if (m.isChannelPressure()) { - fluid_midi_event_t *midi_event(new_fluid_midi_event()); - fluid_midi_event_set_type(midi_event, static_cast(CHANNEL_PRESSURE)); - fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel()); - fluid_midi_event_set_program(midi_event, m.getChannelPressureValue()); - fluid_synth_handle_midi_event(fluidSynthModel.getSynth().get(), midi_event); - delete_fluid_midi_event(midi_event); - } else if (m.isAftertouch()) { - fluid_midi_event_t *midi_event(new_fluid_midi_event()); - fluid_midi_event_set_type(midi_event, static_cast(KEY_PRESSURE)); - fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel()); - fluid_midi_event_set_key(midi_event, m.getNoteNumber()); - fluid_midi_event_set_value(midi_event, m.getAfterTouchValue()); - fluid_synth_handle_midi_event(fluidSynthModel.getSynth().get(), midi_event); - delete_fluid_midi_event(midi_event); -// } else if (m.isMetaEvent()) { -// fluid_midi_event_t *midi_event(new_fluid_midi_event()); -// fluid_midi_event_set_type(midi_event, static_cast(MIDI_SYSTEM_RESET)); -// fluid_synth_handle_midi_event(fluidSynthModel.getSynth().get(), midi_event); -// delete_fluid_midi_event(midi_event); - } else if (m.isSysEx()) { - fluid_midi_event_t *midi_event(new_fluid_midi_event()); - fluid_midi_event_set_type(midi_event, static_cast(MIDI_SYSEX)); - // I assume that the MidiMessage's sysex buffer would be freed anyway when MidiMessage is destroyed, so set dynamic=false - // to ensure that fluidsynth does not attempt to free the sysex buffer during delete_fluid_midi_event() - fluid_midi_event_set_sysex(midi_event, const_cast(m.getSysExData()), m.getSysExDataSize(), static_cast(false)); - fluid_synth_handle_midi_event(fluidSynthModel.getSynth().get(), midi_event); - delete_fluid_midi_event(midi_event); - } - } - -// int pval; - // 73: 64 attack - // 75: decay - // 79: sustain - // 72: 64 release -// fluid_synth_get_cc(fluidSynth, 0, 73, &pval); -// Logger::outputDebugString ( juce::String::formatted("hey: %d\n", pval) ); + fluidSynthModel.processBlock(buffer, midiMessages); // and now get our synth to process these midi events and generate its output. - synth.renderNextBlock (buffer, midiMessages, 0, numSamples); - fluid_synth_process(fluidSynthModel.getSynth().get(), numSamples, 0, nullptr, buffer.getNumChannels(), buffer.getArrayOfWritePointers()); + // synth.renderNextBlock(buffer, midiMessages, 0, numSamples); // (see juce_VST3_Wrapper.cpp for the assertion this would trip otherwise) // we are !JucePlugin_ProducesMidiOutput, so clear remaining MIDI messages from our buffer