KFR C API¶
kfr_capi is KFR's shared-library interface for code that cannot use the C++ API directly. It exposes complex and real DFTs, DCT-II/DCT-III, window functions, biquad, IIR, and FIR design, stateful FIR, FFT-convolution, and IIR filters, and one-shot convolution and zero-phase filtering, through C declarations in <kfr/capi.h>, so it can be used from C and from any language with a C FFI.
The C API library is disabled by default. Build it with -DKFR_ENABLE_CAPI_BUILD=ON; it also requires the DFT module to be enabled.
The surface is intentionally small: fixed-width scalar aliases, opaque plan handles, caller-provided buffers, and global kfr_-prefixed symbols. No C++ containers, templates, exceptions, callbacks, or object layouts cross the ABI boundary.
What the C API provides¶
| Family | Contents |
|---|---|
| Library information | ::kfr_version, ::kfr_version_string, ::kfr_enabled_archs, ::kfr_current_arch, ::kfr_last_error |
| Allocation | ::kfr_allocate, ::kfr_allocate_aligned, ::kfr_deallocate |
| Complex DFT | Reusable 1D, 2D, 3D, and N-dimensional plans in f32 and f64 |
| Real DFT | Real-to-complex and complex-to-real plans, with Perm or CCs packing for 1D |
| DCT | DCT-II plans and their DCT-III inverse |
| One-shot transforms | ::kfr_dft_f32, ::kfr_idft_f32, ::kfr_realdft_f32, ::kfr_irealdft_f32: single-call DFTs without a plan handle |
| Windows | ::kfr_window_f32, ::kfr_window_f64: 17 window types selected by KFR_WINDOW_TYPE |
| Biquad design | ::kfr_biquad_lowpass_f32 and the other kfr_biquad_* designs: one second-order section each |
| IIR design | ::kfr_iir_design_f32: Butterworth, Bessel, Chebyshev I and II, and elliptic prototypes as second-order sections |
| FIR design | ::kfr_fir_design_f32: linear-phase low-pass, high-pass, band-pass, and band-stop taps by the window method |
| Filters | Stateful real FIR, FFT convolution, and IIR (biquad cascade) filters; one-shot ::kfr_convolve_f32 and zero-phase ::kfr_filtfilt_f32 |
| Sample rate conversion | ::kfr_src_create_f32 and ::kfr_src_create_explicit_f32: stateful real-valued rational resampling with streaming, skip, and position helpers |
Every documented entry point has C linkage and a kfr_ name. Other symbols visible in a binary are C++ implementation details and are not part of this ABI.
KFR_FILTER_C32 and KFR_FILTER_C64 are declared, but this release has no creation or processing functions for complex filters; filtering operates on real f32 or f64 buffers.
Header and library¶
#include <kfr/capi.h>
The header is self-contained: it includes the generated <kfr/config.h> and standard C headers only, compiles as C89, C99, or C++, and declares everything extern "C" when included from C++. Do not redeclare imported functions by hand — the header's KFR_API_SPEC macro supplies the import/export annotation and the calling convention (__cdecl on 32-bit x86, the platform default elsewhere).
| Platform | Library used at runtime | Link-time file |
|---|---|---|
| Windows | kfr_capi.dll | kfr_capi.lib import library |
| Linux and other ELF systems | libkfr_capi.so | libkfr_capi.so |
| macOS | libkfr_capi.dylib | libkfr_capi.dylib |
The header and the library must come from the same installed prefix. config.h records the options compiled into that installation, including whether the managed-allocation functions are declared, so mixing a header and a binary from different builds silently changes the declared ABI.
Data types and buffer conventions¶
Real samples are ::kfr_f32 (float) and kfr_f64 (double). These are ABI aliases and are not affected by KFR_BASETYPE_F32.
Complex DFT data uses ::kfr_c32 and kfr_c64. When the C compiler defines __STDC_IEC_559_COMPLEX__, they are C99 float _Complex and double _Complex, and KFR_COMPLEX_SIZE_MULTIPLIER is 1. Otherwise they are the underlying scalar type with interleaved real and imaginary values, and the multiplier is 2. Define KFR_NO_C_COMPLEX_TYPES before including the header to force the interleaved representation:
#define KFR_NO_C_COMPLEX_TYPES 1
#include <kfr/capi.h>
enum { N = 256 };
kfr_f32 spectrum[N * KFR_COMPLEX_SIZE_MULTIPLIER];
/* spectrum[2*i] is real and spectrum[2*i + 1] is imaginary */
Interleaved storage is normally the right choice for a binding, because few language runtimes promise the same layout as C99 _Complex. Pass such a buffer as (kfr_c32*)spectrum.
All sizes in the API are counts of samples, except the *_get_temp_size_* queries, which return bytes.
Plans, transforms, and filter handles¶
Plan pointers such as KFR_DFT_PLAN_F32* and KFR_FILTER_F32* are opaque, library-owned objects; their visible struct fields have no client meaning. Obtain a handle from a kfr_*_create_* function, use it only with the matching family and precision, and destroy it with the matching kfr_*_delete_* function. Creation returns NULL on failure, and delete functions accept NULL.
Filter creation copies its taps or SOS coefficients, so those source arrays may be released afterwards. Input, output, and scratch buffers are caller-owned and must stay valid for the duration of a call; KFR never retains or frees them. Memory management and ownership covers the lifetime, allocator, and thread-safety rules in full.
Transforms¶
Forward and inverse transforms apply no scaling, so a complex round trip produces \(N\) times the original data. *_get_temp_size_* returns the byte capacity that an execute call needs for its temp argument; passing NULL instead lets KFR obtain that storage internally for the call, which a reusable caller buffer avoids in realtime paths.
#define KFR_NO_C_COMPLEX_TYPES 1
#include <kfr/capi.h>
const size_t n = 256;
KFR_DFT_PLAN_F32* plan = kfr_dft_create_plan_f32(n);
kfr_c32* time = kfr_allocate(n * KFR_COMPLEX_SIZE_MULTIPLIER * sizeof(kfr_f32));
kfr_c32* freq = kfr_allocate(n * KFR_COMPLEX_SIZE_MULTIPLIER * sizeof(kfr_f32));
size_t temp_size = kfr_dft_get_temp_size_f32(plan);
uint8_t* temp = temp_size == 0 ? NULL : kfr_allocate(temp_size);
for (size_t i = 0; i < n * KFR_COMPLEX_SIZE_MULTIPLIER; ++i)
time[i] = 0.0f;
time[0] = 1.0f; /* An impulse in the real component of the first sample. */
kfr_dft_execute_f32(plan, freq, time, temp);
kfr_dft_execute_inverse_f32(plan, time, freq, temp);
for (size_t i = 0; i < n * KFR_COMPLEX_SIZE_MULTIPLIER; ++i)
time[i] /= (kfr_f32)n; /* Divide by n for a conventional round trip. */
kfr_deallocate(temp);
kfr_deallocate(freq);
kfr_deallocate(time);
kfr_dft_delete_plan_f32(plan);
The 2D, 3D, and N-dimensional creators take the individual dimensions and operate on the product of them; arrange values in row-major order. ::kfr_dft_real_create_plan_f32 needs an even \(N\) and selects Perm (\(N/2\) complex values) or CCs (\(N/2 + 1\) complex values) output, as described in DFT data layout. ::kfr_dct_create_plan_f32 also requires an even size.
The one-shot functions ::kfr_dft_f32, ::kfr_idft_f32, ::kfr_realdft_f32, and ::kfr_irealdft_f32 run a single transform with no plan handle and no temp argument. They share an internal plan cache keyed by precision and size, so repeated calls at one size do not rebuild the plan. Complex transforms take size complex values. Real transforms take size real samples and size / 2 + 1 complex values in CCs format. Use an explicit plan with a caller-owned temp buffer in realtime paths.
Filters¶
Three filter kinds share one handle type and one processing function:
- ::kfr_filter_create_fir_plan_f32 — direct FIR from taps in natural order \(h[0], h[1], \ldots\). Simplest choice for short filters. Taps from FIR design can be passed directly.
- ::kfr_filter_create_convolution_plan_f32 — same response computed with FFT overlap-add, preferable for a long fixed impulse response.
block_sizemust be a power of two; zero selects 1024. - ::kfr_filter_create_iir_plan_f32 — cascade of second-order sections, each six consecutive scalars
(a0, a1, a2, b0, b1, b2)matching KFR'sbiquad_sectionlayout. Section arrays from biquad design or IIR design can be passed directly.
#include <kfr/capi.h>
#include <stdio.h>
int main(void)
{
const kfr_f32 taps[] = { 0.25f, 0.5f, 0.25f };
const kfr_f32 input[] = { 1, 0, 0, 0, 0, 0, 0, 0 };
kfr_f32 output[sizeof(input) / sizeof(input[0])];
KFR_FILTER_F32* filter =
kfr_filter_create_fir_plan_f32(taps, sizeof(taps) / sizeof(taps[0]));
if (filter == NULL)
{
fprintf(stderr, "KFR: %s\n", kfr_last_error());
return 1;
}
kfr_filter_process_f32(filter, output, input, sizeof(input) / sizeof(input[0]));
kfr_filter_delete_plan_f32(filter);
return 0;
}
A filter handle owns its coefficients and its running state, so processing consecutive blocks through one handle yields the same result as processing their concatenation. ::kfr_filter_reset_f32 clears that state without releasing the handle. output == input is supported for in-place processing, but partial overlap is not.
Every function has an _f64 counterpart. Benchmark a realistic workload before fixing a crossover point between direct FIR and convolution.
One-shot convolution and filtfilt¶
::kfr_convolve_f32 writes the full linear convolution of a and b to out, which must hold a_size + b_size - 1 samples. Nothing is written if either input is empty. ::kfr_filtfilt_f32 applies zero-phase forward-backward filtering in place to data, using sos_count second-order sections in the same six-scalar layout as the IIR plan. Neither function takes a plan handle, so each call is independent of the others.
Sample rate conversion¶
::kfr_src_create_f32 and ::kfr_src_create_f64 build a converter that resamples a real signal by interpolation_factor / decimation_factor. The quality argument (KFR_SRC_QUALITY) selects the filter order and Kaiser window. scale sets the output gain, and cutoff sets the low-pass cutoff as a fraction of the Nyquist frequency. ::kfr_src_create_explicit_f32 and its _f64 counterpart take the tap count and stopband attenuation directly. Their transition_width argument is accepted for parity with the C++ constructor but is currently unused, so the filter length is determined by taps alone.
Only real-valued signals are supported. A converter is stateful: ::kfr_src_process_f32 reads input and writes output in blocks, and consecutive calls produce the same samples as one call over the concatenated input. The input buffer must hold at least ::kfr_src_input_size_for_output_f32 samples for the requested output size. If it does not, the call returns 0 and sets ::kfr_last_error. ::kfr_src_output_size_for_input_f32 gives the reverse count. Both counts depend on the current position, so query them before each call.
::kfr_src_skip_f32 advances the converter over output that is not needed, consuming the matching input without writing it. This is useful for discarding startup transients. ::kfr_src_reset_f32 restores the initial state without releasing the handle.
The position helpers convert between input, output, and intermediate (interpolated) positions. Negative positions round toward negative infinity. ::kfr_src_get_fractional_delay_f32 and ::kfr_src_get_delay_f32 report the filter's group delay as a fraction and as whole samples.
Design helpers¶
Windows¶
::kfr_window_f32 and ::kfr_window_f64 write size samples of the window selected by KFR_WINDOW_TYPE into output. Hamming, Blackman, Gaussian, and Tukey read param as alpha, Kaiser reads it as beta, and Planck-taper reads it as epsilon. Other windows ignore it. Pass a non-zero symmetric for a symmetric window, or zero for a periodic window.
Biquad sections¶
The kfr_biquad_*_f32 and kfr_biquad_*_f64 functions design one second-order section and write its six scalars (a0, a1, a2, b0, b1, b2) to sos. They take no size, so the buffer must hold six scalars. Frequencies are normalized, meaning Hz divided by the sample rate: a 1 kHz cutoff at 48 kHz is passed as 1000.0 / 48000.0.
The all-pass, low-pass, high-pass, band-pass, and notch designs take frequency and Q. The peak design also takes gain_db. The low-shelf and high-shelf designs take frequency and gain_db instead of Q. A single section can be passed to ::kfr_filter_create_iir_plan_f32 with a sos_count of 1.
IIR design¶
::kfr_iir_design_f32 and ::kfr_iir_design_f64 design a digital IIR filter and return it as second-order sections, in the layout accepted by ::kfr_filter_create_iir_plan_f32 and ::kfr_filtfilt_f32. prototype selects Butterworth, Bessel, Chebyshev I, Chebyshev II, or elliptic (KFR_IIR_PROTOTYPE). response selects low-pass, high-pass, band-pass, or band-stop (KFR_IIR_RESPONSE). Frequencies and fs are in Hz. Elliptic prototypes require a build with Boost.Math enabled (KFR_USE_BOOST_MATH).
ordermust be 1 to 24 for Butterworth and Bessel, and at least 1 for the others.rp(passband ripple in dB) applies to Chebyshev I and elliptic.rs(stopband attenuation in dB) applies to Chebyshev II and elliptic. Each must be greater than zero where it applies. Pass 0 otherwise.- Low-pass and high-pass require
0 < frequency < fs / 2. Band-pass and band-stop also requirefrequency < high_frequency < fs / 2.
Pass NULL for sos to get the section count. Then allocate count * 6 scalars and call again with sos_capacity set to the count. Nothing is written when sos is NULL or the capacity is smaller than the count. The return value is 0 on error; read ::kfr_last_error for the message.
#include <kfr/capi.h>
#include <stdio.h>
int main(void)
{
kfr_f32 input[1024];
kfr_f32 output[1024];
kfr_f32* sos;
KFR_FILTER_F32* filter;
size_t sections;
size_t i;
/* Fourth-order Butterworth low-pass, 1 kHz cutoff at 48 kHz. Query the section count first. */
sections = kfr_iir_design_f32(KFR_IIR_BUTTERWORTH, KFR_IIR_LOWPASS, 4, 0, 0, 1000, 0, 48000, NULL, 0);
sos = (kfr_f32*)kfr_allocate(sections * 6 * sizeof(kfr_f32));
kfr_iir_design_f32(KFR_IIR_BUTTERWORTH, KFR_IIR_LOWPASS, 4, 0, 0, 1000, 0, 48000, sos, sections);
filter = kfr_filter_create_iir_plan_f32(sos, sections);
kfr_deallocate(sos);
if (filter == NULL)
{
fprintf(stderr, "KFR: %s\n", kfr_last_error());
return 1;
}
for (i = 0; i < 1024; ++i)
input[i] = 1.0f; /* A constant input settles to the filter's unit DC gain. */
kfr_filter_process_f32(filter, output, input, 1024);
kfr_filter_delete_plan_f32(filter);
return 0;
}
Plan creation copies the coefficients, so the sos buffer can be released as soon as the plan exists.
FIR design¶
::kfr_fir_design_f32 and ::kfr_fir_design_f64 design a linear-phase FIR filter by the window method and write size taps to taps. They are the C counterparts of kfr::fir_lowpass(), kfr::fir_highpass(), kfr::fir_bandpass(), and kfr::fir_bandstop(). response selects low-pass, high-pass, band-pass, or band-stop (KFR_FIR_RESPONSE). type and param select the window as described in Windows. The window is always symmetric, which keeps the taps linear-phase. Frequencies and fs are in Hz.
sizeis the number of taps and must be greater than zero. The filter order issize - 1. With an oddsize, the group delay is(size - 1) / 2samples.- Low-pass and high-pass require
0 < frequency < fs / 2. Band-pass and band-stop also requirefrequency < high_frequency < fs / 2. normalizescales the taps the same way as the C++kfr::fir_*()family. Low-pass and band-stop then have unit DC gain. High-pass and band-pass use the C++ scaling, which leaves their gains only approximately 0 and 1.
The return value is non-zero on success and 0 on error. Validation runs before the design, so taps is left unchanged on error; read ::kfr_last_error for the message. Pass the taps to ::kfr_filter_create_fir_plan_f32 to filter with them.
Next steps¶
- Building a C API package — CMake options, install layout, ABI boundary, and package verification.
- Loading and calling from C — linking, version checks, dynamic loading, and FFI bindings.
- Memory management and ownership — buffers, handle lifetime, allocators, errors, and thread safety.
- C API Reference — every declaration, precision, and dimensionality.