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

# Accela Filter

> Spline-based acceleration filter with velocity-dependent smoothing for fast action games

The Accela filter uses configurable spline curves to provide velocity-dependent smoothing. It applies more filtering to slow movements and less filtering to fast movements, making it ideal for fast-paced action games.

<Note>
  The Accela filter is the simplest filter to configure - it has only 4 parameters and uses predefined acceleration curves for responsiveness.
</Note>

## How It Works

The Accela filter calculates delta (rate of change) for each axis, applies a deadzone, then looks up a multiplier from a spline curve based on the velocity:

1. **Calculate delta**: `delta = input - last_output`
2. **Apply deadzone**: Suppress movement below threshold
3. **Normalize**: Divide by sensitivity threshold
4. **Spline lookup**: Get multiplier from acceleration curve
5. **Apply**: Scale and integrate delta over time

```mermaid theme={null}
graph LR
    A[Input Delta] --> B{> Deadzone?}
    B -->|No| C[Delta = 0]
    B -->|Yes| D[Subtract Deadzone]
    D --> E[Normalize by Sensitivity]
    E --> F[Spline Lookup]
    F --> G[Scale by dt]
    G --> H[Add to Output]
```

### Acceleration Curves

The filter uses predefined spline curves that map normalized velocity to output multiplier:

**Rotation spline:**

```cpp theme={null}
{ x: 9.0,  y: 300 },  // Very fast rotation → huge multiplier
{ x: 8.0,  y: 200 },
{ x: 5.0,  y: 100 },
{ x: 2.5,  y: 35 },
{ x: 1.5,  y: 8 },
{ x: 1.0,  y: 1.5 },   // At sensitivity threshold
{ x: 0.5,  y: 0.4 },   // Slow movement → heavy smoothing
```

**Translation spline:**

```cpp theme={null}
{ x: 9.0,  y: 200 },
{ x: 8.0,  y: 150 },
{ x: 7.0,  y: 110 },
{ x: 5.0,  y: 60 },
{ x: 3.0,  y: 24 },
{ x: 2.0,  y: 7.5 },
{ x: 1.66, y: 4.5 },
{ x: 1.33, y: 2.25 },
{ x: 0.66, y: 0.75 },
{ x: 0.33, y: 0.375 },
{ x: 0.0,  y: 0.0 },   // No movement → no output
```

<Accordion title="Understanding the spline curves">
  The X-axis represents velocity normalized by the sensitivity parameter.

  * X = 1.0 means moving at exactly the sensitivity threshold
  * X = 2.0 means moving twice as fast
  * X = 0.5 means moving at half the threshold

  The Y-axis is the output multiplier applied to the delta.

  * Y = 1.0 means output equals input (no acceleration)
  * Y > 1.0 means output is amplified (acceleration)
  * Y \< 1.0 means output is reduced (smoothing)
</Accordion>

## Algorithm

### Rotation Processing

```cpp theme={null}
// Calculate delta with angle wrap-around
for (unsigned i = 3; i < 6; i++) {
    double d = input[i] - last_output[i];
    if (fabs(d) > 180.0)
        d -= copysign(360.0, d);
    
    // Apply deadzone
    if (fabs(d) > rot_deadzone)
        d -= copysign(rot_deadzone, d);
    else
        d = 0;
    
    // Normalize by sensitivity threshold
    deltas[i] = d / rot_smoothing;
}

// Lookup from spline as a 3D vector (maintains proportions)
do_deltas(&deltas[Yaw], &output[Yaw], [this](double x) {
    return spline_rot.get_value_no_save(x);
});
```

### Translation Processing

```cpp theme={null}
// Calculate delta
for (unsigned i = 0; i < 3; i++) {
    double d = input[i] - last_output[i];
    
    // Apply deadzone
    if (fabs(d) > pos_deadzone)
        d -= copysign(pos_deadzone, d);
    else
        d = 0;
    
    // Normalize by sensitivity threshold
    deltas[i] = d / pos_smoothing;
}

// Lookup from spline as a 3D vector
do_deltas(&deltas[TX], &output[TX], [this](double x) {
    return spline_pos.get_value_no_save(x);
});
```

