SetParameterAutomated (Midi Learn)

Topics: Host Processing, Plugin Parameters
Oct 17, 2015 at 12:32 AM
I'm a little fuzzy on how the IVstHostCommandStub interface works. I do know that when a parameter is changed in one of my hosted VSTs, the SetParameterAutomated method is called on my host object. This allows me to get the parameter by index and I will know the value that the parameter has been changed to. However, I don't know what VST I am host actually caused this to happen...

A bit confused.
Coordinator
Oct 28, 2015 at 8:58 AM
I am uncertain what you are actually asking. Are you writing a host or a plugin?

From the plugin's perspective it simply notifies the host that a value for a parameter has changed. Many hosts keep copies of these plugin parameters and this is how things stay in sync.

From a host's perspective you should use this to keep your data in sync. A plugin's parameter value can change in so many ways - some of them are triggered by the host (think of parameter automation or host generated UI) - these are usually communicated to the plugin by calling SetParameter() - and some of these changes are triggered by the plugin itself. This is just a way for the plugin to let the host know of such a change.

Did I get it? ;-)
Oct 28, 2015 at 11:36 PM
I know what you mean. It was probably one of the first questions I asked here because I felt like my solution was sketchy. That's because it looks like I piggy backed on an internal structure of the Vst.Net framework to store my unique Id.

Here's the relevant interface, I store the Id using 'Set' and retrieve it using 'Find' methods.
using System;
using System.ComponentModel;

using Jacobi.Vst.Core.Plugin;

namespace Jacobi.Vst.Core.Host
{
    /// <summary>
    /// The IVstPluginContext interface represents the Plugin Context interface that is shared to other objects.
    /// </summary>
    public interface IVstPluginContext : INotifyPropertyChanged
    {
        /// <summary>
        /// Sets a context property identified by <paramref name="keyName"/> to a new <paramref name="value"/>.
        /// </summary>
        /// <typeparam name="T">Inferred, no need to specify it explicitly.</typeparam>
        /// <param name="keyName">The unique identification of the context property.</param>
        /// <param name="value">The new value to store in the context for the context property.</param>
        /// <remarks>The <see cref="INotifyPropertyChanged.PropertyChanged"/> event is raised when the property value is overwritten.
        /// If the property did not exist yet, it is created but no event is raised.</remarks>
        void Set<T>(string keyName, T value);

        /// <summary>
        /// Retrieves the value of a context property identified by <paramref name="keyName"/>.
        /// </summary>
        /// <typeparam name="T">Specifies the data type of the context property identified by <paramref name="keyName"/>.</typeparam>
        /// <param name="keyName">The unique identification of the context property.</param>
        /// <returns>Returns null if the property was not found.</returns>
        T Find<T>(string keyName);

        /// <summary>
        /// Removes the property identified by <paramref name="keyName"/> from the context.
        /// </summary>
        /// <param name="keyName">The unique identification of the context property.</param>
        void Remove(string keyName);

        /// <summary>
        /// Removes the property identified by <paramref name="keyName"/> from the context and
        /// calls <see cref="IDisposable.Dispose"/> if the data 'value' implements it.
        /// </summary>
        /// <param name="keyName">The unique identification of the context property.</param>
        void Delete(string keyName);

        /// <summary>
        /// Gets a reference to the HostCommandStub.
        /// </summary>
        IVstHostCommandStub HostCommandStub { get; }

        /// <summary>
        /// Gets a reference to the PluginCommandStub.
        /// </summary>
        IVstPluginCommandStub PluginCommandStub { get; }

        /// <summary>
        /// Gets a reference to the plug information.
        /// </summary>
        VstPluginInfo PluginInfo { get; set; }

        /// <summary>
        /// Promotes the plugin information published in the <b>AEffect</b> structure to the <see cref="PluginInfo"/> property.
        /// </summary>
        /// <param name="raiseEvents">When true the <see cref="INotifyPropertyChanged.PropertyChanged"/> event is raised for
        /// each property that has changed.
        /// The property names of the <see cref="VstPluginInfo"/> class are prefixed with 'PluginInfo.'.</param>
        void AcceptPluginInfoData(bool raiseEvents);
    }
}
I assign my Guid to each plugin on creation.
// Create plugin context and set unique id
Guid yourUniqueId = Guid.NewGuid();
VstPluginContext vstPluginContext = VstPluginContext.Create(pluginPath, host);
vstPluginContext.Set<Guid>("Id", yourUniqueId);
In the HostCommandStub implementation (plugin host class) you implement this property if it's not there already.
public IVstPluginContext PluginContext { get; set; }
In each plugin callback (EditorIdle, ProcessEvents etc...) fetch back the Id from the property.
public void SetParameterAutomated(int index, float value)
{
   Guid yourUniqueId = PluginContext.Find<Guid>("Id");
}
This has worked good so far, with proper locking ;)
Oct 30, 2015 at 11:08 PM
Marc: I am uncertain what you are actually asking. Are you writing a host or a plugin?

