Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/opentrack/opentrack/llms.txt

Use this file to discover all available pages before exploring further.

The EWMA (Exponentially Weighted Moving Average) filter automatically adjusts smoothing strength to minimize lag when moving and minimize noise when still. It’s an excellent general-purpose filter that requires minimal tuning.

How It Works

The EWMA filter uses delta filtering and noise variance detection to distinguish intentional movement from sensor noise:
  1. Delta filtering: Smooths the rate of change over the last 1/60 second (16ms)
  2. Noise estimation: Tracks average noise variance over the last 60 seconds
  3. Adaptive smoothing: Scales filtering from maxSmooth to minSmooth based on detected motion

Algorithm

The filter compares current delta to historical noise statistics:
// Smooth the delta over 1/60 sec
delta_alpha = dt / (dt + 0.0167)  // delta_RC = 1/60
last_delta[i] += delta_alpha * (delta - last_delta[i])

// Smooth noise variance over 60 sec
noise_alpha = dt / (dt + noise_RC)
last_noise[i] = noise_alpha * noise + (1 - noise_alpha) * last_noise[i]

// Normalize noise: 0->1 for 0->9 variances (0->3 std devs)
norm_noise = min(noise / (9.0 * last_noise[i]), 1.0)

// Calculate smoothing strength
smoothing = 1.0 - pow(norm_noise, smoothing_scale_curve)
RC = min_smoothing + smoothing * (max_smoothing - min_smoothing)

// Apply adaptive alpha
alpha = dt / (dt + RC)
output[i] += alpha * (input[i] - output[i])
As the delta increases from 0 to 3 standard deviations of the noise, the filtering scales down from maxSmooth to minSmooth at a rate controlled by smoothing_scale_curve.
The EWMA filter automatically adapts to your tracking environment over the first ~60 seconds. No manual calibration is required.

Parameters

The EWMA filter has only three parameters, making it easy to configure:
min_smoothing
slider
default:"0.02"
Minimum smoothing during fast movement.
  • Lower values: Faster response, more visible jitter during motion
  • Higher values: More smoothing even during fast movements, increased lag
Default: 0.02 (2%)
max_smoothing
slider
default:"0.7"
Maximum smoothing when stationary or moving slowly.
  • Lower values: Less jitter reduction when still, faster initial response
  • Higher values: Maximum jitter suppression, slower initial response
Default: 0.7 (70%)
The filter ensures max_smoothing >= min_smoothing automatically.
smoothing_scale_curve
slider
default:"0.8"
Shape of the transition from max to min smoothing.
  • Lower values (< 1.0): Opens up earlier, more immediate feel
  • Higher values (> 1.0): Keeps heavy smoothing longer during motion
Default: 0.8This is the exponent applied to norm_noise in the smoothing calculation:
smoothing = 1.0 - pow(norm_noise, smoothing_scale_curve)

Tuning Guide

For Simulators and Precision Work

Maximize smoothness when stationary:
min_smoothing: 0.01
max_smoothing: 0.9
smoothing_scale_curve: 1.2
  • Very low min_smoothing (0.01) for minimal lag during intentional movement
  • High max_smoothing (0.9) for maximum jitter reduction when still
  • Curve > 1.0 keeps heavy smoothing applied longer during small motions

For Fast Action Games

Minimize lag while maintaining some smoothing:
min_smoothing: 0.05
max_smoothing: 0.5
smoothing_scale_curve: 0.5
  • Higher min_smoothing (0.05) ensures some smoothing even during fast turns
  • Lower max_smoothing (0.5) reduces lag when starting to move
  • Curve < 1.0 opens up quickly as soon as motion is detected

General Purpose (Default)

Balanced for most use cases:
min_smoothing: 0.02
max_smoothing: 0.7
smoothing_scale_curve: 0.8

How Noise Detection Works

The EWMA filter builds a statistical model of your tracking noise:
1

Measure instantaneous delta

Calculate the difference between input and last output for each axis.
double delta = input[i] - last_output[i]
2

Smooth delta over 1/60 sec

Apply fast exponential smoothing to the delta itself:
last_delta[i] += delta_alpha * (delta - last_delta[i])
This removes very high-frequency noise components.
3

Track variance over 60 sec

Build a long-term estimate of noise variance:
double noise = last_delta[i] * last_delta[i]
last_noise[i] = noise_alpha * noise + (1 - noise_alpha) * last_noise[i]
This converges to the typical noise level of your tracker.
4

