Skip to content

Project layout & generated API

Project layout (full)

After scaffolding with one object and running just-makeit perf:

my_dsp/
├── CMakeLists.txt
├── Makefile
├── just-makeit.toml
├── pyproject.toml
├── cmake/
│   └── my-dsp.pc.in                    # pkg-config template
├── native/
│   ├── benchmarks/
│   │   └── bench_gain_core.c           # C benchmark
│   ├── inc/
│   │   ├── clib_common.h               # common C99 types
│   │   ├── pyex_common.h               # Python extension includes
│   │   ├── my_dsp.h                    # umbrella header
│   │   ├── jm_perf.h                   # JM_FORCEINLINE / JM_HOT / JM_UNROLL …
│   │   ├── jm_simd.h                   # width-portable SIMD macros
│   │   └── gain/
│   │       └── gain_core.h             # object API  ← implement step() here
│   ├── src/
│   │   └── gain/
│   │       ├── CMakeLists.txt
│   │       ├── gain_core.c             # steps() loop + any multi-sample logic
│   │       └── gain_ext.c              # Python binding  ← do not edit
│   └── tests/
│       └── test_gain_core.c            # CTest lifecycle test
└── src/
    └── my_dsp/
        ├── __init__.py
        ├── gain.pyi                    # type stub
        ├── benchmarks/
        │   ├── __init__.py
        │   └── bench_gain.py           # perf_counter benchmark script
        └── tests/
            ├── __init__.py
            └── test_gain.py            # pytest

Generated C API

Every object follows this lifecycle. Names are derived from the component name you pass to just-makeit object:

/* Constructor — one parameter per --state declaration */
engine_state_t *engine_create(double gain);

/* Destructor */
void engine_destroy(engine_state_t *state);

/* Reset — restores every field to its declared default */
void engine_reset(engine_state_t *state);

/* Single sample — inline stub in _core.h; implement here */
static inline float _Complex
engine_step(const engine_state_t *state, float _Complex x);

/* Block processor — in _core.c, loops over step() */
void engine_steps(
    engine_state_t       *state,
    const float _Complex  *input,
    float _Complex        *output,
    size_t                n);

/* Generator (--arg-type void) — no input parameter */
static inline float
nco_step(const nco_state_t *state);
void nco_steps(nco_state_t *state, float *output, size_t n);

/* Getter and setter for each --state variable */
double engine_get_gain(const engine_state_t *state);
void   engine_set_gain(engine_state_t *state, double val);

Generated Python API

from my_project import Engine   # standalone object
import numpy as np

obj = Engine(gain=1.0)   # keyword arg per --state variable
obj = Engine()           # uses declared defaults

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

x = np.ones(1024, dtype=np.complex64)
y = obj.steps(x)                         # returns new array
obj.steps(x, out=y)                      # zero-copy: fills y, returns y

obj.get_gain()                           # getter
obj.set_gain(2.0)                        # setter
obj.reset()                              # restores declared defaults

with Engine() as e:                      # context manager
    y = e.steps(x)

# Module subpackage — one .so, one subpackage import
from my_filters.filter import Fir, Biquad
fir = Fir(gain=1.0)
bq  = Biquad(b0=1.0)

Types within a module are fully independent — separate lifecycles, each with its own step, steps, reset, getters/setters, and context manager.