Timing off, but I can't see why

Hi there, I am trying to get a reliable timing for plugins to come. So I created a timekeeper class t that counts sample blocks and calculates note values in samples and such so other classes can use it.

In the class MidiProcessor, I then use these values and loop over the current block to create the actual events with the corresponding sample number.

Ich checked this a couple of times also debugged the values (sample position, numblocks, samples since start, abd it all looks good and evenb corresponds with my daws samples (at least the debugged values)

Still it sounds just a bit off from time to time, especially with a click. Also when I create notes in the editor of the daw its absolutely perfect and does not sound off.

Can someone look and see if I am doing something wrong or if the concept / approach is wrong? (And maybe tell why and what would be right)

I do not feel like Studio one is changing buffer sizes (aso dbged them out) and also my code should handle that.

Many thanks in advance…

In the processor:

playHead = this->getPlayHead();
playHead->getCurrentPosition(positionInfo);
bpm = positionInfo.bpm;
timeKeeper.setBpm(bpm);
timeKeeper.calculateNoteValues();
midiProcessor.processMidi(midiMessages, timeKeeper);

if (positionInfo.isPlaying)
{
    timeKeeper.keepTime(buffer.getNumSamples());
}
else
{
    timeKeeper.resetTransport();
}

Timekeeper:

void TimeKeeper::keepTime(int samples)
{
numSamples = samples;
tkTransport.tkBar = floor(tkSamplesPassed / tkSamplesPerBar) + 1;
tkTransport.tkQuarter = floor(tkSamplesPassed / tkSamplesPerQuarterNote) + 1 - 4 * (tkTransport.tkBar - 1);
tkTransport.tkEighth = floor(tkSamplesPassed / tkSamplesPerEighthNote) + 1 - 8 * (tkTransport.tkBar - 1);
tkTransport.tkSixteenth = floor(tkSamplesPassed / tkSamplesPerSixteenthNote) + 1 - 16 * (tkTransport.tkBar - 1);
tkTransport.tkThirtySecondth= floor(tkSamplesPassed / tkSamplesPerThirtySecondthNote) + 1 - 32 * (tkTransport.tkBar - 1);
tkTransport.tkSixtyfourth= floor(tkSamplesPassed / tkSamplesPerSixtyFourthNote) + 1 - 64 * (tkTransport.tkBar - 1);
tkSamplesPassed += numSamples;
tkIsPlaying = true;
tkHasStopped = false;
}
void TimeKeeper::resetTransport()
{
tkTransport.tkBar = 1;
tkTransport.tkQuarter = 1;
tkTransport.tkEighth = 1;
tkTransport.tkSixteenth = 1;
tkTransport.tkThirtySecondth = 1;
tkTransport.tkSixtyfourth = 1;
tkSamplesPassed = 0;
tkIsPlaying = false;
tkHasStopped = true;
}
bool TimeKeeper::isPlaying()
{
return tkIsPlaying;
}
bool TimeKeeper::hasStopped()
{
return tkHasStopped;
}

MidiProcessor:

    //MidiBuffer::Iterator it(midiMessages); dont need that right now
    MidiMessage currentMessage;
    int samplePos;
    int numSamples = timeKeeper.numSamples;
    long samplesperQuarterNote = timeKeeper.tkSamplesPerQuarterNote;
    long samplesPassed = timeKeeper.tkSamplesPassed;
    long noteDuration = samplesperQuarterNote * 0.1f;
    uint8 volume = 80;
            
    if (timeKeeper.tkIsPlaying)
    {
         for (int i = 0; i < numSamples; i++)
        {
            if (!noteOn)
            {
                if ((((samplesPassed + i) % samplesperQuarterNote) == 0))
                {
                    auto message = MidiMessage::noteOn(1, 44, volume);
                    midiMessages.addEvent(message, i);
                    noteOnSample = samplesPassed + i;
                    noteOn = true;
                    
                }
            } else
            {
                if ((noteOnSample + noteDuration) == (samplesPassed + i))
                {
                    auto message = MidiMessage::noteOff(1, 44, volume);
                    midiMessages.addEvent(message, i);
                    noteOn = false;
                    noteOnSample = 0;
                }
            }
        }
    }
 
    if (timeKeeper.hasStopped())
    {
        auto message = MidiMessage::allNotesOff(1);
        midiMessages.addEvent(message, numSamples - 1);
    }
}
private:
};

I ran into similar problems when trying to keep time in my own plugins. The main issue you have is that samplesPerQuarterNote etc. will not always be integers. For example at 44.1kHz and 145 BPM the number of samples in a quarter note is 18248.2758621, so quite quickly the errors accumulate enough to be noticeable. Take that into account and you’ll find you can keep time solidly.

Thanyk you,m I picked a rate, wheer the math is good and thhere are no rounding erros, but it does not get better, Also I believe thats not the problem, I might have some general error in my thinking…

Also its always wrong the same way, whic points to a systematic error.

Another thing: I found that my samplesPassed is at first occurence already = the buffer size, I do not see why that could be, any ideas?

And Richie, how did yo solve the rounding issue you had?

OK this is strange or sad I don’t know.

I am using a Windows 10 machine with no special Soundcard and normal windows Audio. I got my music Audio interface out and now its dead on time, also I added round() to all divisions, so it does not dirft off.

But in fact the audio interface made the change! So my code is fine, should at least have installed ASIO4ALL.

Still i do not see why in the first iteration samplesPassed is not 0.

At least now I can go to bed with an eased mind :wink:

I was only interested in aligning events to 16ths, so I used doubles for calculations and fmod(currentSamplePos, samplesPer16th), when the remainder is less than 1 then my events aligned exactly with 16ths from the host. I’m not 100% sure this is the correct way to do it, but it worked for me.