Browse Source

VST3 Host: Send parameter changes triggered by MIDI CCs to the IEditController

This change reshuffles the way that parameter updates and notifications
work for hosted VST3 plugins.

Previously:
- Parameter::setValue would update the processor and editor, but not the
  host
- Parameter::setValueFromEditor would update the processor and host, but
  not the editor
- MIDI CC events would be converted to events and added to the
  processor's input event list, but were not forwarded to the
  IEditController.

Now:
- Parameter::setValue updates the host's cachedParamValues, which is the
  host's source of truth for parameter values. On each process callback,
  changes will be added to the input parameter list. Then, for each item
  in the parameter list, an update will be dispatched back to the
  editor.
- Parameter::setValueFromEditor is removed. All parameter changes will
  be sent back to the editor, even if they originated from the editor.
  setValueNotifyingHost can be used to notify listeners that one of the
  host's JUCE parameters has changed, e.g. in the host context's
  performEdit.
- MIDI CC events trigger calls to setValueNotifyingHost on any mapped
  parameters. The flow is very similar to a parameter change from the
  editor: the cachedParamValues are updated immediately, and host
  parameter listeners are notified. Then, for each changed
  cachedParamValue an entry is added to the inputParameterChanges
  (updating the processor), then for each item in the
  inputParameterChanges an update is sent to the editor.
pull/22/head
reuk 3 years ago
parent
commit
ce061675a5
No known key found for this signature in database GPG Key ID: 9ADCD339CFC98A11
2 changed files with 83 additions and 78 deletions
  1. +68
    -62
      modules/juce_audio_processors/format_types/juce_VST3Common.h
  2. +15
    -16
      modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp

+ 68
- 62
modules/juce_audio_processors/format_types/juce_VST3Common.h View File

@@ -1040,24 +1040,18 @@ public:
}
}
static void hostToPluginEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer,
Steinberg::Vst::IParameterChanges* parameterChanges,
const StoredMidiMapping& midiMapping)
{
toEventList (result,
midiBuffer,
parameterChanges,
&midiMapping,
EventConversionKind::hostToPlugin);
template <typename Callback>
static void hostToPluginEventList (Steinberg::Vst::IEventList& result,
MidiBuffer& midiBuffer,
StoredMidiMapping& mapping,
Callback&& callback)
{
toEventList (result, midiBuffer, &mapping, callback);
}
static void pluginToHostEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer)
{
toEventList (result,
midiBuffer,
nullptr,
nullptr,
EventConversionKind::pluginToHost);
toEventList (result, midiBuffer, nullptr, [] (auto&&...) {});
}
private:
@@ -1073,48 +1067,70 @@ private:
pluginToHost
};
static void toEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer,
Steinberg::Vst::IParameterChanges* parameterChanges,
const StoredMidiMapping* midiMapping,
EventConversionKind kind)
template <typename Callback>
static bool sendMappedParameter (const MidiMessage& msg,
StoredMidiMapping* midiMapping,
Callback&& callback)
{
enum { maxNumEvents = 2048 }; // Steinberg's Host Checker states that no more than 2048 events are allowed at once
int numEvents = 0;
if (midiMapping == nullptr)
return false;
for (const auto metadata : midiBuffer)
{
if (++numEvents > maxNumEvents)
break;
const auto controlEvent = toVst3ControlEvent (msg);
if (! controlEvent.hasValue())
return false;
auto msg = metadata.getMessage();
const auto controlParamID = midiMapping->getMapping (createSafeChannel (msg.getChannel()),
controlEvent->controllerNumber);
if (midiMapping != nullptr && parameterChanges != nullptr)
{
Vst3MidiControlEvent controlEvent;
if (controlParamID != Steinberg::Vst::kNoParamId)
callback (controlParamID, controlEvent->paramValue);
if (toVst3ControlEvent (msg, controlEvent))
{
const auto controlParamID = midiMapping->getMapping (createSafeChannel (msg.getChannel()),
controlEvent.controllerNumber);
return true;
}
if (controlParamID != Steinberg::Vst::kNoParamId)
{
Steinberg::int32 ignore;
template <typename Callback>
static void processMidiMessage (Steinberg::Vst::IEventList& result,
const MidiMessageMetadata metadata,
StoredMidiMapping* midiMapping,
Callback&& callback)
{
const auto msg = metadata.getMessage();
if (auto* queue = parameterChanges->addParameterData (controlParamID, ignore))
queue->addPoint (metadata.samplePosition, controlEvent.paramValue, ignore);
}
if (sendMappedParameter (msg, midiMapping, std::forward<Callback> (callback)))
return;
continue;
}
}
const auto kind = midiMapping != nullptr ? EventConversionKind::hostToPlugin
: EventConversionKind::pluginToHost;
if (auto maybeEvent = createVstEvent (msg, metadata.data, kind))
{
maybeEvent->busIndex = 0;
maybeEvent->sampleOffset = metadata.samplePosition;
result.addEvent (*maybeEvent);
}
auto maybeEvent = createVstEvent (msg, metadata.data, kind);
if (! maybeEvent.hasValue())
return;
maybeEvent->busIndex = 0;
maybeEvent->sampleOffset = metadata.samplePosition;
result.addEvent (*maybeEvent);
}
/* If mapping is non-null, the conversion is assumed to be host-to-plugin, or otherwise
plugin-to-host.
*/
template <typename Callback>
static void toEventList (Steinberg::Vst::IEventList& result,
MidiBuffer& midiBuffer,
StoredMidiMapping* midiMapping,
Callback&& callback)
{
enum { maxNumEvents = 2048 }; // Steinberg's Host Checker states that no more than 2048 events are allowed at once
int numEvents = 0;
for (const auto metadata : midiBuffer)
{
if (++numEvents > maxNumEvents)
break;
processMidiMessage (result, metadata, midiMapping, std::forward<Callback> (callback));
}
}
@@ -1361,28 +1377,18 @@ private:
Steinberg::Vst::ParamValue paramValue;
};
static bool toVst3ControlEvent (const MidiMessage& msg, Vst3MidiControlEvent& result)
static Optional<Vst3MidiControlEvent> toVst3ControlEvent (const MidiMessage& msg)
{
if (msg.isController())
{
result = { (Steinberg::Vst::CtrlNumber) msg.getControllerNumber(), msg.getControllerValue() / 127.0};
return true;
}
return Vst3MidiControlEvent { (Steinberg::Vst::CtrlNumber) msg.getControllerNumber(), msg.getControllerValue() / 127.0 };
if (msg.isPitchWheel())
{
result = { Steinberg::Vst::kPitchBend, msg.getPitchWheelValue() / 16383.0};
return true;
}
return Vst3MidiControlEvent { Steinberg::Vst::kPitchBend, msg.getPitchWheelValue() / 16383.0};
if (msg.isChannelPressure())
{
result = { Steinberg::Vst::kAfterTouch, msg.getChannelPressureValue() / 127.0};
return true;
}
return Vst3MidiControlEvent { Steinberg::Vst::kAfterTouch, msg.getChannelPressureValue() / 127.0};
result.controllerNumber = -1;
return false;
return {};
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiEventList)


