fbpx
Skip to main content

MIDI Forum

Notifications
Clear all

MIDI timing

5 Posts
2 Users
0 Reactions
167 Views
 TCH
Posts: 4
Active Member
Topic starter
 

Hello there.

I am a MIDI beginner and i am currently developing a small and limited MIDI player and nearly done with it, but there is one last thing i cannot sort out: the correct timing. Most sources i've found states that the amount of microseconds need to be wait after each MIDI event is calculated as 'us-per-pulses * event-delta-time'. 'Event-delta-time' directly precedes the event and 'us-per-pulses' can be calculated as 'tempo / pulses-per-quarter-note'. PPQN is obtained from the header, tempo is set by the set tempo meta event. I implemented the code this way and the MIDI files are playing OK, but they are noticeable slower than they should be. Not much, but enough. (Around 5-15%; did not measured and the amount of slowness is varying from MID to MID.)

Most of these sources did not cover the time signature meta event at all, i mean, if it is used for calculating the event time or not. A stackoverflow post states, that it is, while another that it is not. A webpage acquired back from webarchive.org gave a formula which only used the denominator from the signature in calculating the 'us-per-pulses', but that alone will make no difference if the denominator is 2. (Or even if it is not, but the PPQN in the header is adjusted accordingly to the note lengths.)

Can anyone help me out with this problem? Are my informations correct at all, or am i totally wrong?

Thanks in advance.

 
Posted : 02/07/2024 1:30 pm
 TCH
Posts: 4
Active Member
Topic starter
 

Addition: I've just implemented seeking and MID length measuring (as in time). The seeking and the measurement is perfectly working, so now i understand even less why the timing is off. Is usleep() this unreliable? Edit: It is the same with nanosleep().

This post was modified 3 months ago by TCH
 
Posted : 02/07/2024 3:39 pm
Bavi_H
Posts: 266
Reputable Member
 

The time signature information is used if you are converting to or from an amount of measures.

If you are just converting an amount of ticks into an amount of seconds then you do not need to use the time signautre information in the calculation.

A is a delta time duration in ticks
B is the tempo event value in microseconds per quarter note
C is the header resolution value in ticks per quarter note
D is the duration in microseconds

A × B / C = D


  A ticks        quarter note     B microseconds     D microseconds
            ×  ──────────────  ×  ──────────────  =
               C ticks              quarter note

I found some documentation describing the usleep and nanosleep functions of the Standard C library. They each say they will suspend execution of the calling thread for at least the time specified. In other words, the suspended duration may be more that what was requested.

I don't know what alternative to use instead, but I think the concept to search for is a timer (?).

 
Posted : 02/07/2024 8:44 pm
 TCH
Posts: 4
Active Member
Topic starter
 

Thank you, that is the problem. I wrote this small routine and the difference is much less now:

void prusleep(uint32_t us)
{
	struct timespec tp0, tp1;

	clock_gettime(CLOCK_MONOTONIC_RAW, &tp0);
	tp0.tv_nsec += (us % 1000000) * 1000;
	if (tp0.tv_nsec > 1000000000)
	{
		tp0.tv_nsec -= 1000000000;
		++tp0.tv_sec;
	}
	tp0.tv_sec += us / 1000000;
	do
	{
		usleep(10); // 6% slower if present, 2.25% slower if not
		clock_gettime(CLOCK_MONOTONIC_RAW, &tp1);
	}
	while ((tp1.tv_sec < tp0.tv_sec) || ((tp1.tv_sec == tp0.tv_sec) && (tp1.tv_nsec < tp0.tv_nsec)));
}

But the problem is still present. Even if i take out that usleep() from the waiting cycle, it is still 2.25% slower than it should (this time i measured it) and then it creates a massive CPU load to no surprise.

 
Posted : 03/07/2024 5:00 am
 TCH
Posts: 4
Active Member
Topic starter
 

Originally i did the timing by accumulating each track's delta and then subtract lowest delta from all accumulators and wait that time. For this caused the thread becoming difficultly interruptible because some long deltas, i reorganized it to wait one tick in each round. But this caused low wait times which in turn caused usleep() and nanosleep() waiting too much as you've correctly predicted. I now went back to the original approach for the moment and now even usleep() is only 2.25% slower, but on the other hand even this precise timer is still 2% slower. (1.9% with no internal usleep.)

I am sure i am doing something wrong, but i have no idea what and how could i make the timer more precise. (But logic says, if i check the monotonic clock from a sleepless cycle, that cannot be topped.)

 
Posted : 03/07/2024 6:04 am
Share: