From 90664b42df2771df0d5415b7e9b314b2eae8e73e Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 23 Sep 2020 17:50:26 +0100 Subject: [PATCH] VST3: Fix issue where aftertouch messages from the host were incorrectly converted to LegacyMIDICCOut messages --- .../juce_audio_basics/midi/juce_MidiMessage.h | 2 +- .../VST3/juce_VST3_Wrapper.cpp | 2 +- .../format_types/juce_VST3Common.h | 99 ++++++++++++++++--- .../format_types/juce_VST3PluginFormat.cpp | 7 +- 4 files changed, 89 insertions(+), 21 deletions(-) diff --git a/modules/juce_audio_basics/midi/juce_MidiMessage.h b/modules/juce_audio_basics/midi/juce_MidiMessage.h index 2d9e5adf0e0f..b72f9ad5c00e 100644 --- a/modules/juce_audio_basics/midi/juce_MidiMessage.h +++ b/modules/juce_audio_basics/midi/juce_MidiMessage.h @@ -401,7 +401,7 @@ class JUCE_API MidiMessage /** Returns true if the message is an aftertouch event. For aftertouch events, use the getNoteNumber() method to find out the key - that it applies to, and getAftertouchValue() to find out the amount. Use + that it applies to, and getAfterTouchValue() to find out the amount. Use getChannel() to find out the channel. @see getAftertouchValue, getNoteNumber diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index f0b9ac886d57..3e63d337223d 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -2657,7 +2657,7 @@ class JuceVST3Component : public Vst::IComponent, #if JucePlugin_ProducesMidiOutput if (isMidiOutputBusEnabled && data.outputEvents != nullptr) - MidiEventList::toEventList (*data.outputEvents, midiBuffer); + MidiEventList::pluginToHostEventList (*data.outputEvents, midiBuffer); #endif return kResultTrue; diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index 6372bdbce3b6..e7a669989b5b 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -401,8 +401,8 @@ class ComSmartPtr class MidiEventList : public Steinberg::Vst::IEventList { public: - MidiEventList() {} - virtual ~MidiEventList() {} + MidiEventList() = default; + virtual ~MidiEventList() = default; JUCE_DECLARE_VST3_COM_REF_METHODS JUCE_DECLARE_VST3_COM_QUERY_METHODS @@ -455,9 +455,43 @@ class MidiEventList : public Steinberg::Vst::IEventList } } + static void hostToPluginEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer, + Steinberg::Vst::IParameterChanges* parameterChanges, + Steinberg::Vst::IMidiMapping* midiMapping) + { + toEventList (result, + midiBuffer, + parameterChanges, + midiMapping, + EventConversionKind::hostToPlugin); + } + + static void pluginToHostEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer) + { + toEventList (result, + midiBuffer, + nullptr, + nullptr, + EventConversionKind::pluginToHost); + } + +private: + enum class EventConversionKind + { + // Hosted plugins don't expect to receive LegacyMIDICCEvents messages from the host, + // so if we're converting midi from the host to an eventlist, this mode will avoid + // converting to Legacy events where possible. + hostToPlugin, + + // If plugins generate MIDI internally, then where possible we should preserve + // these messages as LegacyMIDICCOut events. + pluginToHost + }; + static void toEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer, - Steinberg::Vst::IParameterChanges* parameterChanges = nullptr, - Steinberg::Vst::IMidiMapping* midiMapping = nullptr) + Steinberg::Vst::IParameterChanges* parameterChanges, + Steinberg::Vst::IMidiMapping* midiMapping, + EventConversionKind kind) { enum { maxNumEvents = 2048 }; // Steinberg's Host Checker states that no more than 2048 events are allowed at once int numEvents = 0; @@ -491,7 +525,7 @@ class MidiEventList : public Steinberg::Vst::IEventList } } - auto maybeEvent = createVstEvent (msg, metadata.data); + auto maybeEvent = createVstEvent (msg, metadata.data, kind); if (! maybeEvent.isValid) continue; @@ -503,7 +537,6 @@ class MidiEventList : public Steinberg::Vst::IEventList } } -private: Array events; Atomic refCount; @@ -562,6 +595,17 @@ class MidiEventList : public Steinberg::Vst::IEventList return e; } + static Steinberg::Vst::Event createPolyPressureEvent (const MidiMessage& msg) + { + Steinberg::Vst::Event e{}; + e.type = Steinberg::Vst::Event::kPolyPressureEvent; + e.polyPressure.channel = createSafeChannel (msg.getChannel()); + e.polyPressure.pitch = createSafeNote (msg.getNoteNumber()); + e.polyPressure.pressure = normaliseMidiValue (msg.getAfterTouchValue()); + e.polyPressure.noteId = -1; + return e; + } + static Steinberg::Vst::Event createChannelPressureEvent (const MidiMessage& msg) noexcept { return createLegacyMIDIEvent (msg.getChannel(), @@ -617,7 +661,8 @@ class MidiEventList : public Steinberg::Vst::IEventList }; static BasicOptional createVstEvent (const MidiMessage& msg, - const uint8* midiEventData) noexcept + const uint8* midiEventData, + EventConversionKind kind) noexcept { if (msg.isNoteOn()) return createNoteOnEvent (msg); @@ -643,11 +688,20 @@ class MidiEventList : public Steinberg::Vst::IEventList if (msg.isQuarterFrame()) return createCtrlQuarterFrameEvent (msg); - // VST3 gives us two ways to communicate poly pressure changes. - // There's a dedicated PolyPressureEvent, and also a LegacyMIDICCOutEvent with a - // `controlNumber` of `kCtrlPolyPressure`. We're sending the LegacyMIDI version. if (msg.isAftertouch()) - return createCtrlPolyPressureEvent (msg); + { + switch (kind) + { + case EventConversionKind::hostToPlugin: + return createPolyPressureEvent (msg); + + case EventConversionKind::pluginToHost: + return createCtrlPolyPressureEvent (msg); + } + + jassertfalse; + return {}; + } return {}; } @@ -738,13 +792,26 @@ class MidiEventList : public Steinberg::Vst::IEventList static bool toVst3ControlEvent (const MidiMessage& msg, Vst3MidiControlEvent& result) { - result.controllerNumber = -1; + if (msg.isController()) + { + result = { (Steinberg::Vst::CtrlNumber) msg.getControllerNumber(), msg.getControllerValue() / 127.0}; + return true; + } - if (msg.isController()) result = { (Steinberg::Vst::CtrlNumber) msg.getControllerNumber(), msg.getControllerValue() / 127.0}; - else if (msg.isPitchWheel()) result = { Steinberg::Vst::kPitchBend, msg.getPitchWheelValue() / 16383.0}; - else if (msg.isAftertouch()) result = { Steinberg::Vst::kAfterTouch, msg.getAfterTouchValue() / 127.0}; + if (msg.isPitchWheel()) + { + result = { Steinberg::Vst::kPitchBend, msg.getPitchWheelValue() / 16383.0}; + return true; + } + + if (msg.isChannelPressure()) + { + result = { Steinberg::Vst::kAfterTouch, msg.getChannelPressureValue() / 127.0}; + return true; + } - return (result.controllerNumber != -1); + result.controllerNumber = -1; + return false; } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiEventList) diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 86cec275b2d3..0b6abb609db1 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -2970,9 +2970,10 @@ class VST3PluginInstance : public AudioPluginInstance midiOutputs->clear(); if (acceptsMidi()) - MidiEventList::toEventList (*midiInputs, midiBuffer, - destination.inputParameterChanges, - midiMapping); + MidiEventList::hostToPluginEventList (*midiInputs, + midiBuffer, + destination.inputParameterChanges, + midiMapping); destination.inputEvents = midiInputs; destination.outputEvents = midiOutputs;