MIDI Plugins - VST Module Architecture vs. VSTi v2.4

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

mucoder wrote:...would be really useful to have a wiki with a compendium of such stuff for any host under the sun. Am willing to contribute the little I know if someone would host the wiki...
+1 for us fledgling types :tu:!
Image
stay juicy!

Post

I think I figured out the timing bug. I was doing this in my clock class when the position was updated:

Code: Select all

void Clock::SetPosition(double position)
{
    assert(position >= 0.0);

    this->position = position;

    tickCount = static_cast<unsigned int>(position * ppqn);
}
That last line was the problem. Say there's a position descrepancy between my clock and the host, and that causes the tickCount to jump ahead a tick or two when the position is updated. Leaving that line in can cause the Advance method to not notice the change in ticks and return false.

However, if I take that line out, the Advance method notices the difference and returns true. This ensures that the arpeggiator checks the tick count to see if it's time to play a note. In other words, my arp was missing a few ticks here and there in FL, and that was causing the timing problem.

Now, it would be better if my algo was so tight till there would be no difference between my position and the host over time. But I'm not sure how to do that. I could switch to integer math to avoid accumulating errors in floating point math. I'll have to think about it.

Post

yeah, good catch, missing some ticks would not be a Good Thing indeed :-)

at first sight, the rest of the approach looks sound, except maybe for this:

"positionIncrement" here means "number of quarter notes per sample", right? That would be a quite small value I guess. Which you are adding to itself in a loop. Not sure if floating point precision limits come into play here...

is a long shot, but what if you'd code the clock position without an iterative addition? So, given you are at i'th sample in buf, make clock pos a function of i, like so:

Code: Select all

double positionAtStartOfBuf = positionYouGotFromHost;
double position = positionAtStartOfBuf + (i * positionIncrement);
just an idea - not sure it would solve all problems tho
tonespace / hypercyclic
Image

Post

btw your tickCount, I was assuming its lifetime was only for the duration of a processReplacing() call?

if not, that is, if you keep a global tickCount (across processReplacing buffers), then host/plugin drift is indeed a risk

myself I tend to calculate everything purely from the position the host provides. So to the plugin clock is completely stateless and purely a function of the current time, as provided by host. Only inside a processReplacing call I need to advance that time a little bit of course.

That way the host can do whatever it likes with time (even jumping back and forth if it wanted to) and the plug just slavishly follows. That pattern is also really useful if you'd like to sync with external midi clock and such.
tonespace / hypercyclic
Image

Post

mucoder wrote: myself I tend to calculate everything purely from the position the host provides. So to the plugin clock is completely stateless and purely a function of the current time, as provided by host. Only inside a processReplacing call I need to advance that time a little bit of course.

That way the host can do whatever it likes with time (even jumping back and forth if it wanted to) and the plug just slavishly follows. That pattern is also really useful if you'd like to sync with external midi clock and such.
I see what you're saying about the sync code being reentrant.

The thing about FL that I noticed, is that it only updated the position every other buffer. So for two consecutive buffers, calls to the getTimeInfo method returns the same value. This could cause a problem if you're not aware of it. Say that a note needs to be triggerd at a certain position. It could get triggered twice if the algorithm assumes that the position value is continually increasing from buffer to buffer.

I was not even aware of this because the way I have my framework set up, it only fires off a "PositionChanged" event when the position value has actually changed. Between those changes, I keep track of my own position value. It wasn't until I printed out the results of getTimeInfo in my debugger did I see what FL was doing.

Post

The thing about FL that I noticed, is that it only updated the position every other buffer
jeez, that's good to know - thx Leslie.

Meanwhile, checked the VST spec - apparently they can do that. But I don't like it at all - messes with the simple stateless model that I prefer

edit: VST spec allows them to set a flag that they are not returning the position even when asked for. Is that what you are seeing? Or is FL just returning the same position twice?
tonespace / hypercyclic
Image

Post

mucoder wrote:edit: VST spec allows them to set a flag that they are not returning the position even when asked for. Is that what you are seeing? Or is FL just returning the same position twice?
FL is updating the position every other buffer. Each call to getTimeInfo with the kVstPpqPosValid succeeds. Here's my code for testing the position value; it's called at the beginning of each block:

Code: Select all

VstTimeInfo *ti = getTimeInfo(kVstTempoValid | 
                              kVstPpqPosValid | 
                              kVstTransportPlaying);

if(ti != 0)
{  
    if(ti->flags & kVstTransportPlaying)
    {
        if(ti->flags & kVstPpqPosValid)
        {
            char text[128];
            sprintf(text, "Tempo: %f\tPosition: %f\n", tempo, ti->ppqPos);
            ::OutputDebugString(text);
        }
    }
}
A small sampling of the print out:

Code: Select all

Position: 0.000000
Position: 0.000000
Position: 0.010423
Position: 0.010423
Position: 0.020847
Position: 0.020847
Position: 0.031270
Position: 0.031270
Tempo is 140, sample rate 44100, and buffer size is 12592 samples (a pretty large buffer).

Edit: I'm using FL Studio 8.0.2 demo version

Post

I changed the code a little so if kVstPpqPosValid fails, it will print that out, too:

Code: Select all

if(ti->flags & kVstPpqPosValid)
{
    char text[128];
    sprintf(text, "Position: %f\n", ti->ppqPos);
    ::OutputDebugString(text);
}
else
{
    char text[128];
    sprintf(text, "%s\n", "kVstPpqPosValid Failed");
    ::OutputDebugString(text);
}
Same results. kVstPpqPosValid is succeeding, but I'm getting the same position value for every two consecutive buffers.

Post

I don't use FL Studio, so can't tell anything pro/contra its timing, but how about the samplePos ? Does at least this value change per block ? Maybe you could also print out the number of frames that are processed per block, to see how much of a relative error is produced.

Post

Cubase (like a bunch of other hosts) doesn't take care of the midi routing and the destination synth may be processed before the midi plug, in which case the events are delayed to the next buffer with a zero timestamp.

With Sonar it's even more interesting, if you stack multiple midi plugs the events get delayed by one buffer after each plug.

The good part, to add some variations to your timing, just change the audio buffer size.

Reaper, Bidule, Vsthost and probably others handle that correctly.

Reaper really does a good job, even when sending the midi to another track, it will take care of processing the plugs in the correct order, even with multicores enabled.

Post

plastique wrote:I don't use FL Studio, so can't tell anything pro/contra its timing, but how about the samplePos ? Does at least this value change per block ? Maybe you could also print out the number of frames that are processed per block, to see how much of a relative error is produced.
Interesting...

I've modified my test code to print out the block size, sample position, and PPQN position. Here's a sample of the results:

Code: Select all

Block Size: 88
Sample Position: 0.000000
PPQ Position: 0.000000
Block Size: 88
Sample Position: 197.000000
PPQ Position: 0.010423
Block Size: 88
Sample Position: 197.000000
PPQ Position: 0.010423
Block Size: 88
Sample Position: 394.000000
PPQ Position: 0.020847
Block Size: 88
Sample Position: 394.000000
PPQ Position: 0.020847
Block Size: 88
Sample Position: 591.000000
PPQ Position: 0.031270
Block Size: 88
Sample Position: 591.000000
PPQ Position: 0.031270
Block Size: 88
Sample Position: 591.000000
PPQ Position: 0.031270
Block Size: 88
Sample Position: 788.000000
PPQ Position: 0.041693
Block Size: 88
Sample Position: 788.000000
PPQ Position: 0.041693
Block Size: 88
Sample Position: 984.000000
PPQ Position: 0.052063
Block Size: 88
Sample Position: 984.000000
PPQ Position: 0.052063
Block Size: 88
Sample Position: 1181.000000
PPQ Position: 0.062487
Block Size: 88
Sample Position: 1181.000000
PPQ Position: 0.062487
Block Size: 88
Sample Position: 1378.000000
PPQ Position: 0.072910
Block Size: 88
Sample Position: 1378.000000
PPQ Position: 0.072910
Block Size: 88
Sample Position: 1575.000000
PPQ Position: 0.083333
Block Size: 88
Sample Position: 1575.000000
PPQ Position: 0.083333
Note that I set the buffer size in the Audio Preference section to 12,592. Why it's using block sizes of only 88 samples, I dunno.

If someone has the time, could they confirm my results? I'm being careful in trying not to overlook something that would skew the results in some way, but I could be missing something.

I'll do the exact same tests and Reaper and report back.
Last edited by Leslie Sanford on Mon Jun 22, 2009 3:58 pm, edited 1 time in total.

Post

Results with Reaper:

Code: Select all

