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

# IFilter Interface

> Complete API reference for implementing filter plugins

## Overview

The `IFilter` interface is the base class for all filter plugins. Filters process raw tracking data to reduce jitter, apply smoothing, implement deadzones, and perform other transformations before the data reaches the output protocol.

## Interface Definition

```cpp theme={null}
struct IFilter : module_status_mixin
{
    IFilter();
    ~IFilter() override;
    
    // Core methods (must implement)
    virtual void filter(const double *input, double *output) = 0;
    
    // Optional methods
    virtual void center() {}
    
    // From module_status_mixin
    virtual module_status initialize() = 0;
    
    // Deleted methods (non-copyable)
    IFilter(const IFilter&) = delete;
    IFilter& operator=(const IFilter&) = delete;
};
```

## Methods

### initialize()

```cpp theme={null}
virtual module_status initialize() = 0;
```

Initializes the filter and validates configuration.

<ResponseField name="return" type="module_status">
  Returns `status_ok()` on success, or `error(message)` on failure.
</ResponseField>

**Description:**

* Called once when the filter is created
* Validate settings and parameters
* Initialize internal state, buffers, or data structures
* Usually just returns `status_ok()`

**Example:**

```cpp theme={null}
module_status MyFilter::initialize()
{
    // Validate configuration
    if (settings.smoothing_factor < 0 || settings.smoothing_factor > 1)
        return error("Smoothing factor must be between 0 and 1");
    
    // Initialize state
    reset_state();
    
    return status_ok();
}
```

### filter()

```cpp theme={null}
virtual void filter(const double *input, double *output) = 0;
```

Processes tracking data and writes filtered result.

<ParamField path="input" type="const double*">
  Input pose array: `[TX, TY, TZ, Yaw, Pitch, Roll]` in cm/degrees
</ParamField>

<ParamField path="output" type="double*">
  Output pose array to fill with filtered data in same format
</ParamField>

<Warning>
  This method is called at 250Hz. Keep it fast and deterministic.
</Warning>

**Description:**

* Called at 250Hz from tracking pipeline thread
* Read from `input` array, write to `output` array
* Apply smoothing, deadzone, or other transformations
* Manage your own timing (dt) if needed
* Don't block or perform heavy computation

**Data format:**

* `input[TX]`: X translation in centimeters
* `input[TY]`: Y translation in centimeters
* `input[TZ]`: Z translation in centimeters
* `input[Yaw]`: Yaw rotation in degrees (-180 to 180)
* `input[Pitch]`: Pitch rotation in degrees (-90 to 90)
* `input[Roll]`: Roll rotation in degrees (-180 to 180)

**Example implementations:**