My synth is both. It's a VST that hosts VSTs. The issue is that SetParameterAutomated is fired when one of the VSTs I am hosting's parameter changes. This is a good thing, but I don't know which VST's parameter has changed.

Yury: your code looks bang on! However, for some reason, the PluginContext getters and setters are never getting hit. I put a breakpoint on the get, and the set, but they are never called. Could this be a DAW specific thing? Could it be that some DAWs call the setter, and some don't? Anyway, still stuck...

PS: What would be really is if the IVstPluginContext interface itself had an event which fires when its parameters are changed.
Nov 4, 2015 at 12:19 AM
Not sure I understand what you mean. When the host creates the plugin the object returned is the PluginContext. When the plugin calls back what is the reference value of PluginContext? Is it null?
Nov 4, 2015 at 12:22 AM
PS: What would be really is if the IVstPluginContext interface itself had an event which fires when its parameters are changed.
  • If you're talking about Vst parameters then it is the SetParameterAutomated method.
  • If you're talking about a generic callback method you could shoehorn that functionality in PluginContext too.
Nov 4, 2015 at 12:44 AM
Edited Nov 4, 2015 at 12:45 AM
When the plugin calls back what is the reference value of PluginContext? Is it null?

Yes. The setter gets called once, and the value is null. This might be an Ableton specific problem...

PS: I was wrong in my previous post. The setter is getting called, it's just getting a value of null.
Nov 4, 2015 at 1:25 AM
That's weird, are you sure you put the code in the class impementing the IVstHostCommandStub?

This is not related to Ableton or other host. It's Vst.Net that calls the setter, here's the IVstHostCommandStub interface:
namespace Jacobi.Vst.Core.Host
{
    /// <summary>
    /// The IVstHostCommandStub interface is implemented by the command stub for handling 
    /// incoming Host commands from the Plugin at the interop assembly.
    /// </summary>
    /// <remarks>The interface derives from <see cref="IVstHostCommands20"/>.</remarks>
    public interface IVstHostCommandStub : IVstHostCommands20
    {
        /// <summary>
        /// Gets or sets the plugin context this instance is part of.
        /// </summary>
        IVstPluginContext PluginContext { get; set; }
    }
}
Nov 4, 2015 at 1:26 AM
I believe the code who calls the setter is in VstHostCommandAdapter.cs

I'm sure Marc's comments will ring a bell to you:
        /// <summary>
        /// This call is forwarded to the <see cref="Jacobi.Vst.Core.Host.IVstHostCommandStub"/> implementation.
        /// </summary>
        /// <param name="pluginInfo">Passed with the forwarded call.</param>
        /// <returns>Returns the value returned from the forwarded call.</returns>
        public bool UpdatePluginInfo(Plugin.VstPluginInfo pluginInfo)
        {
            _hostCmdStub.PluginContext.PluginInfo = pluginInfo;

            return true;
        }
Coordinator
Nov 4, 2015 at 1:03 PM
@Kruddler: SetParameterAutomated has an index that identifies the parameter. The order of parameters in a plugin cannot change - ever. If you create a unique HostCommandStub instance for each plugin you manage you should be able to track it down...

@Yury: I made the properties in the IVstPluginContext for the Host developer to use (I only use it to store the file path I believe). So you're not piggybagin' - you're using it as intended. and you don't have to restrict yourself to native/simple/value types. You can put references to your classes in there as well.
the PluginContext getters and setters are never
Not sure what you're going for here... what getters and setters are you talking about specifically?
Nov 4, 2015 at 8:13 PM
He's talking about the PluginContext property. It's value is null when he wants to use it in the host plugin stub implementation. I'm fairly sure this property is assigned by Vst.Net and fail to see what issue is causing it to be null in his use case since it's working fine by itself here.
Coordinator
Nov 5, 2015 at 6:46 AM
Yeah, we have to see some code in order to help more, I think...
Show us how you load/open plugins and how you manage the HostCommandStub's you pass them...
Nov 5, 2015 at 6:47 PM
Yury: "That's weird, are you sure you put the code in the class impementing the IVstHostCommandStub? "

Yes. Here's a chopped down version:
    public class MySynthHostCommandStubAdapter : host.IVstHostCommandStub, IDisposable
    {

        private IVstHostCommandStub _HostCmdStub;
        private host.IVstPluginContext _VstPluginContext;

        public void SetParameterAutomated(int index, float value)
        {
            if (_HostCmdStub == null)
            {
                return;
            }

            _HostCmdStub.SetParameterAutomated(index, value);

            if (ParamsChanged != null)
            {
                ParamsChanged(this, new ParamsChangedEventArgs { Index = index, Value = value });
            }
        }

        public host.IVstPluginContext PluginContext
        {
            get
            {
                return _VstPluginContext;
            }
            set
            {
                _VstPluginContext = value;
            }
        }
    }
Obiwan:SetParameterAutomated has an index that identifies the parameter.

Umderstood. It's not the index, it's the plugin which I don't know about.

