FXLMS and Active Noise Control System Architecture

Active Noise Control (ANC) works by generating an acoustic wave with equal amplitude and opposite phase to the incoming noise, canceling it through destructive interference at the target zone. Translating this into hardware requires two decisions: which sensors to use for noise sensing, and what control strategy to apply for anti-noise generation. These two factors define the three canonical ANC architectures: feedforward, feedback, and hybrid.

Feedforward ANC

A feedforward ANC system places a reference microphone upstream of the noise source to capture the disturbance early, feeds it to the DSP, and drives the speaker to produce the anti-noise. An error microphone near the ear monitors the residual noise and feeds it back to the adaptive algorithm for coefficient update.

Signal chain:

1
2
3
Noise source → Reference mic → Adaptive filter W(z) → Speaker → Ear
                                      ↑                         ↓
                               Error mic ← Residual noise

The complete feedforward ANC signal flow, showing the reference microphone, adaptive filter, secondary path, and error feedback loop:

mermaid
flowchart TD
    SRC["Noise Source"] --> REF["Reference Mic"]
    REF --> W["Adaptive Filter<br/>W(z)"]
    W --> SPK["Speaker"]
    SPK -->|"Secondary Path S(z)"| EAR["Ear"]
    EAR --> EMIC["Error Mic"]
    EMIC -->|"e(n) Feedback"| W

    classDef src fill:#f44336,color:#fff
    classDef mic fill:#2196F3,color:#fff
    classDef proc fill:#9C27B0,color:#fff
    classDef out fill:#4CAF50,color:#fff
    class SRC src
    class REF,EMIC mic
    class W proc
    class SPK,EAR out

Feedforward architecture senses noise before it reaches the ear, making it effective against broadband random noise. Its effectiveness depends on stable acoustic coupling between the reference microphone and the noise source. If the user turns their head or the headphone fit loosens, the secondary path changes and cancellation degrades. This is the feedforward design’s most sensitive point: variations in the secondary path $S(z)$ show up directly in the error signal, and the adaptive filter must track continuously.

In consumer devices, the feedforward mic is typically mounted on the outside of the headphone earcup to capture ambient noise, while the error mic sits inside the earcup near the ear canal.

Feedback ANC

Feedback ANC needs no reference microphone. It relies solely on the error microphone inside the earcup and treats the entire system as a negative feedback loop.

The structural advantage is straightforward: one fewer microphone, lower hardware cost, and simpler mechanical design. The downside is that feedback can only react to noise that has already reached the ear — inherent latency for transient disturbances. Feedback excels at periodic narrowband noise (engine rumble, transformer hum) because the loop locks onto the phase quickly. Stability is the main concern: a poorly designed feedback loop oscillates.

From a control theory perspective, feedback ANC is equivalent to a high-gain negative feedback system, and the phase margin of the open-loop transfer function must be sufficient.

Hybrid ANC

Hybrid ANC combines both architectures: the reference microphone on the earcup surface and the error microphone inside the earcup coexist.

Premium consumer ANC headphones (AirPods Pro, Sony WH-1000XM series) all use hybrid designs. The feedforward channel handles broadband environmental noise (street noise, wind), while the feedback channel targets low-frequency narrowband residual noise inside the earcup. Two independent filters run in parallel and their outputs are summed in the DSP.

The cost is equally clear: two sets of adaptive filters, two sets of secondary path identification runs — computational load and memory consumption nearly double. For embedded platforms, power and real-time constraints need careful evaluation.

Comparison of the three architectures:

mermaid
flowchart TD
    subgraph Feedforward
        F1["Reference Mic"] --> F2["W(z)"] --> F3["Speaker"]
        F4["Error Mic"] --> F2
    end
    subgraph Feedback
        B1["Error Mic"] --> B2["Controller"] --> B3["Speaker"]
        B3 --> B1
    end
    subgraph Hybrid
        H1["Reference Mic"] --> H2["Feedforward W₁(z)"] --> H5["Σ"] --> H6["Speaker"]
        H3["Error Mic"] --> H4["Feedback W₂(z)"] --> H5
        H6 --> H3
    end

Multi-Channel ANC

