NTP Clock Synchronization¶
Synchronized lyrics require timing precision within tens of milliseconds. Because browser and device system clocks can drift significantly relative to the server and audio source, Cantus implements an NTP-style 4-timestamp clock synchronization protocol over SignalR.
Why Clock Synchronization Matters¶
When Spotify reports that a track is at progress 01:45.320 at timestamp \(T\), the client must accurately render the exact millisecond position as time elapses.
Without clock synchronization: - Client system clock drift creates noticeable desynchronization (lyrics advancing too fast or too slow). - Variable network round-trip latency introduces jitter when playback updates arrive.
The 4-Timestamp Synchronization Algorithm¶
Cantus uses the standard Network Time Protocol (NTP) round-trip model:
sequenceDiagram
autonumber
actor Client as Uno Client
actor Server as SignalR PlaybackHub
Note over Client: t0 = Client Send Time
Client->>Server: SyncClock(t0)
Note over Server: t1 = Server Receive Time<br/>t2 = Server Transmit Time
Server-->>Client: SyncClockResponse(t0, t1, t2)
Note over Client: t3 = Client Receive Time
- Client Send (\(t_0\)): Client records local high-resolution timestamp (\(t_0\)) and sends
SyncClock(t0)to the hub. - Server Receive (\(t_1\)): Server captures timestamp (\(t_1\)) upon receiving the frame.
- Server Transmit (\(t_2\)): Server captures timestamp (\(t_2\)) as it transmits the response payload containing \((t_0, t_1, t_2)\).
- Client Receive (\(t_3\)): Client captures timestamp (\(t_3\)) when the response arrives.
Offset and Jitter Filtering¶
From these four timestamps, Cantus calculates two core metrics:
-
Round-Trip Delay (\(\delta\)):
\[\delta = (t_3 - t_0) - (t_2 - t_1)\]Measures pure network transit time excluding server processing overhead.
-
Clock Offset (\(\theta\)):
\[\theta = \frac{(t_1 - t_0) + (t_2 - t_3)}{2}\]Represents how much the client clock leads or lags behind the server clock.
Moving Average Jitter Filter¶
- When the client connects, it performs a burst of 5 initial sync samples.
- Samples with abnormally high round-trip latency (spikes/outliers) are discarded.
- The remaining samples are averaged to compute a stable base clock offset \(\theta_{\text{stable}}\).
- Periodic background syncs occur every 60 seconds to track long-term clock drift smoothly without visual jumps.
Continuous Playback Interpolation¶
Once the offset is known, the client runs a 60 FPS UI animation timer:
flowchart LR
A["Last Known Playback Snapshot<br/>Progress: 45.200s @ ServerTime"] --> B["Current Client Clock + Offset"]
B --> C["Interpolated Progress = 45.200s + ElapsedTime"]
C --> D["Evaluate Active Lyric Line Index"]
D --> E["Smooth Scroll Animation"]
This ensures fluid, 60fps lyric scrolling even when Spotify polling updates arrive every 500ms over the network.