If you create a unique HostCommandStub instance for each plugin you manage you should be able to track it down...

How do I do that? Currently, I'm just creating a plugin context when I create an instance of a plugin inside my app (which is a VST):
        private interophost.VstPluginContext CreateVstPluginContext(string dllPath)
        {
            return interophost.VstPluginContext.Create(dllPath, hostStub);
        }
Just to give you some context. My synth is a VST. I am hosting it inside Ableton. My synth creates many instances of other VSTs inside my synth. I keep those plugin contexts in a static list. What I want to capture is when a user opens up the editor for one of those plugins and twiddles a knob. What I would expect would be that the plugin context for that VST would raise an event that I could capture to know what is going on. Instead, the host's "SetParametersAutomated" method is called. That tells me the param index and the value, but it doesn't tell me which one of my plugins caused the event to fire.

Obiwan sounds dead right when he says "If you create a unique HostCommandStub instance for each plugin you manage you should be able to track it down", but I don't know how to do that.
Nov 5, 2015 at 6:48 PM
Lastly, this is how my host stub is created:
        public VstPluginInfo GetPluginInfo(IVstHostCommandStub dawHost)
        {
            HostStub = new MySynthHostCommandStubAdapter(dawHost);
        }
Nov 5, 2015 at 6:51 PM
Essentially what I am trying to achieve is midi learn.
Coordinator
Nov 5, 2015 at 8:35 PM
Edited Nov 5, 2015 at 8:39 PM
Loading/opening a VST plugin (managed or unmanaged):
  • Create a class that implements IVstHostCommandStub.
  • Call VstPluginContext.Create passing in the path to the plugin to load and a reference to an IVstHostCommandStub (your class).
  • VstPluginContext.Create returns the VstPluginContext that contains a reference to the plugin just opened, its plugin-info and your host-command-stub. Thus tying everything together.
Just create a new instance of the class that implements the IVstHostCommandStub interface for each VstPluginContext.Create call you do. Then only that one plugin will call into the Host over that specific instance.

For a very simple sample, look at the Host sample code (OpenPlugin method)
http://vstnet.codeplex.com/SourceControl/latest#Source/Samples/Jacobi.Vst.Samples.Host/MainForm.cs

I don't see how/why you would need an adapter...?

Hope it makes sense.
Marc
Nov 5, 2015 at 9:12 PM
Yeah needs one stub per plugin. Maybe that's the only reason why PluginContext is null in your use case.
HostCommandStub host = new HostCommandStub(this);
VstPluginContext ctx = VstPluginContext.Create(pluginPath, host);
ctx.Set<Guid>("Id", id);
Nov 6, 2015 at 11:50 PM
Thanks guys! This is all sorted now which is a major step forward for me.

This is what had confused me.
        public VstPluginInfo GetPluginInfo(IVstHostCommandStub dawHost)
        {
        }
This method passes in an object. I assume (as you can tell by the name of the parameter) that this variables is an interface between my Vst, and the DAW. Perhaps I'm correct about this?

Anyway, I got confused, and was using this variable to create plugin contexts like this. I thought that this was necessary for some reason.
 return interophost.VstPluginContext.Create(dllPath, dawHost);
So, regardless of which Vst was calling SetParametersAutomated, it was ending up in the same host interface. I've fixed this now by creating a new implementation of the interface and passing it in to every new plugin context.
            var host = new VstInstanceHost();
            return interophost.VstPluginContext.Create(dllPath, host);
For the record though, and I know this is probably done for a reason, I'm not really a fan of this pattern. I still feel that the plugin context should be raising events, and shouldn't really need this external host interface unless you want to specify it. I realize that this has probably been done for performance, but in my app, I have to do all this just to listen to the event:
  • Wrap the interface which is cumbersome in and of itself. I don't really need this object.
  • Raise an event from the implemented interface
  • And, due to the design of my framework, then re-raise that event at a higher level so that other code can listen to the event.
But, I'm still very glad that I finally got it working. Thanks very much for the help to both of you.
Nov 7, 2015 at 4:14 AM
The stub contains the callback methods, EditorIdle, SetParameterAutomated... that's the event handlers I use. From there you are on your own. I do lock and operate directly on global static variables in there. I find the object handy because it handles low level stuff like dispose.
Coordinator
Nov 7, 2015 at 5:35 AM
This method passes in an object. I assume (as you can tell by the name of the parameter) that this variables is an interface between my Vst, and the DAW. Perhaps I'm correct about this?
  • Yes correct.
The VstPluginContext is just a collection of stuff that belongs together for one plugin instance. The IVstHostCommandStub interface (or the instance of the class that implements it) is what receives the calls from the plugin to its host. When you write a host - you implement this interface. The reason these are not events is because there are several implementations for this interface in VST.NET. It also allows you to intercept (debug/trace) or redirect any traffic. It's way more versatile. Finally, a virtual method performs a little better than an event+handler.

Anyway, if you want events, create a class that implements the IVstHostCommandStub interface and exposes events for each method/scenario.