Stereo headphones inherently need two independent ANC channels, one per earcup. Acoustic crosstalk exists (the left speaker may be picked up by the right error mic), so multi-channel systems must use a matrix filter structure for decoupling. Each channel has its own adaptive filter and error microphone, and each reference signal typically comes from the corresponding reference microphone.

FXLMS Algorithm Detail

Standard LMS assumes the error signal can be used directly for weight update. In an ANC system, there is a secondary path $S(z)$ between the speaker and the error microphone — encompassing the DAC, power amplifier, speaker, acoustic cavity, error microphone, and ADC. The magnitude and phase distortion introduced by this path, if left uncompensated, drives LMS to a suboptimal solution or outright divergence.

FXLMS (Filtered-x LMS) corrects this by incorporating the secondary path effect into the weight update path. Instead of using the reference signal $x(n)$ directly, it first filters $x(n)$ through an estimate of the secondary path $\hat{S}(z)$, producing $x’(n)$, and then uses $x’(n)$ for the weight update.

The formulation is straightforward. Define:

  • $x(n)$: reference signal
  • $y(n) = \mathbf{w}^T(n) \mathbf{x}(n)$: filter output
  • $d(n)$: desired signal (acoustic response of noise at the error microphone)
  • $e(n) = d(n) - s(n) * y(n)$: error signal ($s(n)$ is the secondary path impulse response)

Weight update equation:

$$ \mathbf{w}(n+1) = \mathbf{w}(n) + \mu \cdot e(n) \cdot \mathbf{x}’(n) $$

where $\mathbf{x}’(n) = \hat{s}(n) * \mathbf{x}(n)$ is the filtered reference signal vector.

FXLMS Four-Step Execution Flow

The diagram below illustrates the internal data flow of the FXLMS algorithm, highlighting the filtered-x path:

mermaid
flowchart TD
    X["Reference x(n)"] --> W["Main Filter W(z)<br/>y = w^T · x"]
    W --> OUT["Output to Speaker"]
    X --> SHAT["Secondary Path Est. Ŝ(z)<br/>x' = ŝ * x"]
    SHAT --> XF["Filtered Reference x'(n)"]
    EMIC["Error Signal e(n)"] --> UP["Weight Update<br/>w += μ · e · x'"]
    XF --> UP
    UP --> W

    classDef input fill:#2196F3,color:#fff
    classDef filter fill:#9C27B0,color:#fff
    classDef key fill:#f44336,color:#fff
    classDef output fill:#4CAF50,color:#fff
    class X,EMIC input
    class W,SHAT,UP filter
    class XF key
    class OUT output

Each sampling period, FXLMS executes the following 4 steps:

Step ①: Filtered Reference Signal

$$ x’(n) = \hat{s}(n) * x(n) $$

Why filter again? Standard LMS assumes the error signal’s gradient direction is directly usable, but in ANC systems the error signal $e(n)$ has been distorted by the secondary path $S(z)$. Without compensation, the gradient direction is wrong — the algorithm optimizes in a warped coordinate system. FXLMS pre-filters the reference signal through $\hat{S}(z)$ to “correct” the gradient direction.

Step ②: Filter Output

$$ y(n) = \mathbf{w}^T(n)\mathbf{x}(n) $$

Standard FIR filtering, identical to plain LMS.

Step ③: Error Signal

$$ e(n) = d(n) - s(n) * y(n) $$

Note this uses the real secondary path $s(n)$ (actual physical path), not the estimate $\hat{s}(n)$. $d(n)$ is the noise arriving at the error microphone through the passive path, and $s(n) * y(n)$ is the speaker output arriving at the error microphone through the secondary path.

Step ④: Weight Update

$$ \mathbf{w}(n+1) = \mathbf{w}(n) + \mu \cdot e(n) \cdot \mathbf{x}’(n) $$

Key distinction: this uses the filtered reference signal $x’(n)$, not the raw $x(n)$. If you substitute $x(n)$ for $x’(n)$, FXLMS degenerates into plain LMS and cannot converge in an ANC system.

💡 Numerical example: Suppose the secondary path $S(z)$ introduces 0.5ms of group delay (about 8 samples at 16kHz). Without filtered-x compensation, the weight update gradient direction lags by 0.5ms. For 1kHz noise (period 1ms), this is half a cycle of phase offset — the weight update direction is nearly orthogonal to the correct direction, and the algorithm essentially cannot converge.

