-
Notifications
You must be signed in to change notification settings - Fork 70
/
time_postprocessor.cpp
135 lines (115 loc) · 4.36 KB
/
time_postprocessor.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include "time_postprocessor.h"
#include "api_config.h"
#include <cmath>
#include <limits>
#include <utility>
#if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wdouble-promotion"
#elif defined(__clang__)
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"
#endif
// === implementation of the time_postprocessor class ===
using namespace lsl;
/// how many samples have to be seen between clocksyncs?
const uint8_t samples_between_clocksyncs = 50;
time_postprocessor::time_postprocessor(postproc_callback_t query_correction,
postproc_callback_t query_srate, reset_callback_t query_reset)
: samples_since_last_clocksync(samples_between_clocksyncs),
query_srate_(std::move(query_srate)), options_(proc_none),
halftime_(api_config::get_instance()->smoothing_halftime()),
query_correction_(std::move(query_correction)), query_reset_(std::move(query_reset)),
next_query_time_(0.0), last_offset_(0.0), last_value_(std::numeric_limits<double>::lowest()) {
}
void time_postprocessor::set_options(uint32_t options)
{
// bitmask which options actually changed (XOR)
auto changed = options_ ^ options;
// dejitter option changed? -> Reset it
// in case it got enabled, it'll be initialized with the correct t0 when
// the next sample comes in
if(changed & proc_dejitter)
dejitter = postproc_dejitterer();
if(changed & proc_monotonize)
last_value_ = std::numeric_limits<double>::lowest();
options_ = options;
}
double time_postprocessor::process_timestamp(double value) {
if (options_ & proc_threadsafe) {
std::lock_guard<std::mutex> lock(processing_mut_);
return process_internal(value);
}
return process_internal(value);
}
void time_postprocessor::skip_samples(uint32_t skipped_samples) {
if (options_ & proc_dejitter && dejitter.smoothing_applicable())
dejitter.samples_since_t0_ += skipped_samples;
}
double time_postprocessor::process_internal(double value) {
// --- clock synchronization ---
if (options_ & proc_clocksync) {
// update last correction value if needed (we do this every 50 samples and at most twice per
// second)
if (++samples_since_last_clocksync > samples_between_clocksyncs &&
lsl_clock() > next_query_time_) {
last_offset_ = query_correction_();
samples_since_last_clocksync = 0;
if (query_reset_()) {
// reset state to unitialized
last_offset_ = query_correction_();
last_value_ = std::numeric_limits<double>::lowest();
// reset the dejitterer to an uninitialized state so it's
// initialized on the next use
dejitter = postproc_dejitterer();
}
next_query_time_ = lsl_clock() + 0.5;
}
// perform clock synchronization; this is done by adding the last-measured clock offset
// value (typically this is used to map the value from the sender's clock to our local
// clock)
value += last_offset_;
}
// --- jitter removal ---
if (options_ & proc_dejitter) {
// initialize the smoothing state if not yet done so
if (!dejitter.is_initialized()) {
double srate = query_srate_();
dejitter = postproc_dejitterer(value, srate, halftime_);
}
value = dejitter.dejitter(value);
}
// --- force monotonic timestamps ---
if (options_ & proc_monotonize) {
if (value < last_value_) value = last_value_;
else
last_value_ = value;
}
return value;
}
postproc_dejitterer::postproc_dejitterer(double t0, double srate, double halftime)
: t0_(static_cast<uint_fast32_t>(t0)) {
if (srate > 0) {
w1_ = 1. / srate;
lam_ = pow(2, -1 / (srate * halftime));
}
}
double postproc_dejitterer::dejitter(double t) noexcept {
if (!smoothing_applicable()) return t;
// remove baseline for better numerical accuracy
t -= t0_;
// RLS update
const double u1 = samples_since_t0_++, // u = np.matrix([[1.0], [samples_seen]])
pi0 = P00_ + u1 * P01_, // pi = u.T * P
pi1 = P01_ + u1 * P11_, // ..
al = t - (w0_ + u1 * w1_), // α = t - w.T * u # prediction error
g_inv = 1 / (lam_ + pi0 + pi1 * u1), // g_inv = 1/(lam_ + pi * u)
il_ = 1 / lam_; // ...
P00_ = il_ * (P00_ - pi0 * pi0 * g_inv); // P = (P - k*pi) / lam_
P01_ = il_ * (P01_ - pi0 * pi1 * g_inv); // ...
P11_ = il_ * (P11_ - pi1 * pi1 * g_inv); // ...
w0_ += al * (P00_ + P01_ * u1); // w += k*α
w1_ += al * (P01_ + P11_ * u1); // ...
return w0_ + u1 * w1_ + t0_; // t = float(w.T * u) + t0
}
void postproc_dejitterer::skip_samples(uint_fast32_t skipped_samples) noexcept {
samples_since_t0_ += skipped_samples;
}