<CodeGroup>
  ```cpp filter-accela/ftnoir_filter_accela.cpp theme={null}
  void accela::filter(const double* input, double *output)
  {
      static constexpr double full_turn = 360.0;
      static constexpr double half_turn = 180.0;

      // First run initialization
      if (first_run) [[unlikely]]
      {
          first_run = false;
          
          for (int i = 0; i < 6; i++)
          {
              output[i] = input[i];
              last_output[i] = input[i];
          }
          
          t.start();
          return;
      }

      const double rot_thres = s.rot_smoothing;
      const double pos_thres = s.pos_smoothing;
      const double dt = t.elapsed_seconds();
      t.start();

      const double rot_dz = s.rot_deadzone;
      const double pos_dz = s.pos_deadzone;

      // Process rotation (Yaw, Pitch, Roll)
      for (unsigned i = 3; i < 6; i++)
      {
          double d = input[i] - last_output[i];
          
          // Handle wrap-around at 180 degrees
          if (fabs(d) > half_turn)
              d -= copysign(full_turn, d);

          // Apply deadzone
          if (fabs(d) > rot_dz)
              d -= copysign(rot_dz, d);
          else
              d = 0;

          deltas[i] = d / rot_thres;
      }

      // Apply rotation spline curve
      do_deltas(&deltas[Yaw], &output[Yaw], [this](double x) {
          return spline_rot.get_value_no_save(x);
      });

      // Process position (TX, TY, TZ)
      for (unsigned i = 0; i < 3; i++)
      {
          double d = input[i] - last_output[i];
          
          // Apply deadzone
          if (fabs(d) > pos_dz)
              d -= copysign(pos_dz, d);
          else
              d = 0;

          deltas[i] = d / pos_thres;
      }

      // Apply position spline curve
      do_deltas(&deltas[TX], &output[TX], [this](double x) {
          return spline_pos.get_value_no_save(x);
      });

      // Integrate deltas with dt and handle wrap-around
      for (unsigned k = 0; k < 6; k++)
      {
          output[k] *= dt;
          output[k] += last_output[k];
          
          // Handle rotation wrap-around
          if (k >= Yaw && fabs(output[k]) > half_turn)
              output[k] -= copysign(full_turn, output[k]);

          last_output[k] = output[k];
      }
  }
  ```

  ```cpp Example: Simple Low-Pass Filter theme={null}
  void SimpleLowPass::filter(const double* input, double* output)
  {
      const double alpha = settings.smoothing;  // 0-1
      
      for (int i = 0; i < 6; i++)
      {
          // Exponential moving average
          output[i] = alpha * input[i] + (1 - alpha) * prev_output[i];
          prev_output[i] = output[i];
      }
  }
  ```

  ```cpp Example: Deadzone Filter theme={null}
  void DeadzoneFilter::filter(const double* input, double* output)
  {
      for (int i = 0; i < 6; i++)
      {
          double delta = input[i] - last_input[i];
          double threshold = (i < 3) ? pos_deadzone : rot_deadzone;
          
          // Only update if moved beyond deadzone
          if (fabs(delta) > threshold)
          {
              output[i] = input[i];
              last_input[i] = input[i];
          }
          else
          {
              output[i] = last_input[i];  // Hold previous value
          }
      }
  }
  ```
</CodeGroup>

### center()

```cpp theme={null}
virtual void center() {}
```

Called when user requests to center/reset tracking.

**Description:**

* Called from UI thread when center hotkey pressed
* Reset internal filter state if needed
* Clear history buffers
* Default implementation does nothing

**Example:**

```cpp theme={null}
void MyFilter::center()
{
    // Reset filter state
    first_run = true;
    
    // Clear history
    for (int i = 0; i < 6; i++)
    {
        last_output[i] = 0;
        history[i].clear();
    }
}
```

## Dialog Interface

```cpp theme={null}
struct IFilterDialog : public BaseDialog
{
    virtual void register_filter(IFilter* filter) = 0;
    virtual void unregister_filter() = 0;
};
```

### register\_filter()

```cpp theme={null}
virtual void register_filter(IFilter* filter) = 0;
```

Receives a pointer to the active filter instance.

<ParamField path="filter" type="IFilter*">
  Pointer to the running filter instance
</ParamField>

**Description:**

* Called from UI thread when filter starts
* Store pointer for runtime interaction (optional)
* Usually implemented as empty function

**Example:**

```cpp theme={null}
void MyFilterDialog::register_filter(IFilter* filter)
{
    this->filter = filter;
}
```

### unregister\_filter()

```cpp theme={null}
virtual void unregister_filter() = 0;
```

Called when filter is about to be destroyed.

**Example:**

```cpp theme={null}
void MyFilterDialog::unregister_filter()
{
    filter = nullptr;
}
```

## Complete Example

<CodeGroup>
  ```cpp my-filter.h theme={null}
  #pragma once
  #include "api/plugin-api.hpp"
  #include "compat/timer.hpp"
  #include <deque>

  class MyFilter : public IFilter
  {
  public:
      MyFilter();
      
      module_status initialize() override;
      void filter(const double* input, double* output) override;
      void center() override;
      
  private:
      struct Settings {
          double smoothing = 0.5;
          double deadzone = 0.5;
      } settings;
      
      double last_output[6] = {};
      std::deque<double> history[6];
      Timer timer;
      bool first_run = true;
  };

  class MyFilterDialog : public IFilterDialog
  {
      Q_OBJECT
  public:
      MyFilterDialog();
      void register_filter(IFilter* f) override { filter = f; }
      void unregister_filter() override { filter = nullptr; }
      
  private:
      IFilter* filter = nullptr;
  };

  class MyFilterMetadata : public Metadata
  {
      Q_OBJECT
      QString name() override { return tr("My Filter"); }
      QIcon icon() override { return QIcon(":/images/filter.png"); }
  };
  ```

  ```cpp my-filter.cpp theme={null}
  #include "my-filter.h"
  #include <cmath>

  MyFilter::MyFilter()
  {
      // Load settings
      // settings.load();
  }

  module_status MyFilter::initialize()
  {
      // Validate settings
      if (settings.smoothing < 0 || settings.smoothing > 1)
          return error("Invalid smoothing value");
      
      // Initialize history buffers
      for (int i = 0; i < 6; i++)
          history[i].resize(10);  // 10 sample history
      
      return status_ok();
  }

  void MyFilter::filter(const double* input, double* output)
  {
      // First run: pass through
      if (first_run)
      {
          first_run = false;
          for (int i = 0; i < 6; i++)
          {
              output[i] = input[i];
              last_output[i] = input[i];
          }
          timer.start();
          return;
      }
      
      const double dt = timer.elapsed_seconds();
      timer.start();
      
      for (int i = 0; i < 6; i++)
      {
          // Calculate delta
          double delta = input[i] - last_output[i];
          
          // Apply deadzone
          if (std::fabs(delta) < settings.deadzone)
              delta = 0;
          
          // Add to history
          history[i].push_back(input[i]);
          if (history[i].size() > 10)
              history[i].pop_front();
          
          // Calculate moving average
          double sum = 0;
          for (double val : history[i])
              sum += val;
          double avg = sum / history[i].size();
          
          // Blend with input based on smoothing
          output[i] = settings.smoothing * avg + 
                      (1 - settings.smoothing) * input[i];
          
          last_output[i] = output[i];
      }
  }

  void MyFilter::center()
  {
      first_run = true;
      
      for (int i = 0; i < 6; i++)
      {
          last_output[i] = 0;
          history[i].clear();
          history[i].resize(10);
      }
  }

  // Register plugin
  OPENTRACK_DECLARE_FILTER(MyFilter, MyFilterDialog, MyFilterMetadata)
  ```
</CodeGroup>

## Common Filter Patterns

<AccordionGroup>
  <Accordion title="Low-Pass Filter (Smoothing)">
    Reduces high-frequency jitter using exponential moving average.

    ```cpp theme={null}
    void filter(const double* input, double* output)
    {
        const double alpha = 0.3;  // Smoothing factor
        
        for (int i = 0; i < 6; i++)
        {
            // EWMA: output = alpha * input + (1-alpha) * prev
            output[i] = alpha * input[i] + 
                       (1 - alpha) * prev_output[i];
            prev_output[i] = output[i];
        }
    }
    ```
  </Accordion>

  <Accordion title="Deadzone Filter">
    Ignores small movements to reduce micro-jitter.

    ```cpp theme={null}
    void filter(const double* input, double* output)
    {
        const double pos_dz = 0.5;  // cm
        const double rot_dz = 1.0;  // degrees
        
        for (int i = 0; i < 6; i++)
        {
            double delta = input[i] - last_input[i];
            double threshold = (i < 3) ? pos_dz : rot_dz;
            
            if (fabs(delta) > threshold)
            {
                output[i] = input[i];
                last_input[i] = input[i];
            }
            else
            {
                output[i] = last_input[i];
            }
        }
    }
    ```
  </Accordion>

  <Accordion title="Acceleration-Based Filter">
    Applies more smoothing when moving slowly, less when moving fast.

    ```cpp theme={null}
    void filter(const double* input, double* output)
    {
        const double dt = timer.elapsed_seconds();
        timer.start();
        
        for (int i = 0; i < 6; i++)
        {
            // Calculate velocity
            double velocity = (input[i] - last_input[i]) / dt;
            
            // Map velocity to smoothing amount
            double speed = fabs(velocity);
            double smoothing = 1.0 / (1.0 + speed * gain);
            
            // Apply adaptive smoothing
            output[i] = smoothing * last_output[i] + 
                       (1 - smoothing) * input[i];
            
            last_input[i] = input[i];
            last_output[i] = output[i];
        }
    }
    ```
  </Accordion>

  <Accordion title="Kalman Filter">
    Optimal estimator combining measurements with predictions.

    ```cpp theme={null}
    void filter(const double* input, double* output)
    {
        const double dt = timer.elapsed_seconds();
        timer.start();
        
        // Update Kalman filter for each axis
        for (int i = 0; i < 6; i++)
        {
            // Prediction step
            kalman[i].predict(dt);
            
            // Update step with measurement
            kalman[i].update(input[i]);
            
            // Get filtered estimate
            output[i] = kalman[i].get_state();
        }
    }
    ```
  </Accordion>