### Vector-Proportional Processing

The `do_deltas` function ensures that the X/Y/Z components maintain their relative proportions:

```cpp theme={null}
// Calculate 3D distance
double dist = sqrt(deltas[0]^2 + deltas[1]^2 + deltas[2]^2);

// Lookup multiplier based on distance
double multiplier = spline(dist);

// Calculate normalized direction
for (k = 0; k < 3; k++)
    norm[k] = fabs(deltas[k]) / dist;

// Apply multiplier proportionally
for (k = 0; k < 3; k++)
    output[k] = signum(deltas[k]) * norm[k] * multiplier;
```

This ensures that diagonal movements don't cause "staircase" artifacts where one axis moves faster than another.

<Note>
  The filter multiplies output by `dt` (delta time) to ensure frame-rate independence. The same settings work regardless of tracking frequency.
</Note>

## Parameters

The Accela filter has only 4 parameters:

<ParamField path="rotation_sensitivity" type="slider" default="1.5" range="[0.05, 2.5]">
  Rotation sensitivity threshold (degrees/sec).

  * **Lower values**: More aggressive acceleration, faster response
  * **Higher values**: More smoothing overall, slower response

  This is the velocity at which the spline is sampled at X=1.0.

  **Default**: `1.5`
</ParamField>

<ParamField path="translation_sensitivity" type="slider" default="1.0" range="[0.05, 1.5]">
  Translation sensitivity threshold (cm/sec).

  * **Lower values**: More acceleration, faster positional response
  * **Higher values**: More smoothing, slower positional response

  **Default**: `1.0`
</ParamField>

<ParamField path="rotation_deadzone" type="slider" default="0.03" range="[0.0, 0.2]" unit="degrees">
  Rotation deadzone in degrees. Movements below this are set to zero.

  * **Too low**: Visible jitter when trying to hold still
  * **Too high**: Small movements feel unresponsive

  **Default**: `0.03°`
</ParamField>

<ParamField path="translation_deadzone" type="slider" default="0.1" range="[0.0, 1.0]" unit="cm">
  Translation deadzone in centimeters. Movements below this are set to zero.

  **Default**: `0.1 cm` (1mm)
</ParamField>

## Tuning Guide

### For Fast Action Games (Recommended)

Minimize lag and maximize acceleration:

```yaml theme={null}
rotation_sensitivity: 1.0
translation_sensitivity: 0.75
rotation_deadzone: 0.02
translation_deadzone: 0.05
```

<Accordion title="Why these settings?">
  * Low sensitivity thresholds = aggressive acceleration kicks in early
  * Small deadzones = don't suppress intentional micro-movements
  * Fast movements get huge multipliers from the spline
</Accordion>

### For Flight Simulators

Balance smoothness with responsiveness:

```yaml theme={null}
rotation_sensitivity: 2.0
translation_sensitivity: 1.2
rotation_deadzone: 0.05
translation_deadzone: 0.15
```

<Accordion title="Why these settings?">
  * Higher sensitivity thresholds = more smoothing overall
  * Larger deadzones = eliminate cockpit vibration jitter
  * Still responsive for intentional head movements
</Accordion>

### For Racing Simulators

Smooth left/right with responsive lean:

```yaml theme={null}
rotation_sensitivity: 1.8
translation_sensitivity: 0.8
rotation_deadzone: 0.04
translation_deadzone: 0.08
```

<Accordion title="Why these settings?">
  * Moderate rotation sensitivity = smooth panning to look at apex
  * Lower translation sensitivity = fast lean response for g-forces
  * Balanced deadzones for stability during vibration
</Accordion>

## Sensitivity vs Smoothing

The sensitivity parameter is **inverse** to smoothing:

