I got bored and built a 2x2 MIMO FxNLMS ANC simulator in Julia + C23

Hey folks,

Today morning was aggressively boring, so naturally I ended up building a 2x2 MIMO active noise control simulator.

I have recently started poking around the border between DSP and control systems, and for some reason I am enjoying it far more than expected. I also do not entirely know why I am posting my random experiments, but maybe someone finds this useful, finds a cursed coefficient, or tells me that I have misunderstood acoustics in a particularly creative way.

The project is a cross-coupled FxNLMS system with:

  • 2 reference microphones
  • 2 anti-noise speakers
  • 2 error microphones
  • A full 2x2 primary acoustic-path matrix
  • A full 2x2 secondary-path matrix
  • Four 64-tap adaptive controller filters
  • Noisy offline secondary-path identification
  • Slowly drifting secondary paths
  • Mixed, stereo WAV, and impulsive-noise scenarios

Julia handles the signal generation, acoustic models, WAV processing, metrics, diagnostics, and plots. I kept a complete Julia implementation as the numerical oracle and moved the sample-processing kernel into C23.

The C backend currently matches Julia to around 1.55e-15, with exactly 0.0 dB ERLE difference across all three scenarios. It is approximately 1.76x faster in the warmed benchmark.

Current final-window results at 8 kHz:

Scenario Combined ERLE
Mixed moving noise 16.25 dB
Stereo WAV 24.06 dB
Impulsive noise 18.10 dB

The impulsive benchmark reaches a sustained 80% of final cancellation in under 25 ms.

I tried to keep the simulation honest:

  • The adaptive controller never sees the true secondary paths.
  • It only receives reference signals, measured residuals, and separately estimated secondary paths.
  • The C processing function performs zero heap allocations.
  • Julia and C run the same deterministic scenarios.
  • The C side is compiled as C23 with warnings treated as errors.
  • The project runs GCC and Clang static analysis plus deterministic unit tests.

The default backend is C:

main()

The Julia oracle is still available:

main(:julia)

Repository:

This is still an offline simulator rather than a real headset or hardware ANC controller. The next genuinely different step would be separating render and adaptation into a real-time API with measured audio latency.
Feedback is very welcome, especially around the MIMO normalization, acoustic scenarios, Julia/C boundary, or anything that looks slightly cursed.
Have a great, day or night or whatever is it at your timezone
Adios
-JB

5 Likes