Skip to content

feat: add OneEuro (1€ filter) smoothing utility - #120

Open
edumeneses wants to merge 4 commits into
mainfrom
oneeuro-filter
Open

feat: add OneEuro (1€ filter) smoothing utility#120
edumeneses wants to merge 4 commits into
mainfrom
oneeuro-filter

Conversation

@edumeneses

Copy link
Copy Markdown
Member

Summary

Adds puara_gestures::utils::OneEuro, an implementation of the 1€ filter (Casiez, Roussel & Vogel, CHI 2012). It is a speed-adaptive low-pass: it filters hard when the signal is still (removes jitter) and eases off when the signal moves fast (keeps responsiveness), trading jitter against lag far better than a fixed low-pass or moving average.

This is the de-facto standard smoothing stage for interactive sensors and controllers, and slots in front of any Puara descriptor.

API

puara_gestures::utils::OneEuro smoother{1.0, 0.007}; // mincutoff, beta
double clean = smoother.filter(analogRead(A0));       // clock-timed
double clean = smoother.filter(value, dt);            // explicit step (seconds)
  • mincutoff — cutoff (Hz) at rest; lower = smoother but laggier
  • beta — how much the cutoff opens with speed; higher = less lag on fast moves
  • dcutoff — derivative cutoff (Hz), rarely changed
  • current_value — last output; reset() clears state

Conventions kept

  • Header-only, #pragma once, puara_gestures::utils namespace
  • Public current_value like LeakyIntegrator
  • Monotonic clock (getCurrentTimeMicroseconds) → Arduino loop(), thread, or ossia process
  • Doubles only, self-contained pi constant — no STL/Boost/allocation

Files

  • include/puara/utils/oneeuro.h
  • registered in include/puara/utils.h
  • examples/arduino/utils/oneeuro/oneeuro.ino
  • Catch2 tests in tests/test_utils.cpp ([utils][oneeuro])
  • README utility list entry

Testing

  • OneEuro: 21 assertions / 4 cases pass (first-sample passthrough, constant-signal stability, monotonic step-response lag, jitter-rejection vs fast-tracking tradeoff)
  • Full utils suite: 156 assertions / 27 cases pass (no regressions)

🤖 Generated with Claude Code

https://claude.ai/code/session_0163cH7Znu2oYvnaP7fTEQMN

Adds puara_gestures::utils::OneEuro, the 1€ filter (Casiez et al. 2012):
a speed-adaptive low-pass that removes jitter when the signal is still and
eases off when it moves fast, trading jitter against lag far better than a
fixed low-pass or moving average. It is the de-facto standard smoothing
stage for interactive sensors and controllers.

Configurable via mincutoff/beta/dcutoff. Timing comes from the library
monotonic clock (filter(value)) or an explicit step (filter(value, dt)),
so it behaves the same in an Arduino loop(), a thread, or an ossia score
process. Header-only, doubles only, no STL/Boost/allocation.

Includes an Arduino example, README entry, and Catch2 tests covering the
first-sample passthrough, constant-signal stability, step-response lag,
and the jitter-rejection vs fast-tracking tradeoff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0163cH7Znu2oYvnaP7fTEQMN
Comment thread include/puara/utils/oneeuro.h Outdated
}

private:
static constexpr double pi = 3.14159265358979323846;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::numbers::pi

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @jcelerier — I'm an AI assistant (Claude Code) working on this branch with @emeneses; flagging that up front.

I looked into using std::numbers::pi here and decided against it, for portability reasons:

  • std::numbers::pi requires C++20 <numbers>. It compiles fine in the desktop test build (it forces -std=c++20), but on the embedded side it is a regression: older arduino-esp32 cores (2.x) and various Arduino IDE cores default to gnu++11, where <numbers> is unavailable, so the header would fail to compile there.
  • The library already standardizes on M_PI with a Boost fallback for exactly this reason — see utils.h and wrap.h:
    #ifndef M_PI
    #define M_PI boost::math::constants::pi<double>()
    #endif
    Boost is already a hard dependency (shimmed on Arduino via boost-embedded-190).

So instead of the hardcoded literal I switched to M_PI — same value, matches the surrounding code, and stays portable across cores. oneeuro.h does not include puara/utils.h, so it carries the same guarded M_PI define locally, like wrap.h does. I applied the same change to the two other new headers that had a hardcoded pi (heading.h, multitouch.h).

Happy to move everything to std::numbers::pi if you would rather set a C++20 floor for the library as a whole — just let me know and I will switch them over.

edumeneses and others added 3 commits July 23, 2026 15:43
Address review comment (#120): replace the hardcoded pi literal with
std::numbers::pi now that the header targets C++20. Behaviour unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0163cH7Znu2oYvnaP7fTEQMN
Keep std::numbers::pi where the standard library provides it
(__cpp_lib_math_constants), with a literal fallback otherwise, and only
include <numbers> when present. Ensures the header still compiles under
Arduino/embedded toolchains whose C++ standard may predate the <numbers>
header, so the ESP32/PlatformIO and Arduino CI stay green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0163cH7Znu2oYvnaP7fTEQMN
Revert the std::numbers::pi change. Match the library convention of M_PI
with a Boost fallback (as in wrap.h) so pi stays portable across Arduino
cores that predate the C++20 <numbers> header. oneeuro.h does not include
puara/utils.h, so it defines the same guarded M_PI locally. Behaviour
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0163cH7Znu2oYvnaP7fTEQMN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants