Skip to content

Optional Accelerate/vDSP FFT and gain-ramped mixing on macOS - #68

Open
JaumeAP wants to merge 3 commits into
ebu:masterfrom
JaumeAP:feat/vdsp-fft-and-gain-ramp
Open

JaumeAP wants to merge 3 commits into
ebu:masterfrom
JaumeAP:feat/vdsp-fft-and-gain-ramp

Conversation

@JaumeAP

@JaumeAP JaumeAP commented Oct 22, 2026 •

Copy link
Copy Markdown

Adds two optional implementations that use Apple's Accelerate framework, enabled with EAR_USE_VDSP=ON (off by default, so nothing changes for existing users).

  1. get_fft_vdsp<float>(): an FFTImpl built on vDSP_DFT_zrop. It handles n_fft = f * 2^k for f in {1, 3, 5, 15} and falls back to KISS for other even sizes. Output layout and scaling match the KISS implementation, so it replaces it in block_convolver. A forward+inverse pair was 5.7x-8.3x faster than KISS on Apple Silicon.
  2. LinearInterpMatrix::apply_interp / apply_constant use vDSP_vramp, vDSP_vma and vDSP_vsma when EAR_HAS_VDSP is defined, and keep the scalar loop otherwise. About 2.9x faster (12.4 us to 4.3 us, 8 in / 2 out channels, 2048-sample block).

It is off by default because linking Accelerate changed KissFFT's floating-point rounding elsewhere in the same process in our tests, even when the vDSP FFT is never called (an exact zero in variable_block_size became ~3e-8). I have not reproduced that on its own.

Tests: fft_vdsp_tests compares against KISS (sizes 64-4096, including 480, 960 and the 882 fallback); block_convolver_vdsp_tests reruns the block convolver suite with the vDSP FFT; gain_interpolator_matrix_tests compares against an independent scalar reference. All 18 tests pass with vDSP on and 16 with it off, on macOS with AppleClang 21.

Do the tests fail when the code is wrong? Checked on a scratch copy, not committed:

  • Against master without this change, fft_vdsp_tests does not build: use of undeclared identifier 'get_fft_vdsp'.
  • With the vDSP FFT's output scaled by 0.6 instead of 0.5 on the real part, fft_vdsp_tests and block_convolver_vdsp_tests both fail.
  • With the gain ramp slope multiplied by 1.01 in the vDSP path, gain_interpolator_matrix_tests fails (2559 assertions). That test guards equivalence with the scalar loop, so it passes on master; there is no behaviour that fails before the change.

Note that the project's last commit is from April 2024, so I understand if this is out of scope.

🤖 Generated with Claude Code

https://claude.ai/code/session_011HCUWvPpbN7e6hcwuWBJjs

JaumeAP and others added 3 commits October 22, 2026 20:02
Adds get_fft_vdsp<float>(), an FFTImpl using vDSP's DFT API
(vDSP_DFT_zrop), which handles n_fft = f * 2^k for f in {1, 3, 5, 15}
and falls back to KISS for any other even size. Output layout and
scaling match the KISS implementation exactly, so it is a drop-in
replacement for block_convolver. Built when EAR_USE_VDSP is on (default
on Apple), which defines EAR_HAS_VDSP publicly.

Tests: fft_vdsp_tests compares against KISS (sizes 64-4096, including
480, 960 and the 882 fallback); block_convolver_vdsp_tests reruns the
block convolver suite with the vDSP implementation. On Apple Silicon a
forward+inverse pair is 5.7x-8.3x faster than KISS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Toh2Vz45TL5yTdDpYntg6d
Merely linking the Accelerate framework into the ear library
measurably changes KissFFT's floating-point rounding elsewhere in the
same process, even when get_fft_vdsp() is never called: it turned an
exact-zero sample in bear's variable_block_size test into ~3e-8.
Defaulting this ON for every Apple build of libear silently changed
numeric behaviour for consumers who never asked for vDSP, such as
bear's own render path. Consumers that do want the vDSP FFT (such as
ear-production-suite's monitoring plugin) now force EAR_USE_VDSP=ON
explicitly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Toh2Vz45TL5yTdDpYntg6d
The per-block direct/diffuse gain ramp mixing used by
MonitoringAudioProcessor (EPS's loudspeaker monitoring plugin) was a
plain scalar loop. LinearInterpMatrix::apply_interp/apply_constant now
build the linear ramp with vDSP_vramp and accumulate with
vDSP_vma/vDSP_vsma when EAR_HAS_VDSP is defined, keeping the original
scalar loop as the fallback otherwise. Same output layout and exact
values (verified below), just vectorised.

Tests: gain_interpolator_matrix_tests compares against an independent
scalar reference (not the header's own fallback code), for both
apply_interp and apply_constant, with non-power-of-two block/channel
sizes. Passes with EAR_USE_VDSP on and off. Apple Silicon
micro-benchmark (8 in / 2 out channels, 2048-sample block): ~2.9x
faster (12.4us -> 4.3us).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Toh2Vz45TL5yTdDpYntg6d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant