Skip to main content
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.
The Accela filter is the simplest filter to configure - it has only 4 parameters and uses predefined acceleration curves for responsiveness.

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

Acceleration Curves

The filter uses predefined spline curves that map normalized velocity to output multiplier: Rotation spline:
{ 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:
{ 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
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)

Algorithm

Rotation Processing

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

// 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:
// 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.
The filter multiplies output by dt (delta time) to ensure frame-rate independence. The same settings work regardless of tracking frequency.

Parameters

The Accela filter has only 4 parameters:
rotation_sensitivity
slider
default:"1.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
translation_sensitivity
slider
default:"1.0"
Translation sensitivity threshold (cm/sec).
  • Lower values: More acceleration, faster positional response
  • Higher values: More smoothing, slower positional response
Default: 1.0
rotation_deadzone
slider
default:"0.03"
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°
translation_deadzone
slider
default:"0.1"
Translation deadzone in centimeters. Movements below this are set to zero.Default: 0.1 cm (1mm)

Tuning Guide

Minimize lag and maximize acceleration:
rotation_sensitivity: 1.0
translation_sensitivity: 0.75
rotation_deadzone: 0.02
translation_deadzone: 0.05
  • Low sensitivity thresholds = aggressive acceleration kicks in early
  • Small deadzones = don’t suppress intentional micro-movements
  • Fast movements get huge multipliers from the spline

For Flight Simulators

Balance smoothness with responsiveness:
rotation_sensitivity: 2.0
translation_sensitivity: 1.2
rotation_deadzone: 0.05
translation_deadzone: 0.15
  • Higher sensitivity thresholds = more smoothing overall
  • Larger deadzones = eliminate cockpit vibration jitter
  • Still responsive for intentional head movements

For Racing Simulators

Smooth left/right with responsive lean:
rotation_sensitivity: 1.8
translation_sensitivity: 0.8
rotation_deadzone: 0.04
translation_deadzone: 0.08
  • Moderate rotation sensitivity = smooth panning to look at apex
  • Lower translation sensitivity = fast lean response for g-forces
  • Balanced deadzones for stability during vibration

Sensitivity vs Smoothing

The sensitivity parameter is inverse to smoothing:
SensitivityEffect on Normalized VelocitySmoothing
Low (0.5)Delta of 1.0°/s → X=2.0 on splineLess
Medium (1.5)Delta of 1.0°/s → X=0.67 on splineMedium
High (2.5)Delta of 1.0°/s → X=0.4 on splineMore
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
Lower sensitivity values create more aggressive acceleration but also amplify noise. If you see jitter during fast movements, increase sensitivity or increase deadzones.

Deadzone Behavior

The Accela filter uses hard deadzones that subtract from the delta:
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
The deadzone subtraction prevents a sudden jump when exiting the deadzone. The transition is continuous.

Vector Processing Details

The do_deltas function processes X/Y/Z as a 3D vector to maintain proportional movement:
// 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
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

Frame-Rate Independence

The filter multiplies output by dt (time since last frame):
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
You don’t need to adjust settings when changing tracker frequency. The filter automatically compensates.

Comparison to Other Filters

FeatureAccelaEWMAHamiltonAlpha Spectrum
ComplexityLowMediumMediumHigh
Parameters4388+
AdaptationVelocityNoiseDistanceMulti-modal
CPU usageLowLowLowMedium
Best forFast actionAuto smoothCinematicAdvanced users
DeadzonesHard subtractNoneSoft rampSoft ramp
Vector processing✅ Yes❌ No❌ No❌ No
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

Troubleshooting

  • 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
  • Decrease sensitivity values
  • This shifts you up the spline curve, increasing multipliers
  • Try decreasing by 0.2-0.3 at a time
  • Increase deadzones slightly
  • Rotation: try 0.05-0.08
  • Translation: try 0.15-0.25
  • Check tracker mounting for vibration
  • Decrease deadzones
  • Minimum useful values:
    • Rotation: 0.01-0.02
    • Translation: 0.05-0.08
  • Below this, sensor noise dominates
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.

Advanced: Custom Spline Curves

Modifying spline curves requires recompiling OpenTrack. This is for advanced users only.
The spline points are defined in accela-settings.hpp:
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):
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):
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

Filter Overview

Compare all available filters

Alpha Spectrum

Advanced adaptive filter

EWMA Filter

Automatic noise-adaptive filtering

Hamilton Filter

Quaternion-based smooth rotations