This project has moved and is read-only. For the latest updates, please go here.

VST Host Process MIDI from Plugins

Topics: Host Development, Midi, VST.NET Core, VST.NET Framework
Aug 26, 2014 at 10:10 PM
Hello,

I'm back and still working on my own VST Host and it's actually coming along quite nicely. Now i stumbled over a "probably small" problem and i couldn't find a solution anywhere. I want to synchronize the BPM of a Looper Plugin with some effect plugins. So I need to process the MIDI Clock event i figured but i can't figure out how to get any kind of event from a Plugin.
I have all the Plugins as VstPluginContext but even after looking through there methods (also then ones from PluginCommandStub and HostCommandStub) i couldn't find any suitable one.

Can someone help me and tell me how to capture MIDI events from a plugin as a VST Host?

Best Regards,
MariusR
Aug 27, 2014 at 3:15 AM
Edited Aug 27, 2014 at 3:17 AM
oh yeah I can help with this one
The plugin calls into Vst.Net GetTimeInfo in the IVstHostCommandStub impementation.
That's where you set the tempo for the plugin.
private VstTimeInfo vstTimeInfo = new VstTimeInfo();
private double tempo = 120d;

public Jacobi.Vst.Core.VstTimeInfo GetTimeInfo(Jacobi.Vst.Core.VstTimeInfoFlags filterFlags)
{            
    vstTimeInfo.Tempo = tempo;
    return vstTimeInfo;
}
Ususally you'll have to set other properties for this to work.
These are the one I've figured out so far:
public Jacobi.Vst.Core.VstTimeInfo GetTimeInfo(Jacobi.Vst.Core.VstTimeInfoFlags filterFlags)
{            
    vstTimeInfo.SamplePosition = AudioParameters.SamplePlayed;
    vstTimeInfo.SampleRate = AudioParameters.SampleRate;
    vstTimeInfo.Tempo = tempo;
    vstTimeInfo.PpqPosition = (vstTimeInfo.SamplePosition / vstTimeInfo.SampleRate) * (vstTimeInfo.Tempo / 60.0);
    vstTimeInfo.NanoSeconds = 0.0;
    vstTimeInfo.BarStartPosition = 0.0;
    vstTimeInfo.CycleStartPosition = 0.0;
    vstTimeInfo.CycleEndPosition = 0.0;
    vstTimeInfo.TimeSignatureNumerator = 4;
    vstTimeInfo.TimeSignatureDenominator = 4;
    vstTimeInfo.SmpteOffset = 0;
    vstTimeInfo.SmpteFrameRate = new Jacobi.Vst.Core.VstSmpteFrameRate();
    vstTimeInfo.SamplesToNearestClock = 0;
    vstTimeInfo.Flags = VstTimeInfoFlags.TempoValid & 
                        VstTimeInfoFlags.PpqPositionValid;

    if (RunActions.GlobalPlayState == PlayState.Play)
        vstTimeInfo.Flags |= VstTimeInfoFlags.TransportPlaying;

    return vstTimeInfo;
}
Aug 27, 2014 at 8:33 PM
Thank you YuryK for your answer but i'm actually not asking about how to SET the Tempo but more on how to RETRIEVE it. More specifically I'm trying to listen to the MIDI clock event which (I assume...i don't know yet for sure) is send by the plugin to the host.
Aug 28, 2014 at 2:42 PM
Hey Yury,

i have come to a point where i seem to need exactly what you're describing in this thread and the other one your posted a while ago. I want to sync a plugin to my Host, especially its tempo. I already set the Tempo in GetPluginInfo and also tried your version of the necessary parameters but the plugin does not seem accept this. It works when i use the plugin in one of the many already made VST Hosts (like e.g. "VST Host" which i use for most of my testing). I also don't know how i would go about changing the tempo at runtime...
(Btw. i don't know what AudioParameters includes in your sample code so i replaced for example SamplePosition with 0.0 .

Regards,
Marius R
Aug 28, 2014 at 3:04 PM
PS. I also tried to send MIDI clock pulses
byte[] middat = new byte[] { 248 };
VstMidiEvent midiev = new VstMidiEvent(0, 0, 0, middat, 0, 0);
VstEvent[] evarr = new VstEvent[] { midiev };
myplugincontext.PluginCommandStub.ProcessEvents(evarr);
manually but this also didn't do anything.
Aug 28, 2014 at 3:57 PM
Edited Aug 28, 2014 at 4:23 PM
Thank you YuryK for your answer but i'm actually not asking about how to SET the Tempo but more on how to RETRIEVE it. More specifically I'm trying to listen to the MIDI clock event which (I assume...i don't know yet for sure) is send by the plugin to the host.
This is not allowed in Vst Spec. The host sets the tempo and transport controls, no interface exists that allows plugins to control the host. This is an often asked question but there is no support for that in Vst 2.x.

The best you could do is ask the developper of the plugin to implement tempo, SamplePosition, PPQ parameters as standard Vst plugin parameter and push them with fake automation or implement some kind of BPM detection in the host (lousy solution).
Aug 28, 2014 at 4:09 PM
Edited Aug 28, 2014 at 4:33 PM
(Btw. i don't know what AudioParameters includes in your sample code so i replaced for example SamplePosition with 0.0 .
That is most likely your problem. sequencers plugins I tested required the SamplePosition, SampleRate, PpqPosition and Tempo in order to sync.
SamplePosition is the total number of samples passed to the audio card buffer in my host.
Some trickier plugins might need BarStartPosition, CycleStartPosition, CycleEndPosition to work but I had difficulty finding a plugin that supports these fields and figuring out the maths required for this.

I suggest you try out a few step sequencers to see what works instead of focusing on a single plugin.
I believe all plugins ignore Midi clock messages and rely solely on the VstTimeInfo structure for tempo sync.
Aug 28, 2014 at 5:51 PM
This is not allowed in Vst Spec. The host sets the tempo and transport controls, no interface exists that allows plugins to control the host. This is an often asked question but there is no support for that in Vst 2.x.
Oh ok thank you this answers that question. I don't actually need it anyways i can calculate it myself "manually".
That is most likely your problem. sequencers plugins I tested required the SamplePosition, SampleRate, PpqPosition and Tempo in order to sync.
SamplePosition is the total number of samples passed to the audio card buffer in my host.
Some trickier plugins might need BarStartPosition, CycleStartPosition, CycleEndPosition to work but I had difficulty finding a plugin that supports these fields and figuring out the maths required for this.

I suggest you try out a few step sequencers to see what works instead of focusing on a single plugin.
I believe all plugins ignore Midi clock messages and rely solely on the VstTimeInfo structure for tempo sync.
Is GetTimeInfo called periodically by the plugin then? Can i then just do like vstTimeInfo.SamplePosition = somevariable; and then do somevariable += buffer.SampleCount everytime I process a new buffer to the plugin?
Also can i change the tempo the same way so do vstTimeInfo.Tempo = tempovariable; and do tempovariable = 140.0 e.g. at Runtime and it will automatically sync it to the plugin?
Another question (you see i'm still fairly new to the hole Midi, VST, Audio thing) what is PpqPosition?
Problem is that i pretty much need exactly this Plugin in (GLoop) to work as i require a simple Looper. I might try a simple step sequencer though if it won't work at all with this plugin.
Okay so no MIDI clock then.
Thank you again for your help :)
Aug 28, 2014 at 6:12 PM
Edited Aug 28, 2014 at 6:13 PM
To answer one of my own question:
Also can i change the tempo the same way so do vstTimeInfo.Tempo = tempovariable; and do tempovariable = 140.0 e.g. at Runtime and it will automatically sync it to the plugin?
it seems like doing the vstTimeInfo.Tempo = tempovariable - way works as one of my other integrated plugins (the effects plugin turnado) seems to respond accurately when i change the variable.
Aug 28, 2014 at 6:51 PM
Edited Aug 28, 2014 at 7:07 PM
Is GetTimeInfo called periodically by the plugin then?
  • Yes it is called when the plugin needs it and seems to perform just fine.
Can i then just do like vstTimeInfo.SamplePosition = somevariable; and then do somevariable += buffer.SampleCount everytime I process a new buffer to the plugin?
  • That's what I do but I use the sample count of the global buffer output that goes to the audio card when it inquires a new block instead, that's to ensure each plugins share the same SamplePosition.
Also can i change the tempo the same way so do vstTimeInfo.Tempo = tempovariable; and do tempovariable = 140.0 e.g. at Runtime and it will automatically sync it to the plugin?
  • Yes it will change the next time the plugins inquire VstTimeInfo structure. My host is geared towards real-time tempo changes and tempo changes are picked up by the plugins in real-time.
Another question (you see i'm still fairly new to the hole Midi, VST, Audio thing) what is PpqPosition?
  • Pulses per quarter note position for whatever that means... I read it as SamplePosition divided by SampleRate multiplied by beat per second.
Problem is that i pretty much need exactly this Plugin in (GLoop) to work as i require a simple Looper. I might try a simple step sequencer though if it won't work at all with this plugin.
  • It's hard to know the problem you're facing if you focus on a single plugin. You can get all fields right but it might occur this particular plugin needs BarStartPosition, CycleStartPosition, CycleEndPosition in addition of SamplePosition, SampleRate, PpqPosition and Tempo for instance. By getting the tempo sync working with a few freeware step sequencers you can ascertain the work so far and focus on more complex fields.
This Sync technique works only for:
VstTimeInfoFlags.TempoValid and VstTimeInfoFlags.PpqPositionValid
Sep 4, 2014 at 8:53 PM
Thanks again YuryK for your detailed input and sorry for not replying for quite a while.
Now after a lot of trial and error i managed to get it working just by adding:
            vstTimeInfo.Flags = filterFlags;
in
public Jacobi.Vst.Core.VstTimeInfo GetTimeInfo(Jacobi.Vst.Core.VstTimeInfoFlags filterFlags)

and it works even in GLoop. I personally don't really know why it works but i'm quite happy that it does :D
Sep 4, 2014 at 10:05 PM
Edited Sep 4, 2014 at 10:09 PM
Great.

If you're talking about the flags, it's quite simple but a little bit dangerous.

When the plugin calls, it specifies which VstTimeInfo properties it needs in filterFlags.
You then set the value of each VstTimeInfo properties required and specify the value of the properties you have filled with a valid value in the Flags attribute.

I used only three flags even if the plugins specify it wants more in the filterFlags parameter :
vstTimeInfo.Flags = VstTimeInfoFlags.TempoValid & VstTimeInfoFlags.PpqPositionValid;

    if (RunActions.GlobalPlayState == PlayState.Play)
        vstTimeInfo.Flags |= VstTimeInfoFlags.TransportPlaying;
By doing vstTimeInfo.Flags = filterFlags you are telling the plugin you have set valid values for every field it required, which might be false.

For example these fields are invalid:
    vstTimeInfo.NanoSeconds = 0.0;
    vstTimeInfo.BarStartPosition = 0.0;
    vstTimeInfo.CycleStartPosition = 0.0;
    vstTimeInfo.CycleEndPosition = 0.0;
    vstTimeInfo.SamplesToNearestClock = 0;
If the plugin requested CyclePosition in filterFlags and you pass filterFlag as is to the Flag attribute you are telling the plugin you have provided valid values for CycleStartPosition and CycleEndPosisiton (which is not the case, a constant 0 is invalid).

By not passing the CyclePosition flag to the plugin even if requested, the plugin will have a way to know it shouldn't use the CycleStartPosition and CycleEndPosisiton values.