1#![forbid(unsafe_code)]
23#![deny(missing_docs)]
24
25pub const DEFAULT_SMOOTHING_HALFTIME: f64 = 90.0;
29
30pub const SAMPLES_BETWEEN_CLOCKSYNCS: u8 = 50;
34
35pub const MIN_CLOCKSYNC_INTERVAL: f64 = 0.5;
39
40pub mod flags {
44 pub const NONE: u32 = 0;
46 pub const CLOCKSYNC: u32 = 1;
48 pub const DEJITTER: u32 = 2;
50 pub const MONOTONIZE: u32 = 4;
52 pub const THREADSAFE: u32 = 8;
54 pub const ALL: u32 = 1 | 2 | 4 | 8;
56}
57
58#[derive(Debug, Clone, PartialEq)]
62pub struct Dejitterer {
63 pub t0: u32,
69 pub samples_since_t0: u32,
71 pub w0: f64,
73 pub w1: f64,
75 pub p00: f64,
77 pub p11: f64,
79 pub p01: f64,
81 pub lam: f64,
83}
84
85impl Default for Dejitterer {
86 fn default() -> Self {
90 Dejitterer {
91 t0: 0,
92 samples_since_t0: 0,
93 w0: 0.0,
94 w1: 0.0,
95 p00: 1e10,
96 p11: 1e10,
97 p01: 0.0,
98 lam: 0.0,
99 }
100 }
101}
102
103impl Dejitterer {
104 pub fn new(t0: f64, srate: f64, halftime: f64) -> Self {
110 let mut d = Dejitterer {
111 t0: t0 as u32,
112 ..Default::default()
113 };
114 if srate > 0.0 {
115 d.w1 = 1. / srate;
116 d.lam = 2f64.powf(-1. / (srate * halftime));
117 }
118 d
119 }
120
121 pub fn is_initialized(&self) -> bool {
127 self.t0 != 0
128 }
129
130 pub fn smoothing_applicable(&self) -> bool {
132 self.lam > 0.0
133 }
134
135 pub fn dejitter(&mut self, t: f64) -> f64 {
140 if !self.smoothing_applicable() {
141 return t;
142 }
143
144 let t = t - self.t0 as f64;
146
147 let u1 = self.samples_since_t0 as f64;
148 self.samples_since_t0 = self.samples_since_t0.wrapping_add(1);
149
150 let pi0 = self.p00 + u1 * self.p01;
151 let pi1 = self.p01 + u1 * self.p11;
152 let al = t - (self.w0 + u1 * self.w1);
153 let g_inv = 1. / (self.lam + pi0 + pi1 * u1);
154 let il_ = 1. / self.lam;
155
156 self.p00 = il_ * (self.p00 - pi0 * pi0 * g_inv);
157 self.p01 = il_ * (self.p01 - pi0 * pi1 * g_inv);
158 self.p11 = il_ * (self.p11 - pi1 * pi1 * g_inv);
159 self.w0 += al * (self.p00 + self.p01 * u1);
160 self.w1 += al * (self.p01 + self.p11 * u1);
161
162 self.w0 + u1 * self.w1 + self.t0 as f64
163 }
164
165 pub fn skip_samples(&mut self, skipped: u32) {
169 self.samples_since_t0 = self.samples_since_t0.wrapping_add(skipped);
170 }
171}
172
173pub trait OffsetSource {
178 fn correction(&mut self) -> f64;
180 fn srate(&mut self) -> f64;
182 fn was_reset(&mut self) -> bool;
184 fn clock(&mut self) -> f64;
186}
187
188pub struct PostProcessor {
190 options: u32,
191 halftime: f64,
192 dejitter: Dejitterer,
193 samples_since_last_clocksync: u8,
194 next_query_time: f64,
195 last_offset: f64,
196 last_value: f64,
197}
198
199impl PostProcessor {
200 pub fn new() -> Self {
205 PostProcessor {
206 options: flags::NONE,
207 halftime: DEFAULT_SMOOTHING_HALFTIME,
208 dejitter: Dejitterer::default(),
209 samples_since_last_clocksync: SAMPLES_BETWEEN_CLOCKSYNCS,
210 next_query_time: 0.0,
211 last_offset: 0.0,
212 last_value: f64::MIN,
213 }
214 }
215
216 pub fn set_halftime(&mut self, halftime: f64) {
218 self.halftime = halftime;
219 }
220
221 pub fn options(&self) -> u32 {
223 self.options
224 }
225
226 pub fn dejitterer(&self) -> &Dejitterer {
228 &self.dejitter
229 }
230
231 pub fn set_options(&mut self, options: u32) {
236 let changed = self.options ^ options;
237 if changed & flags::DEJITTER != 0 {
238 self.dejitter = Dejitterer::default();
239 }
240 if changed & flags::MONOTONIZE != 0 {
241 self.last_value = f64::MIN;
242 }
243 self.options = options;
244 }
245
246 pub fn process(&mut self, value: f64, src: &mut impl OffsetSource) -> f64 {
251 let mut value = value;
252
253 if self.options & flags::CLOCKSYNC != 0 {
254 self.samples_since_last_clocksync = self.samples_since_last_clocksync.saturating_add(1);
257 if self.samples_since_last_clocksync > SAMPLES_BETWEEN_CLOCKSYNCS
258 && src.clock() > self.next_query_time
259 {
260 self.last_offset = src.correction();
261 self.samples_since_last_clocksync = 0;
262 if src.was_reset() {
263 self.last_offset = src.correction();
264 self.last_value = f64::MIN;
265 self.dejitter = Dejitterer::default();
266 }
267 self.next_query_time = src.clock() + MIN_CLOCKSYNC_INTERVAL;
268 }
269 value += self.last_offset;
270 }
271
272 if self.options & flags::DEJITTER != 0 {
273 if !self.dejitter.is_initialized() {
274 let srate = src.srate();
275 self.dejitter = Dejitterer::new(value, srate, self.halftime);
276 }
277 value = self.dejitter.dejitter(value);
278 }
279
280 if self.options & flags::MONOTONIZE != 0 {
281 if value < self.last_value {
282 value = self.last_value;
283 } else {
284 self.last_value = value;
285 }
286 }
287
288 value
289 }
290
291 pub fn skip_samples(&mut self, skipped: u32) {
293 if self.options & flags::DEJITTER != 0 && self.dejitter.smoothing_applicable() {
294 self.dejitter.skip_samples(skipped);
295 }
296 }
297}
298
299impl Default for PostProcessor {
300 fn default() -> Self {
301 Self::new()
302 }
303}
304
305#[derive(Debug, Clone)]
307pub struct FixedSource {
308 pub offset: f64,
310 pub srate: f64,
312 pub reset: bool,
314 pub now: f64,
316}
317
318impl OffsetSource for FixedSource {
319 fn correction(&mut self) -> f64 {
320 self.offset
321 }
322 fn srate(&mut self) -> f64 {
323 self.srate
324 }
325 fn was_reset(&mut self) -> bool {
326 self.reset
327 }
328 fn clock(&mut self) -> f64 {
329 self.now
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 fn src(srate: f64) -> FixedSource {
338 FixedSource {
339 offset: 0.0,
340 srate,
341 reset: false,
342 now: 1e9,
343 }
344 }
345
346 #[test]
347 fn no_stage_changes_a_timestamp() {
348 let mut p = PostProcessor::new();
349 let mut s = src(1.0);
350 for t in [2.0, 3.1, 3.0, 5.0, 5.9, 7.1] {
351 assert_eq!(p.process(t, &mut s), t);
352 }
353 }
354
355 #[test]
356 fn clock_sync_adds_the_offset() {
357 let mut p = PostProcessor::new();
359 p.set_options(flags::CLOCKSYNC);
360 let mut s = src(1.0);
361 s.offset = -50.0;
362 for t in [2.0, 3.1, 3.0, 5.0, 5.9, 7.1] {
363 assert!((p.process(t, &mut s) - (t - 50.0)).abs() < 1e-12);
364 }
365 }
366
367 #[test]
368 fn the_clamp_never_lets_a_timestamp_go_backward() {
369 let mut p = PostProcessor::new();
371 p.set_options(flags::MONOTONIZE);
372 let mut s = src(1.0);
373 let input = [2.0, 3.1, 3.0, 5.0, 5.9, 7.1];
374 let want = [2.0, 3.1, 3.1, 5.0, 5.9, 7.1];
375 for (i, t) in input.iter().enumerate() {
376 assert!(
377 (p.process(*t, &mut s) - want[i]).abs() < 1e-12,
378 "sample {i}"
379 );
380 }
381 }
382
383 #[test]
384 fn a_stage_change_clears_that_state() {
385 let mut p = PostProcessor::new();
386 p.set_options(flags::MONOTONIZE);
387 let mut s = src(1.0);
388 p.process(100.0, &mut s);
389 p.set_options(flags::NONE);
391 p.set_options(flags::MONOTONIZE);
392 assert_eq!(p.process(2.0, &mut s), 2.0);
393 }
394
395 #[test]
396 fn a_rate_of_zero_leaves_the_filter_off() {
397 let d = Dejitterer::new(1000.0, 0.0, 90.0);
398 assert!(!d.smoothing_applicable());
399 let mut d = d;
400 assert_eq!(d.dejitter(1234.5), 1234.5);
401 }
402
403 #[test]
404 fn the_baseline_truncates_to_a_whole_number() {
405 let d = Dejitterer::new(1000.75, 100.0, 90.0);
407 assert_eq!(d.t0, 1000);
408 assert!(d.is_initialized());
409 }
410
411 #[test]
412 fn a_first_timestamp_below_one_reads_as_uninitialized() {
413 let d = Dejitterer::new(0.5, 100.0, 90.0);
416 assert_eq!(d.t0, 0);
417 assert!(!d.is_initialized());
418 }
419
420 #[test]
421 fn the_filter_converges_on_a_clean_ramp() {
422 let srate = 100.0;
423 let t0 = 5000.0;
424 let mut d = Dejitterer::new(t0, srate, 90.0);
425 d.dejitter(t0);
426 for i in 0..2000 {
427 let t = t0 + i as f64 / srate;
428 d.dejitter(t);
429 }
430 assert!((d.w1 - 1.0 / srate).abs() < 1e-6, "slope {}", d.w1);
432 assert!(d.w0.abs() < 0.1, "intercept {}", d.w0);
433 }
434
435 #[test]
436 fn the_filter_removes_a_constant_latency_from_the_slope() {
437 let srate = 100.0;
439 let t0 = 5000.0;
440 let latency = 0.05;
441 let mut d = Dejitterer::new(t0, srate, 90.0);
442 d.dejitter(t0);
443 for i in 0..5000 {
444 d.dejitter(t0 + i as f64 / srate + latency);
445 }
446 assert!((d.w1 - 1.0 / srate).abs() < 1e-6);
447 assert!((d.w0 - latency).abs() < 0.1, "intercept {}", d.w0);
448 }
449
450 #[test]
451 fn skipped_samples_move_the_counter() {
452 let mut d = Dejitterer::new(1000.0, 100.0, 90.0);
453 d.dejitter(1000.0);
454 let before = d.samples_since_t0;
455 d.skip_samples(7);
456 assert_eq!(d.samples_since_t0, before + 7);
457 }
458
459 #[test]
460 fn clock_sync_refreshes_at_most_twice_per_second() {
461 let mut p = PostProcessor::new();
462 p.set_options(flags::CLOCKSYNC);
463 let mut s = src(1.0);
464 s.offset = 1.0;
465 s.now = 100.0;
466 p.process(0.0, &mut s);
468 s.offset = 99.0;
469 for _ in 0..50 {
471 let got = p.process(0.0, &mut s);
472 assert!((got - 1.0).abs() < 1e-12, "the offset changed too early");
473 }
474 assert!((p.process(0.0, &mut s) - 1.0).abs() < 1e-12);
476 s.now = 101.0;
478 assert!((p.process(0.0, &mut s) - 99.0).abs() < 1e-12);
479 }
480
481 #[test]
482 fn a_reset_gives_the_filter_a_new_baseline() {
483 let mut p = PostProcessor::new();
487 p.set_options(flags::CLOCKSYNC | flags::DEJITTER);
488 let mut s = src(100.0);
489 s.now = 100.0;
490
491 p.process(5000.0, &mut s);
492 assert_eq!(p.dejitterer().t0, 5000);
493
494 for i in 1..60 {
497 p.process(5000.0 + i as f64 / 100.0, &mut s);
498 }
499 assert_eq!(p.dejitterer().t0, 5000, "no query fired yet");
500
501 s.reset = true;
503 s.now = 101.0;
504 p.process(9000.0, &mut s);
505 assert_eq!(p.dejitterer().t0, 9000, "the reset gives a new baseline");
506 }
507
508 #[test]
509 fn the_clamp_state_clears_on_a_reset() {
510 let mut p = PostProcessor::new();
516 p.set_options(flags::CLOCKSYNC | flags::MONOTONIZE);
517 let mut s = src(100.0);
518 s.now = 100.0;
519
520 p.process(500.0, &mut s);
521 for _ in 0..55 {
522 p.process(500.0, &mut s);
523 }
524 assert!((p.process(10.0, &mut s) - 500.0).abs() < 1e-12);
527
528 s.reset = true;
531 s.now = 101.0;
532 assert!((p.process(10.0, &mut s) - 10.0).abs() < 1e-12);
533 }
534}