Skip to content

Project template

Directory structure

<component>/
    native/
        inc/
            clib_common.h               # common C99 types
            pyex_common.h               # common Python extension includes
            <component>/
                <component>_core.h      # component API (edit this)
        src/
            <component>/
                <component>_core.c      # core C implementation
                <component>_ext.c       # thin Python binding
        tests/
            test_<component>_core.c     # CTest
    src/
        <component>/
            __init__.py
            <component>.pyi             # type stub
            tests/
                test_<component>.py     # pytest
    CMakeLists.txt
    Makefile
    pyproject.toml
    .gitignore
    README.md

C conventions

State struct

One field is generated for each --state name:type flag.

typedef struct {
    float   gain;
    float   bandwidth;
    int32_t order;
} <component>_state_t;

Constructor

One parameter per --state var, in declaration order.

/**
 * @return Heap-allocated state, or NULL on allocation failure.
 * @note Caller must call <component>_destroy() when done.
 */
<component>_state_t *<component>_create(float gain, float bandwidth, int32_t order);

Destructor

/**
 * @param state  May be NULL.
 */
void <component>_destroy(<component>_state_t *state);

Reset

Restores all state variables to their declared defaults.

void <component>_reset(<component>_state_t *state);

Single-sample processor

Generated as a pass-through stub — implement your DSP here.

/** Inlined for maximum performance. */
static inline float complex
<component>_step(const <component>_state_t *state, float complex x)
{
    (void)state; /* TODO: implement DSP using state variables */
    return x;
}

Block processor

/**
 * @param output  Pre-allocated by caller; may alias input for in-place.
 */
void <component>_steps(
    <component>_state_t *state,
    const float complex  *input,
    float complex        *output,
    size_t                n);

Getter / setter (one pair per --state var)

float   <component>_get_gain(const <component>_state_t *state);
void    <component>_set_gain(<component>_state_t *state, float gain);

float   <component>_get_bandwidth(const <component>_state_t *state);
void    <component>_set_bandwidth(<component>_state_t *state, float bandwidth);

int32_t <component>_get_order(const <component>_state_t *state);
void    <component>_set_order(<component>_state_t *state, int32_t order);

Python binding conventions

The generated <component>_ext.c exposes the C API as a Python class:

from <package> import <Component>

# lifecycle
obj = <Component>(gain=1.0, bandwidth=200.0, order=4)

# single sample
y: complex = obj.step(1.0 + 0.0j)

# block (numpy)
import numpy as np
x = np.ones(256, dtype=np.complex64)
y = obj.steps(x)  # returns complex64 ndarray

# getters / setters
obj.get_gain()
obj.set_gain(2.0)

# reset (restores to declared defaults)
obj.reset()

# context manager (auto-destroys)
with <Component>(1.0) as obj:
    y = obj.step(1.0 + 0.0j)

# explicit release
obj.destroy()

Build system

CMakeLists.txt uses Python3_add_library (CMake ≥ 3.16) to build the extension. The .so is written directly into src/<package>/ so the package is importable from the source tree after a single make.

pyproject.toml declares just-buildit as the PEP 517 backend with command = "make just-build". The just-build Makefile target copies src/<package>/ (Python files + .so) to $JUST_BUILDIT_OUTPUT_DIR for packaging.

make                  →  cmake configure + build
make test             →  ctest + pytest
make just-build       →  build + copy to $JUST_BUILDIT_OUTPUT_DIR
pip install .         →  just-buildit → make just-build → wheel
pip install -e .      →  just-buildit editable (.pth pointing at src/)