+ 15
- 16
modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp View File

@@ -2082,18 +2082,6 @@ public:
void setValue (float newValue) override
{
pluginInstance.cachedParamValues.set (vstParamIndex, newValue);
pluginInstance.parameterDispatcher.push (vstParamIndex, newValue);
}
/* If the editor set the value, there's no need to notify it that the parameter
value changed. Instead, we set the cachedValue (which will be read by the
processor during the next processBlock) and notify listeners that the parameter
has changed.
*/
void setValueFromEditor (float newValue)
{
pluginInstance.cachedParamValues.set (vstParamIndex, newValue);
sendValueChangedMessageToListeners (newValue);
}
/* If we're syncing the editor to the processor, the processor won't need to
@@ -2559,6 +2547,11 @@ public:
inputParameterChanges->set (cachedParamValues.getParamID (index), value);
});
inputParameterChanges->forEach ([&] (Steinberg::int32 index, float value)
{
parameterDispatcher.push (index, value);
});
processor->process (data);
outputParameterChanges->forEach ([&] (Steinberg::int32 index, float value)
@@ -3024,7 +3017,9 @@ private:
{
Steinberg::MemoryStream stream;
if (object->getState (&stream) == kResultTrue)
const auto result = object->getState (&stream);
if (result == kResultTrue)
{
MemoryBlock info (stream.getData(), (size_t) stream.getSize());
head.createNewChildElement (identifier)->addTextElement (info.toBase64Encoding());
@@ -3322,8 +3317,12 @@ private:
{
MidiEventList::hostToPluginEventList (*midiInputs,
midiBuffer,
destination.inputParameterChanges,
storedMidiMapping);
storedMidiMapping,
[this] (const auto controlID, const auto paramValue)
{
if (auto* param = this->getParameterForID (controlID))
param->setValueNotifyingHost ((float) paramValue);
});
}
destination.inputEvents = midiInputs;
@@ -3468,7 +3467,7 @@ tresult VST3HostContext::performEdit (Vst::ParamID paramID, Vst::ParamValue valu
if (auto* param = plugin->getParameterForID (paramID))
{
param->setValueFromEditor ((float) valueNormalised);
param->setValueNotifyingHost ((float) valueNormalised);
// did the plug-in already update the parameter internally
if (plugin->editController->getParamNormalized (paramID) != (float) valueNormalised)


Loading…
Cancel
Save