Normalize current noise

Compare instantaneous noise to historical variance:
double norm_noise = min(noise / (9.0 * last_noise[i]), 1.0)
  • 0.0 = noise within expected bounds (probably stationary)
  • 1.0 = noise 3+ standard deviations above normal (intentional movement)
5

Calculate adaptive alpha

Map normalized noise through the curve to determine smoothing:
double smoothing = 1.0 - pow(norm_noise, smoothing_scale_curve)
double RC = min_smoothing + smoothing * (max_smoothing - min_smoothing)
double alpha = dt / (dt + RC)
The filter takes approximately 60 seconds to converge to accurate noise statistics after startup. During this period, noise_RC gradually increases from 0 to 60 seconds.

Implementation Details

Time Constants

The filter uses two time constants:
static constexpr double delta_RC = 1.0 / 60.0;  // 16.7ms
static constexpr double noise_RC_max = 60.0;     // 60 seconds
  • delta_RC: Fast response for instantaneous motion detection
  • noise_RC_max: Slow convergence for stable noise statistics

Angle Wrap-Around

Rotation angles (Yaw, Pitch, Roll) crossing ±180° are handled automatically:
if (i >= 3 && fabs(delta) > 180.0)  // rotation axis
{
    delta -= copysign(360.0, delta);
    input_value -= copysign(360.0, input_value);
}
This prevents discontinuities when your head rotates through North.

State Reset

When recentering, the filter resets its state:
void center() override { first_run = true; }
On the next frame:
  • noise_RC resets to 0
  • Output jumps to current input
  • Delta and noise arrays clear
After recentering, the filter needs ~60 seconds to rebuild accurate noise statistics. You may experience suboptimal smoothing during this convergence period.

Comparison to Other Filters

FeatureEWMAAlpha SpectrumHamiltonAccela
Auto noise detection✅ Yes✅ Yes❌ No❌ No
Parameters to tune38+84
Convergence time~60sImmediateImmediateImmediate
Rotation handlingPer-axisPer-axisQuaternionPer-axis
Predictive heads❌ No✅ Yes❌ No❌ No
CPU usageLowMediumLowLow
Use EWMA if:
  • You want automatic adaptation without manual tuning
  • You have a consistent tracking environment
  • You prefer simplicity over maximum control
Use Alpha Spectrum if:
  • You need predictive filtering for extremely low latency
  • You want separate control over rotation vs translation
  • You’re willing to tune advanced parameters
  • You need real-time status monitoring

Troubleshooting

  • Decrease max_smoothing to 0.5 or lower
  • Decrease smoothing_scale_curve to open up faster
  • Consider switching to Accela filter for velocity-based response
  • Increase max_smoothing to 0.8 or 0.9
  • Wait for full convergence (~60 seconds)
  • Check tracker mounting and lighting conditions
  • Verify noise isn’t from external vibration (fans, desk movement)
This is normal behavior. The filter needs ~60 seconds to rebuild noise statistics after recentering.If this is problematic, consider using Hamilton or Alpha Spectrum which don’t require convergence time.
The EWMA filter applies the same parameters to all 6 axes (X, Y, Z, Yaw, Pitch, Roll).If you need independent control, use Alpha Spectrum which has separate rotation and translation parameters.

Code Reference

Relevant source files:
  • Implementation: filter-ewma2/ftnoir_filter_ewma2.cpp
  • Header/settings: filter-ewma2/ftnoir_filter_ewma2.h
  • Dialog: filter-ewma2/ftnoir_filter_ewma2_dialog.cpp

Key Implementation Lines

Noise normalization (ftnoir_filter_ewma2.cpp:74):
double norm_noise = last_noise[i] < 1e-10 ? 0 : 
                    std::fmin(noise/(9.0*last_noise[i]), 1.0);
Smoothing calculation (ftnoir_filter_ewma2.cpp:76-77):
double smoothing = 1.0 - pow(norm_noise, smoothing_scale_curve);
double RC = (min_smoothing + smoothing*(max_smoothing - min_smoothing));
Dynamic alpha (ftnoir_filter_ewma2.cpp:79):
double alpha = dt/(dt + RC);

Next Steps

Filter Overview

Compare all available filters

Alpha Spectrum

Advanced filter with predictive heads

Hamilton Filter

Quaternion-based smooth rotations

Accela Filter

Velocity-dependent acceleration filtering