| Sensitivity  | Effect on Normalized Velocity      | Smoothing |
| ------------ | ---------------------------------- | --------- |
| Low (0.5)    | Delta of 1.0°/s → X=2.0 on spline  | Less      |
| Medium (1.5) | Delta of 1.0°/s → X=0.67 on spline | Medium    |
| High (2.5)   | Delta of 1.0°/s → X=0.4 on spline  | More      |

**Example:**

* You move your head 2.0°/sec
* Rotation sensitivity is 1.0
* Normalized velocity = 2.0 / 1.0 = 2.0
* Spline at X=2.5 gives Y=35 multiplier
* Output delta = 2.0 \* 35 = 70°/sec
* This creates fast acceleration

<Warning>
  Lower sensitivity values create more aggressive acceleration but also amplify noise. If you see jitter during fast movements, increase sensitivity or increase deadzones.
</Warning>

## Deadzone Behavior

The Accela filter uses **hard deadzones** that subtract from the delta:

```cpp theme={null}
if (fabs(delta) > deadzone)
    delta -= copysign(deadzone, delta);
else
    delta = 0;
```

**Within deadzone:**

* Delta is set to 0
* No output movement

**Just outside deadzone:**

* Deadzone amount is subtracted
* Example: delta=0.05, deadzone=0.03 → effective delta=0.02
* This creates a smooth transition

**Far outside deadzone:**

* Deadzone subtraction is negligible
* Full acceleration effect

<Note>
  The deadzone subtraction prevents a sudden jump when exiting the deadzone. The transition is continuous.
</Note>

## Vector Processing Details

The `do_deltas` function processes X/Y/Z as a 3D vector to maintain proportional movement:

```cpp theme={null}
// Calculate 3D distance
double dist = sqrt(deltas[0]^2 + deltas[1]^2 + deltas[2]^2);

// Calculate normalized direction (unit vector)
for (k = 0; k < 3; k++)
    norm[k] = fabs(deltas[k]) / dist;

// Normalize so components sum to 1.0
double sum = norm[0] + norm[1] + norm[2];
for (k = 0; k < 3; k++)
    norm[k] /= sum;

// Get spline value from 3D distance
double value = spline(dist);

// Apply proportionally to each axis
for (k = 0; k < 3; k++)
    output[k] = signum(deltas[k]) * norm[k] * value;
```

This ensures:

* Diagonal movements don't favor one axis
* Proportions between X/Y/Z are preserved
* No "staircase" effect on diagonal tracking

<Accordion title="Why vector processing matters">
  Without vector processing, if you move diagonally:

  * X delta = 1.0, Y delta = 1.0
  * Both would lookup spline(1.0) independently
  * Output would favor axis-aligned movement

  With vector processing:

  * Distance = sqrt(1^2 + 1^2) = 1.414
  * Lookup spline(1.414) once
  * Split proportionally: X=0.707, Y=0.707
  * Maintains diagonal direction precisely
</Accordion>

## Frame-Rate Independence

The filter multiplies output by `dt` (time since last frame):

```cpp theme={null}
for (unsigned k = 0; k < 6; k++) {
    output[k] *= dt;
    output[k] += last_output[k];
    last_output[k] = output[k];
}
```

This converts from velocity (degrees/sec) to position (degrees):

* At 60 FPS: dt ≈ 0.0167, small increments
* At 120 FPS: dt ≈ 0.0083, even smaller increments
* Same position reached regardless of frame rate

<Note>
  You don't need to adjust settings when changing tracker frequency. The filter automatically compensates.
</Note>

## Comparison to Other Filters

| Feature           | Accela        | EWMA        | Hamilton  | Alpha Spectrum |
| ----------------- | ------------- | ----------- | --------- | -------------- |
| Complexity        | Low           | Medium      | Medium    | High           |
| Parameters        | 4             | 3           | 8         | 8+             |
| Adaptation        | Velocity      | Noise       | Distance  | Multi-modal    |
| CPU usage         | Low           | Low         | Low       | Medium         |
| Best for          | Fast action   | Auto smooth | Cinematic | Advanced users |
| Deadzones         | Hard subtract | None        | Soft ramp | Soft ramp      |
| Vector processing | ✅ Yes         | ❌ No        | ❌ No      | ❌ No           |

