33
44#include < cmath>
55
6+ #pragma GCC diagnostic ignored "-Wdouble-promotion"
7+ #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion"
8+
69// === implementation of the time_postprocessor class ===
710
811using namespace lsl ;
912
13+ // / how many samples have to be seen between clocksyncs?
14+ const uint8_t samples_between_clocksyncs = 50 ;
15+
1016time_postprocessor::time_postprocessor (const postproc_callback_t &query_correction,
1117 const postproc_callback_t &query_srate, const reset_callback_t &query_reset)
12- : samples_seen_( 0.0 ), query_srate_(query_srate), options_(proc_none ),
13- halftime_(api_config::get_instance()->smoothing_halftime()),
18+ : samples_since_last_clocksync(samples_between_clocksyncs ), query_srate_(query_srate),
19+ options_(proc_none), halftime_(api_config::get_instance()->smoothing_halftime()),
1420 query_correction_(query_correction), query_reset_(query_reset), next_query_time_(0.0 ),
15- last_offset_(0.0 ), smoothing_initialized_(false ),
16- last_value_(-std::numeric_limits<double >::infinity()) {}
21+ last_offset_(0.0 ), last_value_(std::numeric_limits<double >::lowest()) {}
22+
23+ void time_postprocessor::set_options (uint32_t options)
24+ {
25+ // bitmask which options actually changed (XOR)
26+ auto changed = options_ ^ options;
27+
28+ // dejitter option changed? -> Reset it
29+ // in case it got enabled, it'll be initialized with the correct t0 when
30+ // the next sample comes in
31+ if (changed & proc_dejitter)
32+ dejitter = postproc_dejitterer ();
33+
34+ if (changed & proc_monotonize)
35+ last_value_ = std::numeric_limits<double >::lowest ();
36+
37+ options_ = options;
38+ }
1739
1840double time_postprocessor::process_timestamp (double value) {
1941 if (options_ & proc_threadsafe) {
@@ -23,19 +45,27 @@ double time_postprocessor::process_timestamp(double value) {
2345 return process_internal (value);
2446}
2547
48+ void time_postprocessor::skip_samples (uint32_t skipped_samples) {
49+ if (options_ & proc_dejitter && dejitter.smoothing_applicable ())
50+ dejitter.samples_since_t0_ += skipped_samples;
51+ }
52+
2653double time_postprocessor::process_internal (double value) {
2754 // --- clock synchronization ---
2855 if (options_ & proc_clocksync) {
2956 // update last correction value if needed (we do this every 50 samples and at most twice per
3057 // second)
31- if ((fmod (samples_seen_, 50.0 ) == 0.0 ) && lsl_clock () > next_query_time_) {
58+ if (++samples_since_last_clocksync > samples_between_clocksyncs &&
59+ lsl_clock () > next_query_time_) {
3260 last_offset_ = query_correction_ ();
61+ samples_since_last_clocksync = 0 ;
3362 if (query_reset_ ()) {
3463 // reset state to unitialized
3564 last_offset_ = query_correction_ ();
36- last_value_ = -std::numeric_limits<double >::infinity ();
37- samples_seen_ = 0 ;
38- smoothing_initialized_ = false ;
65+ last_value_ = std::numeric_limits<double >::lowest ();
66+ // reset the dejitterer to an uninitialized state so it's
67+ // initialized on the next use
68+ dejitter = postproc_dejitterer ();
3969 }
4070 next_query_time_ = lsl_clock () + 0.5 ;
4171 }
@@ -48,51 +78,52 @@ double time_postprocessor::process_internal(double value) {
4878 // --- jitter removal ---
4979 if (options_ & proc_dejitter) {
5080 // initialize the smoothing state if not yet done so
51- if (!smoothing_initialized_ ) {
81+ if (!dejitter. is_initialized () ) {
5282 double srate = query_srate_ ();
53- smoothing_applicable_ = (srate > 0.0 );
54- if (smoothing_applicable_) {
55- // linear regression model coefficients (intercept, slope)
56- w0_ = 0.0 ;
57- w1_ = 1.0 / srate;
58- // forget factor lambda in RLS calculation & its inverse
59- lam_ = pow (2.0 , -1.0 / (srate * halftime_));
60- il_ = 1.0 / lam_;
61- // inverse autocovariance matrix of predictors u
62- P00_ = P11_ = 1e10 ;
63- P01_ = P10_ = 0.0 ;
64- // numeric baseline
65- baseline_value_ = value;
66- }
67- smoothing_initialized_ = true ;
68- }
69- if (smoothing_applicable_) {
70- value -= baseline_value_;
71-
72- // RLS update
73- double u1 = samples_seen_; // u = np.matrix([[1.0], [samples_seen]])
74- double pi0 = P00_ + u1 * P10_; // pi = u.T * P
75- double pi1 = P01_ + u1 * P11_; // ... (ct'd)
76- double al = value - w0_ - w1_ * u1; // al = value - w.T * u (prediction error)
77- double gam = lam_ + pi0 + pi1 * u1; // gam = lam + pi * u
78- P00_ = il_ * (P00_ - ((pi0 * pi0) / gam)); // Pp = k * pi; P = il * (P - Pp)
79- P01_ = il_ * (P01_ - ((pi0 * pi1) / gam)); // ...
80- P10_ = il_ * (P10_ - ((pi1 * pi0) / gam)); // ...
81- P11_ = il_ * (P11_ - ((pi1 * pi1) / gam)); // ...
82- w0_ += al * (P00_ + P10_ * u1); // w = w + k*al
83- w1_ += al * (P01_ + P11_ * u1); // ...
84- value = w0_ + w1_ * u1; // value = float(w.T * u)
85-
86- value += baseline_value_;
83+ dejitter = postproc_dejitterer (value, srate, halftime_);
8784 }
85+ value = dejitter.dejitter (value);
8886 }
8987
9088 // --- force monotonic timestamps ---
9189 if (options_ & proc_monotonize) {
9290 if (value < last_value_) value = last_value_;
91+ else
92+ last_value_ = value;
9393 }
9494
95- samples_seen_ += 1.0 ;
96- last_value_ = value;
9795 return value;
9896}
97+
98+ postproc_dejitterer::postproc_dejitterer (double t0, double srate, double halftime)
99+ : t0_(static_cast <uint_fast32_t >(t0)) {
100+ if (srate > 0 ) {
101+ w1_ = 1 . / srate;
102+ lam_ = pow (2 , -1 / (srate * halftime));
103+ }
104+ }
105+
106+ double postproc_dejitterer::dejitter (double t) noexcept {
107+ if (!smoothing_applicable ()) return t;
108+
109+ // remove baseline for better numerical accuracy
110+ t -= t0_;
111+
112+ // RLS update
113+ const double u1 = samples_since_t0_++, // u = np.matrix([[1.0], [samples_seen]])
114+ pi0 = P00_ + u1 * P01_, // pi = u.T * P
115+ pi1 = P01_ + u1 * P11_, // ..
116+ al = t - (w0_ + u1 * w1_), // α = t - w.T * u # prediction error
117+ g_inv = 1 / (lam_ + pi0 + pi1 * u1), // g_inv = 1/(lam_ + pi * u)
118+ il_ = 1 / lam_; // ...
119+ P00_ = il_ * (P00_ - pi0 * pi0 * g_inv); // P = (P - k*pi) / lam_
120+ P01_ = il_ * (P01_ - pi0 * pi1 * g_inv); // ...
121+ P11_ = il_ * (P11_ - pi1 * pi1 * g_inv); // ...
122+ w0_ += al * (P00_ + P01_ * u1); // w += k*α
123+ w1_ += al * (P01_ + P11_ * u1); // ...
124+ return w0_ + u1 * w1_ + t0_; // t = float(w.T * u) + t0
125+ }
126+
127+ void postproc_dejitterer::skip_samples (uint_fast32_t skipped_samples) noexcept {
128+ samples_since_t0_ += skipped_samples;
129+ }
0 commit comments