Skip to content

KISS FFT Wrapping

Wraps the KISS FFT library using c2py23. Demonstrates complex-number handling via interleaved float32 buffers (c2py23 has no native complex type).

Kissfft Wrap

Interface

# Python dict format equivalent of kissfft.c2py
{
    "module": "kissfftmod",
    "source": [
        "../kissfft/kiss_fft.c",
        "../kissfft/kiss_fftr.c",
        "kissfft_thin.c",
    ],
    "headers": [
        "../kissfft/kiss_fft.h",
        "../kissfft/kiss_fftr.h",
    ],
    "free_threading": True,
    "functions": [
        {
            "py_sig": "rfft_forward(data: buffer, spec: buffer) -> void",
            "checks": [
                "data.format == 'f'",
                "spec.format == 'f'",
                "data.ndim == 1",
                "spec.ndim == 1",
                "spec.n >= data.n + 2",
            ],
            "c_overloads": [
                {
                    "sig": "kissfft_rfft_forward(const float *data, float *spec, int n)",
                    "map": {
                        "data": "data.ptr",
                        "spec": "spec.ptr",
                        "n": "data.n",
                    },
                },
            ],
        },
        {
            "py_sig": "cfft_forward(fin: buffer, fout: buffer) -> void",
            "checks": [
                "fin.format == 'f'",
                "fout.format == 'f'",
                "fin.ndim == 1",
                "fout.ndim == 1",
                "fin.n == fout.n",
                "fin.n % 2 == 0",
            ],
            "c_overloads": [
                {
                    "sig": "kissfft_cfft_forward(const float *fin, float *fout, int n)",
                    "map": {
                        "fin": "fin.ptr",
                        "fout": "fout.ptr",
                        "n": "fin.n / 2",
                    },
                },
            ],
        },
    ],
}

C Source

#include <stdlib.h>
#include "../kissfft/kiss_fft.h"
#include "../kissfft/kiss_fftr.h"

void kissfft_rfft_forward(const float *data, float *spec, int n) {
    kiss_fftr_cfg cfg = kiss_fftr_alloc(n, 0, NULL, NULL);
    if (cfg) {
        kiss_fftr(cfg, data, (kiss_fft_cpx *)spec);
        free(cfg);
    }
}

void kissfft_cfft_forward(const float *fin, float *fout, int n) {
    kiss_fft_cfg cfg = kiss_fft_alloc(n, 0, NULL, NULL);
    if (cfg) {
        kiss_fft(cfg, (const kiss_fft_cpx *)fin, (kiss_fft_cpx *)fout);
        free(cfg);
    }
}

Build

$ c2py23 kissfft.c2py -o kissfftmod_wrapper.c

Compile:

$ cc -shared -fPIC c2py23/runtime/c2py_runtime.c kissfftmod_wrapper.c kissfft_thin.c -I c2py23/runtime -o kissfftmod.so -ldl -lm

See docs/building for cmake, meson, and setuptools options.

Run

$ python example.py
Traceback (most recent call last):
  File "/home/runner/work/c2py23/c2py23/examples/kissfft_wrap/example.py", line 12, in <module>
    import kissfftmod
ModuleNotFoundError: No module named 'kissfftmod'

How It Works

Complex numbers as float pairs

c2py23 has no complex type. The thin wrapper casts float* buffers to kiss_fft_cpx* (a struct of two floats). Python callers create interleaved float arrays -- [re0, im0, re1, im1, ...].

Buffer sizing

The real FFT takes N real floats. Its output is N/2+1 complex bins stored as (N/2+1)*2 = N+2 floats. The check spec.n >= data.n + 2 enforces this.

The complex FFT takes N complex numbers = N*2 floats. The check fin.n % 2 == 0 ensures an even number of floats (complete pairs).

Map expressions

The C function receives n (number of complex elements), but the buffer has N*2 floats. The map n: "fin.n / 2" bridges this.