vacaboja / tg

A program for timing mechanical watches
GNU General Public License v2.0
249 stars 62 forks source link

implementing self-calibration #40

Open drmatthewclark opened 1 year ago

drmatthewclark commented 1 year ago

I've implemented a self calibration. It depends on the computer clock being regulated to at least one second per day. On Linux systems NTPD regulates the time far more accurately. By comparing the sound data frame count to the clock one can accurately calibrate the actual frequency. Since the computer OS is not real time, one has to wait considerable time to get a horologically accurate rate, between 12 and 24 hours.

The self calibration watches a window average of the computed calibration correction, and when it is less than 0.001 s/day it will start updating the calibration every 10 minutes. I've attached two graphs of calibration value vs runtime. One can see that the calibration varies a lot a first, but after about 24 hours the variation is far less than the 0.1 s/day increment that the program currently uses to set the calibration. Just leave your computer running with the tg-timer for a day and you are all set.

calibration

calibration-close

if someone else has a fork, I could add it to yours if you like.

xyzzy42 commented 1 year ago

My fork at xyzzy42/tg is the only one to be active. This repo hasn't been updated in a few years.

Did you use the timestamps from port audio in the audio callback? Or just timestamp the data as it arrived?

I'm surprised it take so long to stabilize. ntpd or chrony both stabilize quite a bit quicker and the ntp data has even more error than audio timestamps.

drmatthewclark commented 1 year ago

it is using the system time function gettimeofday(). Using the port audio for the time will just give the rate of the A/D converter, will give you the nominal sample rate which is what we need to calibrate. The ntp time is stable, but the gap between the audio callback and calling gettimeofday varies a lot in milliseconds since the OS is also doing other tasks, and horological precision requires 1 part in a million precision to be accurate to 0.1 s/d.

The graphs are of the number of frames / seconds of capture, so you can see the rate slowly asymptotically approach the actual rate. The dips are where the computer was busy doing something else at the same time I presume.

I had written my own timers and so basically implemented the same algorithm. It adds about 10 lines to audio.c.

xyzzy42 commented 1 year ago

The time in the port audio callback, at least on Linux, will be the system time. Roughly the same clock as with gettimeofday(). But it can be done in the interrupt handler of the audio driver, so there will be a lot less jitter since the delay between then the interrupt handler receives notification of the audio and when the port audio callback allows your code to run will vary quite a bit.

IIRC, ALSA can use a special clock provided by the audio hardware, but this is only supported on handful of "pro" audio cards with good ALSA drivers and not the default anyway.

You'll need to use the ALSA device directly to get the timestamps. Going through pulseaudio causes the timestamps to be lost. My version of tg has a nice audio setup dialog for not using the system default device. I don't know if pipewire fixes this no timestamps issue or not.

If you tried this, I think you'll find that the graphs converge to the same rate, but there is much less jitter in the audio timestamp.

Better than gettimeofday() would be the modern POSIX API, clock_gettime(). This gives you nanoseconds. It also lets one specify which system clock is used. gettimeofday() is now more of a wrapper around clock_gettime() with a clock of CLOCK_REALTIME. The better choice is CLOCK_MONOTONIC, which is not affected by coarse clock set operations and never goes backward. It *is affected by ntp adjustments, i.e. frequency skewing, which of course we want. The ALSA timestamp, returned by port audio, uses CLOCK_MONOTONIC. So it's roughly the same clock, but better due to the clock adjustment issue.