Block Size: 10000
Sample Position: 0.000000
PPQ Position: 0.000000
Block Size: 10000
Sample Position: 10000.000000
PPQ Position: 0.453515
Block Size: 10000
Sample Position: 20000.000000
PPQ Position: 0.907029
Block Size: 10000
Sample Position: 30000.000000
PPQ Position: 1.360544
Block Size: 10000
Sample Position: 40000.000000
PPQ Position: 1.814059
Block Size: 10000
Sample Position: 50000.000000
PPQ Position: 2.267574
Block Size: 10000
Sample Position: 60000.000000
PPQ Position: 2.721088
Block Size: 10000
Sample Position: 70000.000000
PPQ Position: 3.174603
Block Size: 10000
Sample Position: 80000.000000
PPQ Position: 3.628118
Block Size: 10000
Sample Position: 90000.000000
PPQ Position: 4.081633
Block Size: 10000
Sample Position: 100000.000000
PPQ Position: 4.535147
Block Size: 10000
Sample Position: 110000.000000
PPQ Position: 4.988662
Block Size: 10000
Sample Position: 120000.000000
PPQ Position: 5.442177
Block Size: 10000
Sample Position: 130000.000000
PPQ Position: 5.895692
Block Size: 10000
Sample Position: 140000.000000
PPQ Position: 6.349206
Block Size: 10000
Sample Position: 150000.000000
PPQ Position: 6.802721
Block Size: 10000
Sample Position: 160000.000000
PPQ Position: 7.256236
Block Size: 10000
Sample Position: 170000.000000
PPQ Position: 7.709751
Block Size: 10000
Sample Position: 180000.000000
PPQ Position: 8.163265
Block Size: 10000
Sample Position: 190000.000000
PPQ Position: 8.616780
Block Size: 10000
Sample Position: 200000.000000
PPQ Position: 9.070295
Block Size: 10000
Sample Position: 210000.000000
PPQ Position: 9.523810
Block Size: 10000
Sample Position: 220000.000000
PPQ Position: 9.977324
Block Size: 10000
Sample Position: 230000.000000
PPQ Position: 10.430839

Post

Ok, if I turn off the "Used fixed size buffer" under Compatability for my plugin, I get these results in FL:

Code: Select all

Sample Position: 0.000000
PPQ Position: 0.000000
Block Size: 197
Sample Position: 197.000000
PPQ Position: 0.010423
Block Size: 197
Sample Position: 394.000000
PPQ Position: 0.020847
Block Size: 197
Sample Position: 591.000000
PPQ Position: 0.031270
Block Size: 196
Sample Position: 788.000000
PPQ Position: 0.041693
Block Size: 197
Sample Position: 984.000000
PPQ Position: 0.052063
Block Size: 197
Sample Position: 1181.000000
PPQ Position: 0.062487
Block Size: 197
Sample Position: 1378.000000
PPQ Position: 0.072910
Block Size: 197
Sample Position: 1575.000000
PPQ Position: 0.083333
Block Size: 197
Sample Position: 1772.000000
PPQ Position: 0.093757
Block Size: 197
Sample Position: 1969.000000
PPQ Position: 0.104180
Block Size: 196
Sample Position: 2166.000000
PPQ Position: 0.114603
Block Size: 197
Sample Position: 2362.000000
PPQ Position: 0.124974
Block Size: 197
Sample Position: 2559.000000
PPQ Position: 0.135397
Block Size: 197
Sample Position: 2756.000000
PPQ Position: 0.145820
Block Size: 197
Sample Position: 2953.000000
PPQ Position: 0.156243
Block Size: 197
Sample Position: 3150.000000
PPQ Position: 0.166667
Block Size: 197
Sample Position: 3347.000000
PPQ Position: 0.177090
Block Size: 197
Sample Position: 3544.000000
PPQ Position: 0.187513
Block Size: 197
This seems more sane. The PPQ Position and sample position are updated every block. I'm still puzzled why the block is so small given what I have it set to, but my plugins make no assumption about the block size, so this doesn't affect my algorithms.

Post

Ok, so what appears to be happening in FL is that when the fixed buffer size mode is used, the sample position and the PPQ position are updated every other block:

Code: Select all

Block Size: 88 
Sample Position: 0.000000 
PPQ Position: 0.000000 
Block Size: 88 
Sample Position: 197.000000 
PPQ Position: 0.010423 
Block Size: 88 
Sample Position: 197.000000 
PPQ Position: 0.010423 
Block Size: 88 
Sample Position: 394.000000 
PPQ Position: 0.020847 
However, the sample position in this example seem to be off. After two blocks of 88 samples, we should have a sample position of 176, not 197. And I'm guessing the PPQ position value is off as well.

When the fixed buffer option is turned off, sample position and PPQ position are updated every block (as expected):

Code: Select all

Sample Position: 0.000000 
PPQ Position: 0.000000 
Block Size: 197 
Sample Position: 197.000000 
PPQ Position: 0.010423 
Block Size: 197 
Sample Position: 394.000000 
PPQ Position: 0.020847 
Block Size: 197 
Sample Position: 591.000000 
PPQ Position: 0.031270 
Block Size: 196 
Sample Position: 788.000000 
PPQ Position: 0.041693
And the values are accurate [edit](I think). That last block size is 196, which should put the sample position at 787. Unless I'm missing something. :? [/edit]
Last edited by Leslie Sanford on Mon Jun 22, 2009 5:52 pm, edited 1 time in total.

Post

I would get in touch with the FL devs, they're quite approachable. tony tony chopper would probably be your best bet to start with..
Image

Post Reply

Return to “DSP and Plugin Development”