OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
filters.hpp
Go to the documentation of this file.
1#pragma once
2#include <ossia/detail/config.hpp>
3
5#include <ossia/detail/small_vector.hpp>
6
7#include <cmath>
8
9#include <algorithm>
10#include <array>
11#include <cstdint>
12#include <type_traits>
13
30namespace ossia
31{
32
41template <typename T>
42[[nodiscard]] OSSIA_INLINE T lowpass_alpha(T cutoff, T dt) noexcept
43{
44 const T r = T(ossia::two_pi) * cutoff * dt;
45 return r / (r + T(1));
46}
47
58template <typename T>
59[[nodiscard]] OSSIA_INLINE T lag_alpha(T tau, T dt) noexcept
60{
61 return dt / (dt + tau);
62}
63
71template <typename T = float>
73{
74 using value_type = T;
75
77 T y{};
78
81 bool primed{};
82
84 OSSIA_INLINE void reset() noexcept
85 {
86 y = T{};
87 primed = false;
88 }
89
91 OSSIA_INLINE void reset(T x) noexcept
92 {
93 y = x;
94 primed = true;
95 }
96
98 OSSIA_INLINE void assign_parameters(const one_pole_filter&) noexcept { }
99
104 [[nodiscard]] OSSIA_INLINE T operator()(T x, T alpha) noexcept
105 {
106 if(!primed) [[unlikely]]
107 {
108 primed = true;
109 return y = x;
110 }
111
112 // One FMA, versus two multiplies for the alpha * x + (1 - alpha) * y form.
113 return y += alpha * (x - y);
114 }
115};
116
133template <typename T = float>
135{
136 using value_type = T;
137
140
142 T beta{0};
143
146
149
152
155
157 OSSIA_INLINE void reset() noexcept
158 {
159 x_filter.reset();
160 dx_filter.reset();
161 x_prev = T{};
162 }
163
165 OSSIA_INLINE void assign_parameters(const one_euro_filter& p) noexcept
166 {
167 min_cutoff = p.min_cutoff;
168 beta = p.beta;
169 d_cutoff = p.d_cutoff;
170 }
171
179 [[nodiscard]] OSSIA_INLINE T operator()(T x, T dt) noexcept
180 {
181 if(dt <= T(0)) [[unlikely]]
182 return x_filter.primed ? x_filter.y : x;
183
184 // Shared between both alphas: alpha = (k * cutoff) / (k * cutoff + 1).
185 const T k = T(ossia::two_pi) * dt;
186
187 // First sample: no derivative to speak of.
188 const T dx = x_filter.primed ? (x - x_prev) / dt : T(0);
189 x_prev = x;
190
191 const T kd = k * d_cutoff;
192 const T edx = dx_filter(dx, kd / (kd + T(1)));
193
194 const T cutoff = min_cutoff + beta * std::abs(edx);
195 const T kc = k * cutoff;
196 return x_filter(x, kc / (kc + T(1)));
197 }
198};
199
210template <typename T = float, std::size_t N = 8>
212{
213 static_assert(N >= 1, "a moving average needs at least one sample");
214
215 using value_type = T;
216
218 using accumulator_type = std::conditional_t<std::is_same_v<T, float>, double, T>;
219
221 std::array<T, N> history{};
222
225
227 std::uint32_t head{};
228
230 std::uint32_t count{};
231
233 OSSIA_INLINE void reset() noexcept
234 {
236 head = 0;
237 count = 0;
238 }
239
241 OSSIA_INLINE void assign_parameters(const moving_average_filter&) noexcept { }
242
243 [[nodiscard]] OSSIA_INLINE T operator()(T x) noexcept
244 {
245 if(count == N)
246 sum -= history[head];
247 else
248 ++count;
249
250 sum += x;
251 history[head] = x;
252 head = (head + 1 == N) ? 0 : head + 1;
253
254 return T(sum / accumulator_type(count));
255 }
256};
257
271template <typename T = float, std::size_t N = 5>
273{
274 static_assert(N >= 1, "a median needs at least one sample");
275
276 using value_type = T;
277
279 std::array<T, N> history{};
280
282 std::array<T, N> sorted{};
283
285 std::uint32_t head{};
286
288 std::uint32_t count{};
289
291 OSSIA_INLINE void reset() noexcept
292 {
293 head = 0;
294 count = 0;
295 }
296
298 OSSIA_INLINE void assign_parameters(const median_filter&) noexcept { }
299
300 [[nodiscard]] OSSIA_INLINE T operator()(T x) noexcept
301 {
302 const auto begin = sorted.begin();
303
304 if(count == N)
305 {
306 // Drop the sample falling out of the window, then insert the new one.
307 const auto last = begin + N;
308 const auto old = std::lower_bound(begin, last, history[head]);
309 std::move(old + 1, last, old);
310
311 const auto pos = std::lower_bound(begin, last - 1, x);
312 std::move_backward(pos, last - 1, last);
313 *pos = x;
314 }
315 else
316 {
317 const auto last = begin + count;
318 const auto pos = std::lower_bound(begin, last, x);
319 std::move_backward(pos, last, last + 1);
320 *pos = x;
321 ++count;
322 }
323
324 history[head] = x;
325 head = (head + 1 == N) ? 0 : head + 1;
326
327 const std::uint32_t n = count;
328 if(n & 1u)
329 return sorted[n / 2];
330 return (sorted[n / 2 - 1] + sorted[n / 2]) * T(0.5);
331 }
332};
333
344template <typename Filter, std::size_t N = 4>
346{
347 using filter_type = Filter;
348 using value_type = typename Filter::value_type;
349
352 Filter prototype{};
353
355 ossia::small_vector<Filter, N> filters{};
356
363 OSSIA_INLINE void ensure(std::size_t n)
364 {
365 if(filters.size() != n) [[unlikely]]
366 filters.assign(n, prototype);
367 }
368
370 OSSIA_INLINE void configure() noexcept
371 {
372 for(auto& f : filters)
373 f.assign_parameters(prototype);
374 }
375
377 void reset() noexcept
378 {
379 for(auto& f : filters)
380 f.reset();
381 }
382
384 void clear() noexcept { filters.clear(); }
385
393 template <typename... Args>
394 OSSIA_INLINE void operator()(value_type* v, std::size_t n, Args... args) noexcept
395 {
396 ensure(n);
397 for(std::size_t i = 0; i < n; i++)
398 v[i] = filters[i](v[i], args...);
399 }
400};
401
411template <typename T>
412[[nodiscard]] OSSIA_INLINE T unwrap_angle(T a, T reference) noexcept
413{
414 // remainder() lands in [-pi, pi] for a divisor of two_pi.
415 return reference + T(ossia::remainder(a - reference, T(ossia::two_pi)));
416}
417
418}
Definition git_info.h:7
OSSIA_INLINE T lag_alpha(T tau, T dt) noexcept
Smoothing factor of a first-order lag with a given time constant.
Definition filters.hpp:59
OSSIA_INLINE T lowpass_alpha(T cutoff, T dt) noexcept
Smoothing factor of a first-order low-pass, for a sample spaced dt seconds from the previous one.
Definition filters.hpp:42
OSSIA_INLINE T unwrap_angle(T a, T reference) noexcept
Move a onto the branch closest to reference.
Definition filters.hpp:412
Running median over the last N samples.
Definition filters.hpp:273
OSSIA_INLINE void reset() noexcept
Forget the history: the next sample restarts the filter.
Definition filters.hpp:291
std::uint32_t count
How many entries are valid, saturating at N.
Definition filters.hpp:288
std::array< T, N > history
Chronological ring of the window contents.
Definition filters.hpp:279
OSSIA_INLINE void assign_parameters(const median_filter &) noexcept
No runtime parameters: the window size is a template argument.
Definition filters.hpp:298
std::uint32_t head
Where the next sample goes in history.
Definition filters.hpp:285
std::array< T, N > sorted
The same contents, kept sorted over the first count entries.
Definition filters.hpp:282
Simple moving average over the last N samples.
Definition filters.hpp:212
std::uint32_t count
How many entries of history are valid, saturating at N.
Definition filters.hpp:230
std::uint32_t head
Where the next sample goes in history.
Definition filters.hpp:227
std::conditional_t< std::is_same_v< T, float >, double, T > accumulator_type
Widened so that the incremental sum does not drift.
Definition filters.hpp:218
std::array< T, N > history
Chronological ring of the window contents.
Definition filters.hpp:221
OSSIA_INLINE void assign_parameters(const moving_average_filter &) noexcept
No runtime parameters: the window size is a template argument.
Definition filters.hpp:241
OSSIA_INLINE void reset() noexcept
Forget the history: the next sample restarts the filter.
Definition filters.hpp:233
accumulator_type sum
Running sum of the first count entries of the window.
Definition filters.hpp:224
Applies one independent Filter per component of a vector-valued signal.
Definition filters.hpp:346
OSSIA_INLINE void operator()(value_type *v, std::size_t n, Args... args) noexcept
Filter n components in place.
Definition filters.hpp:394
void reset() noexcept
Forget every component's history, keeping the component count.
Definition filters.hpp:377
ossia::small_vector< Filter, N > filters
One filter per component.
Definition filters.hpp:355
OSSIA_INLINE void configure() noexcept
Push the prototype's tuning onto the live filters, keeping their history.
Definition filters.hpp:370
Filter prototype
Definition filters.hpp:352
OSSIA_INLINE void ensure(std::size_t n)
Make sure there are exactly n components.
Definition filters.hpp:363
void clear() noexcept
Drop every component.
Definition filters.hpp:384
One-euro filter: a low-pass whose cutoff rises with the speed of the signal.
Definition filters.hpp:135
OSSIA_INLINE void reset() noexcept
Forget the history: the next sample restarts the filter.
Definition filters.hpp:157
T min_cutoff
Cutoff at zero speed, in Hz. Lower is smoother, and laggier.
Definition filters.hpp:139
OSSIA_INLINE void assign_parameters(const one_euro_filter &p) noexcept
Adopt p's tuning, keeping the history.
Definition filters.hpp:165
one_pole_filter< T > x_filter
Smoother for the value.
Definition filters.hpp:148
OSSIA_INLINE T operator()(T x, T dt) noexcept
Definition filters.hpp:179
T x_prev
Previous raw input.
Definition filters.hpp:154
one_pole_filter< T > dx_filter
Smoother for the derivative.
Definition filters.hpp:151
T beta
How much the speed raises the cutoff. 0 degrades to a plain low-pass.
Definition filters.hpp:142
T d_cutoff
Cutoff of the low-pass applied to the derivative itself, in Hz.
Definition filters.hpp:145
First-order low-pass. The building block for everything below.
Definition filters.hpp:73
OSSIA_INLINE void assign_parameters(const one_pole_filter &) noexcept
No runtime parameters: the coefficient is passed per-sample.
Definition filters.hpp:98
T y
Last output. Only meaningful once primed.
Definition filters.hpp:77
OSSIA_INLINE void reset(T x) noexcept
Restart the filter as if x had just been output.
Definition filters.hpp:91
OSSIA_INLINE T operator()(T x, T alpha) noexcept
Definition filters.hpp:104
OSSIA_INLINE void reset() noexcept
Forget the history: the next sample restarts the filter.
Definition filters.hpp:84
bool primed
Definition filters.hpp:81