</AccordionGroup>

## Handling Rotation Wrap-Around

<Warning>
  Rotation angles wrap around at ±180°. Handle this carefully in filters.
</Warning>

**Problem:** Rotation values jump from 179° to -179° when crossing the boundary.

**Solution:** Detect and handle wrap-around when computing deltas.

```cpp theme={null}
void filter(const double* input, double* output)
{
    constexpr double half_turn = 180.0;
    constexpr double full_turn = 360.0;
    
    for (int i = 3; i < 6; i++)  // Yaw, Pitch, Roll
    {
        double delta = input[i] - last_input[i];
        
        // Handle wrap-around
        if (fabs(delta) > half_turn)
            delta -= copysign(full_turn, delta);
        
        // Apply filtering to delta
        double filtered_delta = apply_filter(delta);
        
        // Integrate back
        output[i] = last_output[i] + filtered_delta;
        
        // Normalize output to [-180, 180]
        while (output[i] > half_turn)
            output[i] -= full_turn;
        while (output[i] < -half_turn)
            output[i] += full_turn;
        
        last_input[i] = input[i];
        last_output[i] = output[i];
    }
}
```

## Time Management

Filters often need accurate time measurement for velocity-based algorithms:

```cpp theme={null}
#include "compat/timer.hpp"

class MyFilter : public IFilter
{
    Timer timer;
    
    void filter(const double* input, double* output) override
    {
        // Get time since last call
        const double dt = timer.elapsed_seconds();
        timer.start();
        
        // Use dt for velocity calculations
        double velocity = (input[0] - last_input[0]) / dt;
        
        // ...
    }
};
```

## Performance Tips

<AccordionGroup>
  <Accordion title="Avoid Dynamic Allocations">
    ```cpp theme={null}
    // Bad: allocates every call
    void filter(const double* input, double* output) {
        std::vector<double> temp(6);  // Allocation!
        // ...
    }

    // Good: member variable
    class MyFilter : IFilter {
        double temp[6];  // No allocation
        
        void filter(const double* input, double* output) {
            // Use temp...
        }
    };
    ```
  </Accordion>

  <Accordion title="Use Efficient Math">
    ```cpp theme={null}
    // Prefer built-in operations
    double abs_value = fabs(value);     // Fast
    double sqrt_value = sqrt(value);    // Fast

    // Avoid expensive operations
    double pow_value = pow(value, 2);   // Use value * value instead
    double exp_value = exp(value);      // Expensive
    ```
  </Accordion>

  <Accordion title="Minimize Conditionals">
    ```cpp theme={null}
    // Better branch prediction with likely/unlikely
    if (first_run) [[unlikely]]
    {
        // Initialization code
    }

    // Or restructure to avoid branches
    double factor = (delta > threshold) ? 1.0 : 0.0;
    output = input * factor;
    ```
  </Accordion>
</AccordionGroup>

## See Also

* [Tracker Interface](/api/tracker-interface) - Capture tracking data
* [Protocol Interface](/api/protocol-interface) - Output tracking data
* [Metadata](/api/metadata) - Plugin metadata requirements