<Accordion title="When to choose Accela">
  **Choose Accela if:**

  * You play fast-paced action games
  * You want aggressive acceleration on fast movements
  * You prefer simple, predictable settings
  * You want minimal CPU overhead

  **Choose something else if:**

  * You want automatic noise adaptation (use EWMA)
  * You need quaternion rotation (use Hamilton)
  * You want predictive filtering (use Alpha Spectrum)
  * You need separate rotation/translation curves
</Accordion>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Movements feel too fast/twitchy">
    * Increase sensitivity values (rotation and/or translation)
    * This shifts you down the spline curve, reducing multipliers
    * Try increasing by 0.3-0.5 at a time
  </Accordion>

  <Accordion title="Feels laggy/sluggish">
    * Decrease sensitivity values
    * This shifts you up the spline curve, increasing multipliers
    * Try decreasing by 0.2-0.3 at a time
  </Accordion>

  <Accordion title="Jitter when holding still">
    * Increase deadzones slightly
    * Rotation: try 0.05-0.08
    * Translation: try 0.15-0.25
    * Check tracker mounting for vibration
  </Accordion>

  <Accordion title="Small movements ignored">
    * Decrease deadzones
    * Minimum useful values:
      * Rotation: 0.01-0.02
      * Translation: 0.05-0.08
    * Below this, sensor noise dominates
  </Accordion>

  <Accordion title="Diagonal movements feel wrong">
    This should not happen - the filter uses vector processing.

    Check that you're using a recent version of OpenTrack. Older versions may have per-axis processing.
  </Accordion>
</AccordionGroup>

## Advanced: Custom Spline Curves

<Warning>
  Modifying spline curves requires recompiling OpenTrack. This is for advanced users only.
</Warning>

The spline points are defined in `accela-settings.hpp`:

```cpp theme={null}
static constexpr gains const rot_gains[] {
    { 9, 300 },
    { 8, 200 },
    { 5, 100 },
    { 2.5, 35 },
    { 1.5, 8 },
    { 1, 1.5 },
    { .5, .4 },
};
```

You can modify these to create custom response curves:

* **More aggressive**: Increase Y values at high X
* **More smoothing**: Decrease Y values at low X
* **Steeper curve**: Increase the difference between adjacent points
* **Flatter curve**: Decrease the difference between adjacent points

## Code Reference

Relevant source files:

* Implementation: `filter-accela/ftnoir_filter_accela.cpp`
* Header: `filter-accela/ftnoir_filter_accela.h`
* Settings/splines: `filter-accela/accela-settings.hpp`
* Dialog: `filter-accela/ftnoir_filter_accela_dialog.cpp`

### Key Functions

**Vector processing** (`ftnoir_filter_accela.cpp:24-64`):

```cpp theme={null}
static void do_deltas(const double* deltas, double* output, F&& fun)
{
    double norm[3];
    double dist = sqrt(deltas[0]^2 + deltas[1]^2 + deltas[2]^2);
    const double value = fun(dist);
    // ... normalize and apply proportionally
}
```

**Main filter loop** (`ftnoir_filter_accela.cpp:74-162`):

```cpp theme={null}
void accela::filter(const double* input, double *output)
{
    // Calculate deltas with deadzones
    // ...
    
    // Rotation: vector spline lookup
    do_deltas(&deltas[Yaw], &output[Yaw], [this](double x) {
        return spline_rot.get_value_no_save(x);
    });
    
    // Translation: vector spline lookup
    do_deltas(&deltas[TX], &output[TX], [this](double x) {
        return spline_pos.get_value_no_save(x);
    });
    
    // Scale by dt and integrate
    // ...
}
```

## 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 adaptive filter
  </Card>

  <Card title="EWMA Filter" icon="chart-line" href="/filters/ewma">
    Automatic noise-adaptive filtering
  </Card>

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