Syncing drum machine to host.

Topics: VST.NET Framework
Mar 1, 2012 at 7:55 PM
Edited Mar 1, 2012 at 8:13 PM

I've managed to resume work on my  vst host for my DAW and have experienced some issues when trying to start a drum machine.

My intentions are to :-

start ,

stop

change tempo

sync to my host

and be able to pause and resume from a specific bar, ( my host is 24ppq)

So far I can:

Change tempo  - no issues

start and stop with some success.

Start and stop works for this drum machine  (tatpoum)  http://www.kvraudio.com/product/tatapoum_by_venusian_snails only if I toggle the state from Stop to Start and call  EditorIdle in the middle.  Why I have to do that ?? who knows.  For this http://www.vst4free.com/free_vst.php?plugin=TheDrumSource&id=1054 only triggers a single drum beat.

I understand the basic concept of the  Jacobi.Vst.Core.VstTimeInfo.Flags.  The code i have at the moment was gather by extracting the flag info from a vsthost that works usin the wrapper plugin, it called  (Tuna Fish).  I have left the updating of tempo out to be brief.

In order to keep in sync should I be updating the PpqPosition.   I understand that PpqPosition 1 represnts 1 quater note, so given that my mIDi clock is 24 ppq would each pulse be 1/24 ?

In order to resume from the current  postion do I need to update the BarStartPostion.  Would a BarStartPostion of 5 be the first beat of the second bar ?

Or should I just continue where I left of updating the PPQN position.

Or I am i barking up the wrong tree.

 

Any assitance would be invaluable

Regards Tony

P.S

 both drum machines pass via GetTimeInfo( filterFlags)

PpqPositionValid | TempoValid | BarStartPositionValid

/// <summary>
    /// The HostCommandStub class represents the part of the host that a plugin can call.
    /// </summary>
    class HostCommandStub : IVstHostCommandStub
    {

 public void StopTransport()
        {
            if (((int)vstTimeInfo.Flags & (int)Jacobi.Vst.Core.VstTimeInfoFlags.TransportPlaying) == (int)Jacobi.Vst.Core.VstTimeInfoFlags.TransportPlaying)
            {
                vstTimeInfo.Flags -= (int)Jacobi.Vst.Core.VstTimeInfoFlags.TransportChanged +
                     (int)Jacobi.Vst.Core.VstTimeInfoFlags.TransportPlaying +
                      (int)Jacobi.Vst.Core.VstTimeInfoFlags.AutomationWriting +
                      (int)Jacobi.Vst.Core.VstTimeInfoFlags.AutomationReading;
            }
        }
        public void StartTransport()
        {
            if (((int)vstTimeInfo.Flags & (int)Jacobi.Vst.Core.VstTimeInfoFlags.TransportPlaying) != (int)Jacobi.Vst.Core.VstTimeInfoFlags.TransportPlaying)
            {
                //TransportChanged | TransportPlaying | AutomationWriting | AutomationReading | NanoSecondsValid | PpqPositionValid | TempoValid | BarStartPositionValid | CyclePositionValid | TimeSignatureValid | SmpteValid | ClockValid
                //NanoSecondsValid | PpqPositionValid | TempoValid | BarStartPositionValid | CyclePositionValid | TimeSignatureValid | SmpteValid | ClockValid
                vstTimeInfo.Flags +=
                     (int)Jacobi.Vst.Core.VstTimeInfoFlags.TransportChanged +
                     (int)Jacobi.Vst.Core.VstTimeInfoFlags.TransportPlaying +
                      (int)Jacobi.Vst.Core.VstTimeInfoFlags.AutomationWriting +
                      (int)Jacobi.Vst.Core.VstTimeInfoFlags.AutomationReading;
            }
        }


        Jacobi.Vst.Core.VstTimeInfo vstTimeInfo = new Jacobi.Vst.Core.VstTimeInfo()
        {
            Flags = 
            (int)Jacobi.Vst.Core.VstTimeInfoFlags.NanoSecondsValid +
            Jacobi.Vst.Core.VstTimeInfoFlags.PpqPositionValid +
             (int)Jacobi.Vst.Core.VstTimeInfoFlags.TempoValid +
             (int)Jacobi.Vst.Core.VstTimeInfoFlags.BarStartPositionValid +
             (int)Jacobi.Vst.Core.VstTimeInfoFlags.CyclePositionValid +
             (int)Jacobi.Vst.Core.VstTimeInfoFlags.TimeSignatureValid +
              (int)Jacobi.Vst.Core.VstTimeInfoFlags.SmpteValid +
             (int)Jacobi.Vst.Core.VstTimeInfoFlags.ClockValid,
            Tempo = 120,
            SamplePosition = 0.0,
            SampleRate = 44100,
            NanoSeconds = 0.0,
            PpqPosition = 0.0,
            BarStartPosition = 0.0,
            CycleStartPosition = 0.0,
            CycleEndPosition = 0.0,
            TimeSignatureNumerator = 4,
            TimeSignatureDenominator = 4,
            SmpteOffset = 0,
            SmpteFrameRate = new Jacobi.Vst.Core.VstSmpteFrameRate(),
            SamplesToNearestClock = 0

        };

     }
