> ## 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.

# EWMA Filter

> Exponentially Weighted Moving Average filter with automatic dynamic smoothing based on noise detection

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:

```cpp theme={null}
// 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`.

<Note>
  The EWMA filter automatically adapts to your tracking environment over the first \~60 seconds. No manual calibration is required.
</Note>

## Parameters

The EWMA filter has only three parameters, making it easy to configure:

<ParamField path="min_smoothing" type="slider" default="0.02" range="[0.01, 1.0]">
  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%)
</ParamField>

<ParamField path="max_smoothing" type="slider" default="0.7" range="[0.01, 1.0]">
  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%)

  <Note>
    The filter ensures `max_smoothing >= min_smoothing` automatically.
  </Note>
</ParamField>

<ParamField path="smoothing_scale_curve" type="slider" default="0.8" range="[0.1, 5.0]">
  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.8`

  This is the exponent applied to `norm_noise` in the smoothing calculation:

  ```cpp theme={null}
  smoothing = 1.0 - pow(norm_noise, smoothing_scale_curve)
  ```
</ParamField>

## Tuning Guide

### For Simulators and Precision Work

Maximize smoothness when stationary:

```yaml theme={null}
min_smoothing: 0.01
max_smoothing: 0.9
smoothing_scale_curve: 1.2
```

<Accordion title="Why these settings?">
  * 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
</Accordion>

### For Fast Action Games

Minimize lag while maintaining some smoothing:

```yaml theme={null}
min_smoothing: 0.05
max_smoothing: 0.5
smoothing_scale_curve: 0.5
```

<Accordion title="Why these settings?">
  * 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
</Accordion>

### General Purpose (Default)

Balanced for most use cases:

```yaml theme={null}
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:

<Steps>
  <Step title="Measure instantaneous delta">
    Calculate the difference between input and last output for each axis.

    ```cpp theme={null}
    double delta = input[i] - last_output[i]
    ```
  </Step>

  <Step title="Smooth delta over 1/60 sec">
    Apply fast exponential smoothing to the delta itself:

    ```cpp theme={null}
    last_delta[i] += delta_alpha * (delta - last_delta[i])
    ```

    This removes very high-frequency noise components.
  </Step>

  <Step title="Track variance over 60 sec">
    Build a long-term estimate of noise variance:

    ```cpp theme={null}
    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.
  </Step>

  <Step title="Normalize current noise">
    Compare instantaneous noise to historical variance:

    ```cpp theme={null}
    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)
  </Step>

  <Step title="Calculate adaptive alpha">
    Map normalized noise through the curve to determine smoothing:

    ```cpp theme={null}
    double smoothing = 1.0 - pow(norm_noise, smoothing_scale_curve)
    double RC = min_smoothing + smoothing * (max_smoothing - min_smoothing)
    double alpha = dt / (dt + RC)
    ```
  </Step>
</Steps>

<Note>
  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.
</Note>

## Implementation Details

### Time Constants

The filter uses two time constants:

```cpp theme={null}
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:

```cpp theme={null}
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:

```cpp theme={null}
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

<Warning>
  After recentering, the filter needs \~60 seconds to rebuild accurate noise statistics. You may experience suboptimal smoothing during this convergence period.
</Warning>

## Comparison to Other Filters

| Feature              | EWMA     | Alpha Spectrum | Hamilton   | Accela    |
| -------------------- | -------- | -------------- | ---------- | --------- |
| Auto noise detection | ✅ Yes    | ✅ Yes          | ❌ No       | ❌ No      |
| Parameters to tune   | 3        | 8+             | 8          | 4         |
| Convergence time     | \~60s    | Immediate      | Immediate  | Immediate |
| Rotation handling    | Per-axis | Per-axis       | Quaternion | Per-axis  |
| Predictive heads     | ❌ No     | ✅ Yes          | ❌ No       | ❌ No      |
| CPU usage            | Low      | Medium         | Low        | Low       |

<Accordion title="When to use EWMA vs Alpha Spectrum">
  **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
</Accordion>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Filter feels too smooth/laggy">
    * 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
  </Accordion>

  <Accordion title="Still seeing jitter when stationary">
    * 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)
  </Accordion>

  <Accordion title="Slow to respond after recenter">
    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.
  </Accordion>

  <Accordion title="Different behavior for rotation vs translation">
    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.
  </Accordion>
</AccordionGroup>

## 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`):

```cpp theme={null}
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`):

```cpp theme={null}
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`):

```cpp theme={null}
double alpha = dt/(dt + RC);
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Filter Overview" icon="list" href="/filters/overview">
    Compare all available filters
  </Card>

  <Card title="Alpha Spectrum" icon="brain" href="/filters/alpha-spectrum">
    Advanced filter with predictive heads
  </Card>

  <Card title="Hamilton Filter" icon="compass" href="/filters/hamilton">
    Quaternion-based smooth rotations
  </Card>

  <Card title="Accela Filter" icon="gauge" href="/filters/accela">
    Velocity-dependent acceleration filtering
  </Card>
</CardGroup>
