+1 for us fledgling typesmucoder 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...
MIDI Plugins - VST Module Architecture vs. VSTi v2.4
-
- KVRAF
- 6111 posts since 18 Oct, 2007
-
Leslie Sanford Leslie Sanford https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=131095
- KVRAF
- Topic Starter
- 1640 posts since 4 Dec, 2006
I think I figured out the timing bug. I was doing this in my clock class when the position was updated:
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.
Code: Select all
void Clock::SetPosition(double position)
{
assert(position >= 0.0);
this->position = position;
tickCount = static_cast<unsigned int>(position * ppqn);
}
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.
-
- KVRist
- 277 posts since 19 Aug, 2006 from Leuven, Belgium
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:
just an idea - not sure it would solve all problems tho
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);
-
- KVRist
- 277 posts since 19 Aug, 2006 from Leuven, Belgium
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.
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.
-
Leslie Sanford Leslie Sanford https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=131095
- KVRAF
- Topic Starter
- 1640 posts since 4 Dec, 2006
I see what you're saying about the sync code being reentrant.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.
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.
-
- KVRist
- 277 posts since 19 Aug, 2006 from Leuven, Belgium
jeez, that's good to know - thx Leslie.The thing about FL that I noticed, is that it only updated the position every other buffer
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?
-
Leslie Sanford Leslie Sanford https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=131095
- KVRAF
- Topic Starter
- 1640 posts since 4 Dec, 2006
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: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?
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);
}
}
}
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
Edit: I'm using FL Studio 8.0.2 demo version
-
Leslie Sanford Leslie Sanford https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=131095
- KVRAF
- Topic Starter
- 1640 posts since 4 Dec, 2006
I changed the code a little so if kVstPpqPosValid fails, it will print that out, too:
Same results. kVstPpqPosValid is succeeding, but I'm getting the same position value for every two consecutive buffers.
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);
}
-
- KVRAF
- 3948 posts since 8 Sep, 2003 from germany
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.
m i d i - v s t (free)
-
- KVRian
- 1239 posts since 17 Jul, 2003
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.
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.
-
Leslie Sanford Leslie Sanford https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=131095
- KVRAF
- Topic Starter
- 1640 posts since 4 Dec, 2006
Interesting...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.
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
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.
-
Leslie Sanford Leslie Sanford https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=131095
- KVRAF
- Topic Starter
- 1640 posts since 4 Dec, 2006
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
-
Leslie Sanford Leslie Sanford https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=131095
- KVRAF
- Topic Starter
- 1640 posts since 4 Dec, 2006
Ok, if I turn off the "Used fixed size buffer" under Compatability for my plugin, I get these results in FL:
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.
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-
Leslie Sanford Leslie Sanford https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=131095
- KVRAF
- Topic Starter
- 1640 posts since 4 Dec, 2006
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:
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):
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]
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
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
Last edited by Leslie Sanford on Mon Jun 22, 2009 5:52 pm, edited 1 time in total.
-
- KVRAF
- 8389 posts since 11 Apr, 2003 from back on the hillside again - but now with a garden!
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..