Mar 1, 2012 at 8:12 PM
Edited Mar 1, 2012 at 8:13 PM

 -

Coordinator
Mar 2, 2012 at 5:51 PM

Do you have to call EditorIdle even when the editor window is not open? (which would suprise me).

To my knowledge EditorIdle is meant to provide the custom Editor UI of the plugin an entry point for idle-time processing (short and fast). I think the host is supposed to call it repeatedly whenever the editor UI is open. I could imagine the plugin uses that idle processing to all sorts of short maintenance tasks (like keeping track of host-time).

I know the VST spec uses a slightly different meaning for ppq compared to how midi defines it. The vst C++ header file (http://vstnet.codeplex.com/SourceControl/changeset/view/63261#191297) contains these comments for ppq:

At tempo 120, 1 quarter makes 1/2 second, so 2.0 ppq translates to 48000 samples at 48kHz sample rate.
.25 ppq is one sixteenth note then. if you need something like 480ppq, you simply multiply ppq by that scaler.

I would suggest you also take these questions to the kvraudio forum (http://www.kvraudio.com/forum/viewforum.php?f=33), because there are a lot more knowledgeable people there...

Mar 2, 2012 at 7:23 PM
Edited Mar 2, 2012 at 9:20 PM

sorry for being so dim, still don't get it!  My clock MIDI  generates 24 pulses per quater note so each pulse get i should I update the vst ppq ?

But by how much, my midi clock generates 24 pules per quarter note.  If 24 pulses represent 1/4 note  each pulse will  advance the vst ppq by 0.4416.  This would appear correct cos at 24 ppq  6 pulse = one sixteenth note.  So 0.4416 * 6 = 0.25 that seems right ?

Mar 12, 2012 at 7:57 AM

Hi done a bit more reading and bashed my head a few more times with this issue and it seems the anwer is the

VstInfo.SamplePosition 
How would I go about obtaining that please
Regards T
Coordinator
Mar 12, 2012 at 9:13 AM
Edited Mar 12, 2012 at 9:14 AM

"Current Position. It must always be valid, and should not cost a lot to ask for. The sample position is ahead of the time displayed to the user. In sequencer stop mode, its value does not change. A 32 bit integer is too small for sample positions, and it's a double to make it easier to convert between ppq and samples."

I have no in-depth knowledge of the specifics of the time structure. All VST.NET does is pass on the data.

Again: please go to the AVR Audio forum ((http://www.kvraudio.com/forum/viewforum.php?f=33) for more usable answers.

Mar 12, 2012 at 9:44 AM

Thank anyway, more diggin' I guess

Regatds T

Mar 12, 2012 at 11:17 PM

Thanks to obiwanjacobi AdmiralQuality, Arakula, ola wistedt ( author of TheDrumSource) & others
Success, I have got the machine in Sync, what a relief. I had to think about a different approach to Syncing, it seems the clock provided Leslie Sanford
http://www.codeproject.com/Articles/5501/The-Multimedia-Timer-for-the-NET-Framework was either not accurate enough or there was to much latency.
So I decided to try another approach and let the Vst drive the Midi Clock rather than use the Midi clock to provide timimg info to the Vst Host
Anyhow did some reading on forums and on the web in general and it seems they key is to use the sample postion provided by the audio library. From this the ppqPosition can easily be derived
In my case it was portAudio.
This calls host.ProcessReplacing(blockSize);
Which in turn then asks the plugins to provide their audio data via plugin.ProcessReplacing()
It's after host.ProcessReplacing(blockSize);
that portAudio uses the data gathered, to fill its output buffer
Its at this point I can increment the host sample position

 private PortAudio.PaStreamCallbackResult AudioCallback(IntPtr audioInputBuffer, IntPtr audioOutputBuffer, uint blockSize, ref PortAudio.PaStreamCallbackTimeInfo timeInfo, PortAudio.PaStreamCallbackFlags statusFlags, IntPtr userData)
        {

            // If host and VstBuffer are initialized
            if (host != null && host.VstOutputBuffers != null)
            {
                // This function fills vstOutputBuffers with audio processed by a plugin
                host.ProcessReplacing(blockSize);

                // Init loop indexes
                i = 0;
                j = 0;

                unsafe
                {
                    // Fill audio buffer for all channels, multiply samples by volume range [0, 1]
                    audioBuffer = (float*)audioOutputBuffer.ToPointer();

                    while (i < blockSize * AudioParameters.NbChannels)
                    {
                        for (k = 0; k < AudioParameters.NbChannels; k++)
                            audioBuffer[i++] = host.VstOutputBuffers[k][j] * AudioParameters.globalVolume;
                        host.IncrementSamplePos();
                        j++;
                    }
                }
            }
            else if (host == null)
            {
                // Check if pointer to VstHost is available
                //host = VstHostActions.Obj.Host;
            }

            return PortAudio.PaStreamCallbackResult.paContinue;
        }


As somebody very kindly pointed out here, the ppqPostion can then easily be derived from the following

vstTimeInfo.PpqPosition = (vstTimeInfo.SamplePosition / vstTimeInfo.SampleRate) * (vstTimeInfo.Tempo / 60.0);

This keeps the vst drum plugins very nicely in sync , from this I can raise an event and use it to step my midi sequencer

 

I hope this is of some use and sves someone else some pain

Regards T

 private PortAudio.PaStreamCallbackResult AudioCallback(IntPtr audioInputBuffer, IntPtr audioOutputBuffer, uint blockSize, ref PortAudio.PaStreamCallbackTimeInfo timeInfo, PortAudio.PaStreamCallbackFlags statusFlags, IntPtr userData)
        {

            // If host and VstBuffer are initialized
            if (host != null && host.VstOutputBuffers != null)
            {
                // This function fills vstOutputBuffers with audio processed by a plugin
                host.ProcessReplacing(blockSize);

                // Init loop indexes
                i = 0;
                j = 0;

                unsafe
                {
                    // Fill audio buffer for all channels, multiply samples by volume range [0, 1]
                    audioBuffer = (float*)audioOutputBuffer.ToPointer();

                    while (i < blockSize * AudioParameters.NbChannels)
                    {
                        for (k = 0; k < AudioParameters.NbChannels; k++)
                            audioBuffer[i++] = host.VstOutputBuffers[k][j] * AudioParameters.globalVolume;
                        host.IncrementSamplePos();
                        j++;
                    }
                }
            }
            else if (host == null)
            {
                // Check if pointer to VstHost is available
                //host = VstHostActions.Obj.Host;
            }

            return PortAudio.PaStreamCallbackResult.paContinue;
        }
 private PortAudio.PaStreamCallbackResult AudioCallback(IntPtr audioInputBuffer, IntPtr audioOutputBuffer, uint blockSize, ref PortAudio.PaStreamCallbackTimeInfo timeInfo, PortAudio.PaStreamCallbackFlags statusFlags, IntPtr userData)
        {

            // If host and VstBuffer are initialized
            if (host != null && host.VstOutputBuffers != null)
            {
                // This function fills vstOutputBuffers with audio processed by a plugin
                host.ProcessReplacing(blockSize);

                // Init loop indexes
                i = 0;
                j = 0;

                unsafe
                {
                    // Fill audio buffer for all channels, multiply samples by volume range [0, 1]
                    audioBuffer = (float*)audioOutputBuffer.ToPointer();

                    while (i < blockSize * AudioParameters.NbChannels)
                    {
                        for (k = 0; k < AudioParameters.NbChannels; k++)
                            audioBuffer[i++] = host.VstOutputBuffers[k][j] * AudioParameters.globalVolume;
                        host.IncrementSamplePos();
                        j++;
                    }
                }
            }
            else if (host == null)
            {
                // Check if pointer to VstHost is available
                //host = VstHostActions.Obj.Host;
            }

            return PortAudio.PaStreamCallbackResult.paContinue;
        }