Skip to content

Commands

just-makeit new <proj> [--object name ...] [--state name:type[:default] ...]

Create a new project. Optionally scaffold a first object in the same step.

just-makeit new my_project
just-makeit new my_project --object engine
just-makeit new my_project --object engine --state rate:double:1.0
just-makeit new my_project --object engine --state rate:double --state order:int:4
just-makeit new my_project --object gain --arg-type float --return-type float --state gain:float:1.0
just-makeit new my_filters --module filter
just-makeit new my_dsp --module osc --module env

new writes a just-makeit.toml that records the project name, version, and any objects — the source of truth for all subsequent commands.

Arguments

Argument Description
project Project name in snake_case. Used as the Python package name and distribution name.
--object name Scaffold a standalone object immediately. Repeatable.
--module name Scaffold an empty extension module immediately. Repeatable; mutually exclusive with --object.
--state name:type[:default] Declare a state variable for the object. Repeatable.
--arg-type TYPE C type for step() input x. Defaults to float _Complex. Use void for generator objects with no scalar input. Append [] for objects whose primary operation takes a whole buffer: --arg-type "float _Complex[]".
--return-type TYPE C type for step() return value. Defaults to --arg-type for scalar inputs, or void for array inputs (T[]). Use void explicitly for sink objects that consume input but produce no scalar output.
--basic Generate a plain Makefile instead of a CMake project. Useful for quick prototypes that don't need a full build system.
--perf Generate jm_perf.h with JM_HOT, JM_LIKELY, and JM_FORCEINLINE macros and apply them to step(). See Performance annotations.

just-makeit module <name>

Scaffold a new Python extension module — a subpackage .so that groups multiple types added via just-makeit object. Must be run from the project root.

just-makeit module filter
just-makeit module osc

Creates:

File Purpose
native/inc/<name>/<name>_core.h Public C API — declare module-level functions here
native/src/<name>/<name>_core.c Implementation — write module-level functions here
native/src/<name>/<name>_ext.c Python binding (auto-generated)
native/src/<name>/CMakeLists.txt Python module target
src/<pkg>/<name>/__init__.py Subpackage init (empty exports)

Appends add_subdirectory(native/src/<name>) to the root CMakeLists.txt and records [module.<name>] with an empty objects list in just-makeit.toml.

Types are added with just-makeit object. Module-level functions are added with just-makeit function.


just-makeit object <name> [--module <name>] [--state name:type[:default] ...] [--arg-type TYPE] [--return-type TYPE]

Add a stateful Python type to the project. Use this when the algorithm needs persistent state (history, coefficients, a cursor, a running accumulator). For stateless operations, use just-makeit function instead. Must be run from the project root.

Without --module — standalone object (own .so):

just-makeit object engine --state rate:double:1.0
just-makeit object ema --arg-type float --return-type float --state alpha:double:0.1 --state prev:float:0.0

Creates the full standalone set of files, updates the top-level CMakeLists.txt, and splices the import + __all__ entry into src/<pkg>/__init__.py.

With --module — grouped into a module subpackage .so:

just-makeit object fir --module filter --state "coeffs:float[16]" --state "delay:float _Complex[16]" --state "gain:float:1.0"
just-makeit object biquad --module filter --state "b0:double:1.0" --state "a1:double:0.0" --state "w1:double:0.0"

Per-object files created (same for both modes):

File Purpose
native/inc/<obj>/<obj>_core.h Header: struct, inline _step, getters/setters
native/src/<obj>/<obj>_core.c Source: create/destroy/reset/steps
native/src/<obj>/CMakeLists.txt OBJECT library + C test + bench
native/tests/test_<obj>_core.c C test with CHECK macro counter
native/benchmarks/bench_<obj>_core.c C benchmark

Additional files for standalone objects (no --module):

File Purpose
native/src/<obj>/<obj>_ext.c Python C extension (own .so)
src/<pkg>/<obj>.pyi Type stub
src/<pkg>/tests/test_<obj>.py pytest suite

Module files regenerated after each just-makeit object --module:

File What changes
native/src/<module>/<module>_ext.c New type block added; PyMODINIT_FUNC updated
native/src/<module>/CMakeLists.txt New <obj>_core added to link list
src/<pkg>/<module>/__init__.py New type added to import and __all__

The module _ext.c is always fully regenerated from the complete object list — never patched — so adding a third type never disturbs the first two.

Arguments

Argument Description
name Object name in snake_case. Becomes the C prefix and Python class name (title-cased).
--module name Target module. Without this flag the object is standalone (own .so).
--state name:type[:default] Declare a state variable. Repeatable.
--arg-type TYPE C type for step() input. Defaults to float _Complex. Use void for generator objects with no input. Append [] for objects whose primary operation takes a whole buffer: --arg-type "float _Complex[]"steps() is not generated.
--return-type TYPE C type for step() return value. Defaults to --arg-type for scalar inputs, or void for array inputs (T[]). Use void explicitly for sink objects that consume input but produce no output.
--perf Generate jm_perf.h and apply JM_FORCEINLINE JM_HOT to step().
--mutable Remove const from the state pointer in step(). Use for objects whose step() must mutate state directly (e.g. an NCO that advances phase, a counter). The default const is correct for pure-computation objects; --mutable opts out.
--no-state Suppress auto-generated state variables, constructor args, and getter/setter scaffolding. Emits <<IMPLEMENT>> stubs in the C struct body and lifecycle functions (create, destroy, reset). Mutually exclusive with --state. Use when the constructor signature is too domain-specific to express via --state (e.g. a filter that takes const float *taps, size_t num_taps).
--no-step Suppress step() and steps() from all C and Python output. Lifecycle functions (create, destroy, reset) are still generated. Use for objects whose interface consists entirely of named methods added with jm method.
--init-param name:type[:default] Declare a constructor parameter for --no-state objects. Repeatable. Generates a typed constructor argument in both C and Python; not stored as a struct field. Requires --no-state.
--impl file::funcname Lift the step() body from funcname in file instead of emitting a blank <<IMPLEMENT>> stub. The function body is extracted verbatim and substitutions from --replace are applied before insertion.
--replace old::new String substitution applied to the body lifted by --impl. Repeatable. Use to rename identifiers from the source file to match the generated struct/param names.

See State Variable Types for supported types, defaults, and C/Python mappings.

Each --state name:type[:default] generates:

  • A field in the C state struct
  • A constructor parameter with the declared default
  • get_name() and set_name() methods in both C and Python
  • Getter/setter tests in both CTest and pytest
  • Reset behaviour that restores the declared default

Naming rules

  • Lowercase letters, digits, and underscores only.
  • Must not start with a digit.
  • Examples: engine, parser, rate_limiter

The Python class name is derived automatically:

Object name Python class
engine Engine
rate_limiter RateLimiter
half_band_filter HalfBandFilter

just-makeit method <object> <method_name> [--module name] [--param name:type ...] --return-type TYPE [--variable-output] [--arg-type TYPE] [--multi-output TYPE ...]

Add a named execute method to an existing object. Must be run from the project root.

Each method appends a C stub to <obj>_core.c and regenerates the module _ext.c with the new Python glue.

Arguments

Argument Description
object Object name (must already exist in just-makeit.toml).
method_name Snake-case name for the new method.
--module name Module the object belongs to (required for module objects).
--param name:type Named typed scalar parameter. Repeatable.
--param name:type[] Named numpy array parameter. Repeatable. Generates const elem_t *name, size_t name_len in C.
--return-type TYPE C type of the return value (void for no return).
--arg-type TYPE C type of a single array-style input. Use void for count-only inputs.
--variable-output Pre-allocate output buffer at init; return zero-copy numpy view. See below.
--multi-output TYPE Add a second (or further) output array. Repeatable; produces a tuple return.
--out-type TYPE Allocate a complex64 (or other) output array per call and pass *out to C. The C stub receives (... , elem_t *out) and the Python wrapper allocates and returns the ndarray automatically. The output length equals in_len / out_divisor.
--out-divisor N Divide the input length by N to determine the output array length when --out-type is active (default: 1). Use 2 for methods that interpret the input as interleaved I/Q pairs (e.g. a CI8 buffer where each complex sample is 2 bytes).
--impl file::funcname Lift the method body from funcname in file instead of emitting a blank <<IMPLEMENT>> stub.
--replace old::new String substitution applied to the body lifted by --impl. Repeatable.

Named parameters (--param)

Use --param name:type (repeatable) when the method takes multiple distinct typed scalar inputs. This generates named parameters in both the C stub and the Python wrapper.

just-makeit method nco configure --module dsp \
    --param freq:float \
    --param phase:float \
    --param mode:int32_t \
    --return-type void

Generated C stub appended to nco_core.c:

void
nco_configure(nco_state_t *state, float freq, float phase, int32_t mode)
{
    (void)state; (void)freq; (void)phase; (void)mode;
}

Generated Python wrapper in nco_ext.c:

float freq = 0.0f;
float phase = 0.0f;
long mode_raw = 0L;
if (!PyArg_ParseTuple(args, "ffl", &freq, &phase, &mode_raw))
    return NULL;
int32_t mode = (int32_t)mode_raw;
nco_configure(self->handle, freq, phase, mode);
Py_RETURN_NONE;

Python call:

nco.configure(0.1, 0.0, 2)

All scalar types in _CTYPE_META are supported as --param types (float, double, int, int32_t, uint32_t, size_t, float _Complex, etc.).

Array parameters (--param name:type[]) generate a numpy array input. The C stub receives (const elem_t *name, size_t name_len) and the Python wrapper uses PyArray_FROM_OTF to convert the incoming numpy array:

just-makeit method resamp execute_ctrl --module resample \
    --param ctrl:"float _Complex[]" \
    --return-type size_t

Generated C stub:

size_t
resamp_execute_ctrl(resamp_state_t *state,
                    const float complex *ctrl, size_t ctrl_len)
{
    (void)state; (void)ctrl; (void)ctrl_len;
    return (size_t)0;
}

Python call: resamp.execute_ctrl(np.zeros(64, dtype=np.complex64))

--param and --arg-type are mutually exclusive per method.


Output modes

method supports two output modes. Choosing the right one depends entirely on whether the maximum output count is knowable at init time.


Default — per-sample scalar or 1:1-rate array

Without --variable-output, the generated C stub processes one sample per call. For array (batch) processing at a 1:1 rate (output length = input length, unknown at init time), the same generated stub is the correct starting point: implement the C function to accept a pointer and length, then write the Python glue manually following the steps() pattern — call PyArray_SimpleNew per call, pass the data pointer to C.

just-makeit method nco steps_u32 --module source \
    --arg-type void --return-type uint32_t

just-makeit method nco steps_ctrl --module source \
    --arg-type float --return-type float

Generated C stub appended to nco_core.c — start here, then expand to a batch signature in place:

uint32_t nco_steps_u32(nco_state_t *state);
float    nco_steps_ctrl(nco_state_t *state, float x);

Expand to the batch signature in nco_core.c:

void nco_steps_u32(nco_state_t *state, size_t n, uint32_t *output);
void nco_steps_ctrl(nco_state_t *state, const float *ctrl,
                    size_t n, uint32_t *output);

Python glue in <module>_ext.c (following the generated Nco_steps pattern):

static PyObject *
Nco_steps_u32(NcoObject *self, PyObject *args)
{
    Py_ssize_t n = 1;
    if (!PyArg_ParseTuple(args, "n", &n)) return NULL;
    npy_intp dims[] = {n};
    PyObject *out = PyArray_SimpleNew(1, dims, NPY_UINT32);
    if (!out) return NULL;
    nco_steps_u32(self->handle, (size_t)n,
                  (uint32_t *)PyArray_DATA((PyArrayObject *)out));
    return out;
}

Python call:

ph  = nco.steps_u32(1024)    # returns uint32 ndarray
out = nco.steps_ctrl(ctrl)   # ctrl is float32 ndarray; returns uint32 ndarray

Use the per-sample scalar form when you only need one value at a time. Use the batch form (handwritten Python glue) for all 1:1-rate array methods.


--variable-output — pre-allocated zero-copy batch

With --variable-output, the generated code calls <method>_max_out(state) at init time, pre-allocates a fixed output buffer, and returns a zero-copy numpy view into that buffer on every call — no per-call malloc.

just-makeit method hbdecim execute --module resample \
    --arg-type "float _Complex" --return-type "float _Complex" \
    --variable-output

just-makeit method hbdecim execute_ovf --module resample \
    --arg-type "float _Complex" --return-type "float _Complex" \
    --variable-output --multi-output uint8_t

Generated C stubs appended to hbdecim_core.c:

/* Return maximum output samples possible given current state. */
size_t hbdecim_execute_max_out(hbdecim_state_t *state);

/* Process n_in input samples; write up to _max_out results; return actual count. */
size_t hbdecim_execute(hbdecim_state_t *state,
                       const float complex *in, size_t n_in,
                       float complex *out);

Python call:

out = decim.execute(block)   # zero-copy view; valid until next call

When to use --variable-output

This is the right choice when the maximum output count is bounded by the object's state and knowable at init time:

Use case _max_out returns Appropriate?
Decimator, fixed ratio R, block size B ceil(B / R) Yes
Buffer / FIFO with fixed capacity C C Yes
FIR filter, 1:1 rate unknown at init No — use per-call alloc
NCO extended outputs, 1:1 rate unknown at init No — use per-call alloc
Overflow/carry detector, 1:1 rate unknown at init No — use per-call alloc

Warning: if _max_out returns 0 (the placeholder default), malloc(0) behaviour is implementation-defined. Always implement _max_out to return a positive bound before calling the method from Python.


--multi-output

Each --multi-output TYPE adds a parallel output array. Works with both modes, but note: in scalar mode the extra outputs are currently not generated — use --variable-output whenever you need multiple output arrays.

# uint32 phase + uint8 carry flag, batch, bounded output
just-makeit method nco steps_u32_ovf --module resample \
    --arg-type void --return-type uint32_t \
    --variable-output --multi-output uint8_t

just-makeit function <name> --module <mod> [--param name:type ...] [--return-type TYPE] [--doc "text"]

Add a stateless C function to an existing module. Use this for algorithms that need no persistent state — no struct, no create/destroy, no lifecycle. Must be run from the project root.

Appends a C stub to native/src/<module>/<module>_core.c (never regenerated — your implementation is safe) and injects the declaration into native/inc/<module>/<module>_core.h. Then regenerates <module>_ext.c to add a _bind_<name> Python wrapper and wire it into the PyMethodDef array.

Arguments

Argument Description
name Snake-case function name.
--module mod Module the function belongs to (required).
--param name:type Named typed scalar parameter. Repeatable.
--param name:type[] Named numpy array parameter. Repeatable. Generates const elem_t *name, size_t name_len in C.
--return-type TYPE C return type (default: void).
--doc "text" Python docstring for the function.
--impl file::funcname Lift the function body from funcname in file instead of emitting a blank <<IMPLEMENT>> stub.
--replace old::new String substitution applied to the body lifted by --impl. Repeatable.

Without --param — generates a void stub in _core.c and a minimal Python wrapper in _ext.c:

just-makeit function fft_global_setup --module fft --doc "Initialize FFT tables."

fft_core.c (yours to implement):

/* <<IMPLEMENT: fft_global_setup>> */
void
fft_global_setup(void)
{
}

fft_core.h (declaration injected automatically):

void fft_global_setup(void);

fft_ext.c (auto-generated Python wrapper):

static PyObject *
_bind_fft_global_setup(PyObject *self, PyObject *Py_UNUSED(args))
{
    (void)self;
    (void)args;
    fft_global_setup();
    Py_RETURN_NONE;
}

With --param — generates a typed stub in _core.c (with suppress lines and a placeholder return) and a full parse block in the _ext.c wrapper:

just-makeit function compute_window \
    --module fft \
    --param n:size_t \
    --param beta:float \
    --return-type float

fft_core.c:

/* <<IMPLEMENT: compute_window>> */
float
compute_window(size_t n, float beta)
{
    (void)n; (void)beta;
    return (float)0.0f; /* placeholder */
}

fft_core.h:

float compute_window(size_t n, float beta);

fft_ext.c (auto-generated):

static PyObject *
_bind_compute_window(PyObject *self, PyObject *args)
{
    (void)self;
    unsigned long long n_raw = 0ULL;
    float beta = 0.0f;
    if (!PyArg_ParseTuple(args, "Kf", &n_raw, &beta))
        return NULL;
    size_t n = (size_t)n_raw;
    return PyFloat_FromDouble((double)compute_window(n, beta));
}

Python call:

from my_pkg import fft
w = fft.compute_window(512, 5.0)

Implement compute_window in fft_core.c and delete the (void) suppression lines.

Array parameters work identically to jm method: append [] to the type. The C stub receives (const elem_t *name, size_t name_len); the Python wrapper performs PyArray_FROM_OTF + Py_DECREF automatically.