Secondary Path Offline Identification

$\hat{S}(z)$ coefficients are typically obtained through offline identification before ANC begins. The procedure: play white noise from the speaker, capture the response with the error microphone, and identify the FIR coefficients using LMS. Once identified, the coefficients are fixed and not updated during runtime.

The convergence curve below shows how error decreases during the LMS identification process — dropping from a large initial error to steady state:

Secondary Path Identification Convergence
1.00.750.500.250.00255075100迭代次数误差初始阶段收敛阶段稳态阶段

Below is a complete FXLMS implementation. Pay attention to xf_buf — it is the key differentiator between FXLMS and plain LMS.

c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#define W_ORDER     64
#define S_ORDER     32
#define MU_W        0.0005f
#define MU_S        0.001f

typedef struct {
    float w[W_ORDER];
    float x_buf[W_ORDER];
    float xf_buf[W_ORDER];      // filtered reference signal buffer
    uint32_t x_ptr;
    float s_hat[S_ORDER];
    float s_buf[S_ORDER];
    uint32_t s_ptr;
    float s_model[S_ORDER];
    float y_buf[S_ORDER];
    uint32_t y_ptr;
    uint8_t mode;
} FXLMS_Controller;

void fxlms_init(FXLMS_Controller *fx) {
    memset(fx, 0, sizeof(FXLMS_Controller));
    fx->mode = 0;
}

// Secondary path offline identification
void fxlms_identify_secondary_path(FXLMS_Controller *fx,
                                    float *white_noise,
                                    float *error_signal,
                                    uint32_t len) {
    for (uint32_t n = 0; n < len; n++) {
        fx->y_buf[fx->y_ptr] = white_noise[n];
        float y_est = 0.0f;
        for (uint32_t i = 0; i < S_ORDER; i++) {
            uint32_t idx = (fx->y_ptr + S_ORDER - i) % S_ORDER;
            y_est += fx->s_model[i] * fx->y_buf[idx];
        }
        float e_s = error_signal[n] - y_est;
        for (uint32_t i = 0; i < S_ORDER; i++) {
            uint32_t idx = (fx->y_ptr + S_ORDER - i) % S_ORDER;
            fx->s_model[i] += MU_S * e_s * fx->y_buf[idx];
        }
        fx->y_ptr = (fx->y_ptr + 1) % S_ORDER;
    }
    memcpy(fx->s_hat, fx->s_model, sizeof(fx->s_model));
    fx->mode = 1;
}

// FXLMS core processing
float fxlms_process(FXLMS_Controller *fx, float x_ref, float e_mic) {
    fx->x_buf[fx->x_ptr] = x_ref;
    float y = 0.0f;
    for (uint32_t i = 0; i < W_ORDER; i++) {
        uint32_t idx = (fx->x_ptr + W_ORDER - i) % W_ORDER;
        y += fx->w[i] * fx->x_buf[idx];
    }
    // Compute filtered reference signal x'(n)
    fx->s_buf[fx->s_ptr] = x_ref;
    float x_filtered = 0.0f;
    for (uint32_t i = 0; i < S_ORDER; i++) {
        uint32_t idx = (fx->s_ptr + S_ORDER - i) % S_ORDER;
        x_filtered += fx->s_hat[i] * fx->s_buf[idx];
    }
    fx->xf_buf[fx->x_ptr] = x_filtered;

    // Weight update uses the filtered reference signal
    for (uint32_t i = 0; i < W_ORDER; i++) {
        uint32_t idx = (fx->x_ptr + W_ORDER - i) % W_ORDER;
        fx->w[i] += MU_W * e_mic * fx->xf_buf[idx];
    }

    fx->x_ptr = (fx->x_ptr + 1) % W_ORDER;
    fx->s_ptr = (fx->s_ptr + 1) % S_ORDER;
    return y;
}

The xf_buf is the soul of FXLMS. Each time fxlms_process is called, it filters the current reference sample x_ref through s_hat to obtain x_filtered, stored in xf_buf. The weight update reads from xf_buf, not from x_buf. This single line:

c
1
fx->w[i] += MU_W * e_mic * fx->xf_buf[idx];

is the only difference between FXLMS and standard LMS — and the key to stable ANC convergence.