just-makeit function apply_window \
    --module fft \
    --param data:"float _Complex[]" \
    --return-type void

just-makeit property <object> <prop_name> [--module name] --type TYPE [--writable] [--field]

Add a read-only (or read-write) Python property to an existing object. Must be run from the project root.

just-makeit property nco phase --module source --type uint32_t
just-makeit property nco phase_inc --module source --type uint32_t
just-makeit property buffer capacity --type size_t --writable
just-makeit property reader samples_read --module conv --type uint32_t --field

Generates a get_<prop>() C function stub (and set_<prop>() if --writable) that you implement in <obj>_core.c, plus the Python getter (and setter) glue in the module _ext.c.

Arguments

Argument Description
object Object name (must already exist in just-makeit.toml).
prop_name Snake-case property name.
--module name Module the object belongs to (required for module objects).
--type TYPE C type of the property value.
--writable Also generate a setter. Without this flag the property is read-only.
--field Add a TYPE prop_name; field to the state struct and auto-implement the getter as return state->prop_name. No <<IMPLEMENT>> stub is generated — the field is the implementation. Combine with --writable for a read-write struct field property.

just-makeit add --state name:type[:default] [...] [--object name]

Add one or more state variables to an existing standalone object. Must be run from the project root.

just-makeit add --state order:int:4
just-makeit add --state threshold:double:0.5 --state window:int:64
just-makeit add --object parser --state depth:int:8
just-makeit add --param n_taps:int:16

When the project has a single standalone object --object may be omitted.

add accepts --state and --param (repeatable, mixable in one call) and regenerates the six state-sensitive files from the merged state list:

  • native/inc/<obj>/<obj>_core.h
  • native/src/<obj>/<obj>_core.c
  • native/src/<obj>/<obj>_ext.c
  • native/tests/test_<obj>_core.c
  • src/<project>/<obj>.pyi
  • src/<project>/tests/test_<obj>.py

All six files are backed up before regeneration. If any write fails, they are restored and just-makeit.toml is left unchanged.

Constraints

  • Each new variable name must be unique within the object's state list.
  • Requires a just-makeit.toml — run just-makeit new first.

just-makeit script

Print a shell script to stdout that fully reconstructs the current project from scratch via CLI commands. Must be run from the project root.

just-makeit script              # print to stdout
just-makeit script > rebuild.sh # save to file

Reads just-makeit.toml and emits one command per scaffold step in the correct order: newmoduleobjectmethodpropertyfunction. The output is a valid shell script that, when run from the parent directory, produces an identical just-makeit.toml.

Note: --impl / --replace are not stored in just-makeit.toml (the lifted body is patched directly into the generated files), so they are not reproduced. Implemented function and step bodies are preserved in your C source files and are unaffected.


just-makeit perf

Upgrade an existing project to use performance annotations without overwriting any user code. Must be run from the project root.

just-makeit perf

Writes native/inc/jm_perf.h, adds #include "jm_perf.h" to each object header, and replaces static inline with JM_FORCEINLINE JM_HOT on step(). Records perf = true in just-makeit.toml so future object/add commands inherit it. Safe to run on a project with a filled-in step(). Idempotent.

See Performance annotations for the full macro reference and JM_DEFINE_STEPS documentation.


just-makeit config [key value]

Show or edit the project configuration stored in just-makeit.toml. Must be run from the project root.

just-makeit config                 # print current config
just-makeit config version 0.2.0  # update version

Example output

project:  my_project
version:  0.1.0

engine:
  rate:  double = 1.0
  order: int    = 4

parser:
  depth:  int = 8
  strict: int = 1

Supported keys

Key Description
version Project version string stored in just-makeit.toml.

just-makeit build [dir]

Configure the CMake project (if not already done), build the C extensions, and package a wheel via just-buildit.

just-makeit build           # wheel → dist/
just-makeit build wheels/   # wheel → wheels/

Must be run from a project directory containing pyproject.toml.


just-makeit test

Build (if needed), then run CTest and pytest.

just-makeit test
  • CTest runs the C tests in each object's tests/ directory.
  • pytest runs the Python tests in src/.

just-makeit dry-run

Show what would be compiled and packaged without running any build steps.

just-makeit dry-run

Output includes the list of C source files and the full cmake configure command that just-makeit build would invoke.