Types and patterns¶
This page has two halves. The first is a tight quick reference —
arrays, pointers (including void), opaque state fields, the full
supported-types table, and the common patterns they combine into.
The second is the per-slot detail, useful when a flag rejects a type
and you want to know exactly which slot's allowlist drove the
rejection.
If a type isn't listed under a slot below, that flag will reject it. This page is the single source of truth.
Quick reference¶
Arrays — two shapes for two jobs¶
| Shape | Lives | Crosses Python? | Declared as |
|---|---|---|---|
T[] |
A step / function / method param | yes — becomes a numpy ndarray | --arg-type "T[]", --param "name:T[]", --out-param "name:T[]" |
T[N] |
Inside a state struct | no — C-only, embedded directly | --state "name:T[N]" |
T[] is the variable-length form parsed from a numpy array and
passed to C as const T * (or T * for an --out-param).
T[N] is a fixed-length C array embedded in the state struct — one
allocation for the whole object, no pointer chasing, no separate
free. It gets array accessors: <pkg>_<comp>_get_<name>(state, dest)
(copy out), <pkg>_<comp>_get_<name>_view(state) (read-only pointer)
and <pkg>_<comp>_set_<name>(state, src). In Python these are
get_<name>() (an ndarray copy), get_<name>_view() (a read-only
view) and set_<name>(ndarray). In C you can also index
state->name[i] directly.
Which element types T may be depends on the slot, and the
Supported types table has a column for each: a step's
T[] takes any type marked IO, an array parameter's T[] any marked
A, and a state T[N] any marked N.
Pointers — three forms in generated code¶
| Pointer | Where it appears |
|---|---|
const T * |
Default for an array parameter — the function promises not to write. |
T * |
Output array parameter (--out-param) — const is dropped so the function can write into the buffer. |
void * |
Inside opaque state, or as a vendor handle whose definition stays in C. Rarely appears on a Python surface. |
const semantics are load-bearing. A T[] declared without
--out-param is const T * in C, and the binding will not let you
write through the buffer from Python. Use --out-param for writable
output buffers.
Opaque state fields¶
Some types don't fit the scalar registry — heap pointers to your own
structs, FILE *, third-party plans (fftwf_plan,
pocketfft_plan_t), or any C type with no canonical Python
representation. Declare these as opaque: the renderer emits the
type verbatim and generates no auto-machinery for the field. You own
the lifecycle — you write the create() / destroy() /
optionally reset() bodies directly in _core.c.
When a state field is opaque, the renderer:
- Emits the type into the struct verbatim.
- Generates no auto-getter, setter, ctor kwarg, or reset assignment.
- Leaves the field zeroed by
create()'scallocand untouched bydestroy()/reset()— the acquire / release code is yours, ascreate_impl/destroy_implin the manifest or by hand in_core.c.
This is the escape hatch for types just-makeit can't introspect. You take over; the renderer steps aside.
CLI parity gap. A
--state name:type:opaqueflag is pending. Today, marking a state field as opaque requires one line injust-makeit.toml(opaque = trueon the entry). Because_core.cis sacred —jm applynever overwrites it — your hand-written lifecycle bodies survive later edits, exceptjm add, which rebuilds the component from the manifest (keep the bodies increate_impl/destroy_impl, orgit stashfirst). See the vendor plan pattern below for the full recipe.
Supported types¶
Every type registered in _CTYPE_META, plus the array shapes and
opaque. Slot legality:
- S — state field (
--state name:T) - N — element of a fixed state array (
--state name:T[N]) - IO — step input / output (
--arg-type,--return-type), as a scalar and as the element of a stepT[] - I — init param (
--init-param) - P — function / method param (
--param) - A — element of an array parameter or result:
T[]in--param,--out-paramand--init-param, and--out-type
| C type | NumPy dtype | S | N | IO | I | P | A | Zero literal |
|---|---|---|---|---|---|---|---|---|
float |
np.float32 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0.0f |
double |
np.float64 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0.0 |
int |
np.int32 |
✓ | ✓ | ✓ | ✓ | ✓ | 0 |
|
bool |
np.bool_ |
✓ | ✓ | ✓ | ✓ | 0 |
||
int8_t |
np.int8 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0 |
int16_t |
np.int16 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0 |
int32_t |
np.int32 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0 |
int64_t |
np.int64 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0 |
uint8_t |
np.uint8 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0U |
uint16_t |
np.uint16 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0U |
uint32_t |
np.uint32 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0U |
uint64_t |
np.uint64 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0U |
size_t |
np.uintp |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0 |
ptrdiff_t |
np.intp |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0 |
float _Complex |
np.complex64 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0.0f + 0.0f * I |
double _Complex |
np.complex128 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0.0 + 0.0 * I |
long double _Complex |
np.clongdouble |
✓ | ✓ | ✓ | ✓ | ✓ | 0.0L + 0.0L * I |
|
const char * |
str |
✓ | ✓ | NULL |
||||
void |
(none) | ✓ | — (--arg-type void) |
|||||
T[N] (fixed array) |
(none — C-only) | ✓ | {0} |
|||||
T[] (variable array) |
(T's dtype) | ✓ | ✓ | ✓ | numpy-owned | |||
| opaque (declared) | (none) | ✓ | user-managed |
Notes:
-
The three array slots take three element sets. A step
T[]takes every step type, sobool[],int[]andlong double _Complex[]arrive asnp.bool_,np.int32andnp.clongdoublearrays. An array parameter (and--out-type) takes only the A column: spell those threeuint8_t[],int32_t[]anddouble _Complex[]there. A stateT[N]takes the A column plusintandlong double _Complex, whose width the struct fixes;bool[N]is refused, so useuint8_t[N]. -
const char *can't be a state field (no lifetime story) or a step input / output (no per-sample semantics). It is legal as an init-param and as a method / module-function--param, where Python passes astr. If you need a string in state, declare an opaque field and copy / strdup it in your_core.ccreate()body. -
An integer the C type cannot hold is refused, not wrapped (gh-2144). Passing
300to anint8_traisesOverflowError: gain: 300 is out of range for int8_t [-128, 127], on every face that takes a scalar: a constructor, a setter, a method or function param,step()and its controllable overrides, a composer row, a handle or capsule module. numpy refusesnp.int8(300)the same way.uint64_tandsize_tincluded:-1raisesOverflowErrorrather than arriving as2**64 - 1(gh-2220). Every integer face takes any object with__index__, a numpy integer such asnp.uint64(5)included, and refuses afloatwithTypeError. A generated C command line -- ajm appexecutable, a composer's CLI -- refuses a flag the same way,-1for auint64_tincluded: it prints<prog>: --gain: '300' is out of range for int8_t [-128, 127]and exits 2 (gh-2225).--state,jm addand a manifest[[<obj>.state]]entry all refuse it, as a scalar and as aT[N]element, and say so. So do--arg-type/--return-typeand a manifestarg_type/return_type, as a scalar and as aT[]element (see Scalar shapes). -
A
long double _Complexscalar is truncated todouble _Complexat the Python boundary. Its arrays are not: a stepT[]and a stateT[N]of it cross asnp.clongdouble. -
There is no complex-integer type, and integer IQ is a structured array. C's
_Complexis float-only and numpy has no complex-integer dtype to map one onto, soint16_t _Complexhas nothing to convert through; the CLI andjm applyboth refuse it by name.Integer IQ is returned as a two-field record —
record_dtypeoverint16_t i, q, giving[('i','<i2'),('q','<i2')]: 1-D, one element per sample, byte order stated. Decided on gh-1310 after measuring all four zero-copy spellings of the same bytes. Two were rejected for reasons worth keeping:- 1-D
int16, length 2n (interleaved) — no longer one element per sample, so every length and index is in half-samples. - 1-D
int32, packed I/Q — tempting (builtin enum, 1-D, one element per sample) and silently wrong:d + 1yields[1, 1, 3, 3, 5, 5, 7, 7], incrementing I only, because int32 addition carries across the I/Q boundary.arr * 2orarr - dccorrupts with no error. Which half holds I is also endianness-dependent.
The structured form refuses arithmetic loudly instead —
ufunc 'add' did not contain a loop— which is the trade jm exists to make. Giving integer IQ the operationscomplex64gets is tracked separately as gh-1314. - 1-D
-
voidis special — only legal as--arg-typeor--return-type, where it strips that side of the step signature (generator, consumer), and never as an array element. -
Array input (
T[]as--arg-type,--param,--out-param) works. Array return (--return-type "T[]") is supported via--preset blockwise(array-in / array-out; see blockwise).
Patterns¶
Four common combinations of the above. Each is a worked recipe you can paste verbatim.
Fixed coefficient table inside state¶
coeffs[64] lives in the struct; one alloc; populate inside
<pkg>_my_fir_create() in _core.c, or from Python with the generated
set_coeffs(ndarray). Access in C as state->coeffs[i].
Vendor plan in opaque state¶
jm object my_fft \
--arg-type "float _Complex[]" \
--init-param n:size_t
# then declare the opaque field — CLI flag pending:
# --state plan:fftwf_plan:opaque
# Until that lands, set `opaque = true` on the [[state]] entry once.
Renderer treats plan as a black box. You call fftwf_plan_dft_1d()
inside <pkg>_my_fft_create() and fftwf_destroy_plan() inside
<pkg>_my_fft_destroy(), both in _core.c. The state struct carries
fftwf_plan plan; verbatim.
File / socket reader¶
jm object iq_reader --no-step \
--init-param filepath:"const char *" \
--state fd:int:-1 \
--state file_size:size_t:0
filepath is the user-facing ctor arg (parsed as Python str).
fd lives in state, initialised inside <pkg>_iq_reader_create() after
open().
Function with output buffer¶
jm function q15_to_float --module io \
--param input:int16_t[] \
--out-param output:float[] \
--param n:size_t
input is const int16_t *; output is writable float * (no
const). Python side: q15_to_float(input_arr, output_arr, n) —
caller passes both buffers.
Type slots — per-slot detail¶
| Slot | CLI flags | TOML field |
|---|---|---|
| State variable | --state name:T:D |
[[obj.state]] type = "T" |
| Step input / output | --arg-type T, --return-type T |
arg_type, return_type |
| Constructor / init param | --init-param name:T[:D] |
[[obj.init_params]] type = "T" |
| Module function param | --param name:T, --out-param name:T[] |
[[module.<mod>.functions.params]] type = "T" |
| Method param | --param name:T, --out-param name:T[] |
[[<obj>.methods.params]] type = "T" |
Templates in the gallery list their concrete type choices per slot at the bottom of each page, with links back into the sections below.
State variable types¶
State variables are declared with --state name:type[:default].
The type determines the C struct field, the PyArg_ParseTuple format code, the
Python type in the generated stub, and the default zero value used when no
default is supplied.
Supported types¶
Floating point¶
| Type | C field type | NumPy type | Format | Zero literal |
|---|---|---|---|---|
float |
float |
np.float32 |
f |
0.0f |
double |
double |
np.float64 |
d |
0.0 |
Integer¶
| Type | C field type | NumPy type | Zero literal |
|---|---|---|---|
int |
int |
np.int32 |
0 |
int8_t |
int8_t |
np.int8 |
0 |
int16_t |
int16_t |
np.int16 |
0 |
int32_t |
int32_t |
np.int32 |
0 |
int64_t |
int64_t |
np.int64 |
0 |
uint8_t |
uint8_t |
np.uint8 |
0U |
uint16_t |
uint16_t |
np.uint16 |
0U |
uint32_t |
uint32_t |
np.uint32 |
0U |
uint64_t |
uint64_t |
np.uint64 |
0U |
size_t |
size_t |
np.uintp |
0 |
ptrdiff_t |
ptrdiff_t |
np.intp |
0 |
bool |
bool |
np.bool_ |
0 |
Fixed-width types require <stdint.h>, which is included via clib_common.h.
They are parsed through the nearest standard integer type and cast to the
declared type in the generated extension.
size_t and ptrdiff_t are pointer-sized types useful for lengths, offsets,
and index arithmetic. They map to NumPy's uintp and intp respectively.
int is kept for convenience; prefer int32_t when bit-width matters.
bool is parsed with CPython's p format, so any truthy Python value sets
it, and its getter returns a Python bool.
Complex¶
| Type | C field type | NumPy type | Zero literal |
|---|---|---|---|
float _Complex |
float _Complex |
np.complex64 |
0.0f + 0.0f * I |
double _Complex |
double _Complex |
np.complex128 |
0.0 + 0.0 * I |
long double _Complex |
long double _Complex |
np.clongdouble |
0.0L + 0.0L * I |
Complex types are parsed via Py_complex (CPython format "D") and cast to
the target C type. long double _Complex is truncated to double at the
Python boundary.
Fixed-length arrays¶
Append [N] to a type marked N in Supported types to
embed a fixed-length C array directly inside the state struct. N must be a
positive integer literal.
--state "coeffs:float[16]" # float coeffs[16];
--state "delay:float _Complex[16]" # float _Complex delay[16];
--state "history:double[64]" # double history[64];
The array lives inside the struct — one malloc for the whole object, no
pointer chasing, no separate free. This is the right choice for fixed-size
delay lines, coefficient tables, and circular buffers whose length is known at
code-generation time.
Array fields do not support explicit defaults — they are always
zero-initialized at construction. Each gets array accessors:
<pkg>_<comp>_get_<name>(state, dest), <pkg>_<comp>_get_<name>_view(state)
and <pkg>_<comp>_set_<name>(state, src) in C, and get_<name>(),
get_<name>_view() and set_<name>(ndarray) in Python. Your C implementation
can also index state->coeffs[i] directly.
Array fields work with --state (standalone objects and object --module)
and are recorded verbatim in just-makeit.toml, so jm add and jm config
round-trip them correctly.
Opaque state fields (pointers, handles)¶
For heap pointers, file handles, FFTW plans, or any C type that doesn't
fit _CTYPE_META, declare the field as opaque = true (currently
TOML-only; --state name:type:opaque flag pending). The type string
is emitted into the struct verbatim and no auto-getter, setter, kwarg,
or reset assignment is generated — lifecycle is your responsibility,
written directly in _core.c's create() / destroy() (and
optionally reset()) bodies. See the
Quick reference opaque section above for the
short version and the vendor plan pattern
for a worked example.
Defaults¶
If you omit the default, the zero literal for the declared type is used:
Explicit defaults must be valid C literals for the type:
Note: Complex defaults are C complex literals:
--state 'pole:double _Complex:1.0+2.0*I'(the stub shows(1+2j)).
A default is one value with two spellings. The C faces write it as declared; every Python face (the generated test, the stub's signature and doctest, the runtime docstring) writes the same value in Python (gh-1946):
| Declared (C) | Python spelling |
|---|---|
0.1L, 1.5f, 7ULL |
0.1, 1.5, 7 (suffix dropped) |
017 (octal), 0x1F |
0o17, 0x1F |
2 in a double |
2.0 |
0.1f in a double |
0.10000000149011612 (what C stores) |
Two literals have no Python spelling and are refused where they are declared:
an octal with an 8 or 9 in it (08, which C refuses too), and a floating
literal for an integer type (1.5 or 1e3 into an int, which C converts
silently and Python refuses).
A stub's doctest prints what the getter returns, which for a float or
float _Complex field is the declared value rounded to single precision:
--state 'z:float _Complex:0.1' reads back (0.10000000149011612+0j)
(gh-1947).
C to Python mapping¶
Getters return the matching Python scalar (float, int, complex, bool);
setters accept the same:
double <pkg>_engine_get_gain(const <pkg>_engine_state_t *state);
void <pkg>_engine_set_gain(<pkg>_engine_state_t *state, double val);
uint8_t <pkg>_engine_get_channel(const <pkg>_engine_state_t *state);
void <pkg>_engine_set_channel(<pkg>_engine_state_t *state, uint8_t val);
def get_gain(self) -> float: ...
def set_gain(self, value: float, /) -> None: ...
def get_channel(self) -> int: ...
def set_channel(self, value: int, /) -> None: ...
Notes¶
- Without
--init-param, every state variable is an optional keyword argument to__init__, soComponent()with no arguments is valid. Once init-params are declared they alone form the constructor, and a:requiredone must be passed. reset()restores every field to its declared default, not the zero literal.- The state struct is public in
<comp>_core.hunless--opaque-state; from Python, reach fields through the generatedget_<name>()/set_<name>().
Step input / output types¶
The --arg-type and --return-type flags set the C signature of
<pkg>_<comp>_step and <pkg>_<comp>_steps. Both accept the same allowlist
plus a few shape forms.
Scalar shapes¶
Every type marked IO in Supported types is a legal
--arg-type / --return-type value: every registered type except
const char *. Strings can't flow through a sample-by-sample DSP step:
steps() moves a block of samples as an ndarray, which has no dtype for a
C string. jm new,
jm object (every --preset included) and jm apply of a manifest
arg_type / return_type refuse it, as a scalar and as a T[] element,
with one error: line, before anything is written. Take text as an
init-param or a method --param instead.
Array shape — T[]¶
Append [] to any scalar shape to declare an input
array that arrives as a numpy ndarray of that type's dtype and expands to
(const T *name, size_t name_len) in C.
That is wider than what an array parameter takes
(below): bool[], int[] and
long double _Complex[] are step arrays, not parameter arrays.
The void shape¶
Pass void to either flag to omit that side of the signature:
| Combination | What it produces | Preset |
|---|---|---|
--arg-type void --return-type T |
Generator: step() takes no input. |
generator |
--arg-type T --return-type void |
Consumer: step() returns nothing. |
consumer |
--no-step |
Custom verbs only; no auto step() / steps(). |
reader (adds a filepath init-param) |
Array parameter element types¶
An array passed to a method or module function (--param name:T[],
--out-param name:T[]) or to the constructor (--init-param name:T[]),
and the element of an --out-type result, takes one of these: the A
column of Supported types. A step's T[] takes more
(Array shape), and a state T[N] the N column.
T[] form |
C element | NumPy dtype |
|---|---|---|
float[] |
float |
np.float32 |
double[] |
double |
np.float64 |
float _Complex[] |
float _Complex |
np.complex64 |
double _Complex[] |
double _Complex |
np.complex128 |
int8_t[] |
int8_t |
np.int8 |
int16_t[] |
int16_t |
np.int16 |
int32_t[] |
int32_t |
np.int32 |
int64_t[] |
int64_t |
np.int64 |
uint8_t[] |
uint8_t |
np.uint8 |
uint16_t[] |
uint16_t |
np.uint16 |
uint32_t[] |
uint32_t |
np.uint32 |
uint64_t[] |
uint64_t |
np.uint64 |
size_t[] |
size_t |
np.uintp |
ptrdiff_t[] |
ptrdiff_t |
np.intp |
Constructor / init-param types¶
Constructor parameters are the broadest slot. They feed <pkg>_<comp>_create
and the Python __init__, and they need to accept things the DSP hot
path doesn't — filepaths, format names, optional buffers.
| Type form | Use case | Example |
|---|---|---|
Any scalar including const char * |
flags, options, paths | --init-param filepath:"const char *" |
Any array shape T[] |
required positional ndarray | --init-param coeffs:"float _Complex[]" |
T[][] (2-D array) |
required 2-D ndarray (e.g. polyphase banks) | --init-param bank:"float _Complex[][]" |
string_enum:a,b,c |
optional string mapped to a C enum index | --init-param mode:"string_enum:read,write,rw" |
enum:<name> |
a named [[enum]] (single source of truth) |
--init-param mode:"enum:io_mode" |
T[N] (fixed length) |
not accepted here — use --state for that |
— |
const char * is legal as an init-param but not as a state field —
strings live in Python land or the caller's memory; the state struct
holds the parsed/converted result. The reader template carries
filepath:"const char *" in its init-params and fd:int in its state.
Named enums — [[enum]] single source of truth¶
string_enum:a,b,c inlines the choices on the parameter. When the same
value set is used by more than one parameter — or needs to feed more than the
one binding (a CLI choice flag, a JSON field, a C enum) — inlining duplicates
it, and the copies drift. Declare it once at the top level instead, then refer
to it with enum:<name>:
[[enum]]
name = "io_mode"
values = ["read", "write", "rw"] # order IS the C int — append-only
[[reader.init_params]]
name = "mode"
type = "enum:io_mode"
enum:<name> resolves to the equivalent string_enum:read,write,rw on the
codegen read path, so it behaves identically everywhere string_enum: does
(choice flags, stubs, the C enum index) — the manifest just keeps the value
list in one place. Value order is the C integer value, so append only; never
reorder. Referring to an undeclared enum is an error. (Requires schema 7;
run jm upgrade.)
When the C values are not 0..n-1 — enumerators¶
A C enum with a sentinel at -1, a gap, or a first value other than 0
cannot be bound by position. Name each choice's C constant, in the same
order as values:
[[enum]]
name = "level"
values = ["auto", "debug", "info", "warn"]
enumerators = ["LVL_AUTO", "LVL_DEBUG", "LVL_INFO", "LVL_WARN"]
Every path a choice crosses then carries the constant, not the index:
constructor params, properties, method and function params, handle and
composer fields, and a C app's choice flags. The generated tables spell the
constants by name, so the C compiler checks each one, and a renamed constant
fails the build instead of shifting a meaning. Every negative value is legal:
a string is looked up as an index first, and only that index can signal "not
found". A value that is none of the constants raises ValueError when read
back.
Each name must be a C identifier visible where the binding compiles, which
is the component's header. There must be exactly one per value. The same
list also attaches each constant's ///< doc to its choice in the
docstrings. Worked end to end:
Binding a C enum whose values are not 0..n-1.
Module-function param types¶
Module-level functions (jm function FN --module MOD) accept scalars,
T[] arrays, path, and a named [[enum]]
(--param name:enum:<ename>[=default]) — not inline string_enum:
choices or 2-D arrays.
| Param flag | Legal types |
|---|---|
--param name:T |
Any scalar, including const char * (Python str), or any T[] array shape. Arrays are const-qualified. |
--out-param name:T[] |
Array shapes only. Drops const. Rejected for scalars (gh-72). |
jm function --out-type T makes the function return a fresh ndarray sized
from the first array param's length. --out-type 'T[n]' names the integer
param that holds the length instead (make_window(512) from
--param n:size_t --out-type 'float[n]'). With --variable-output,
--out-size EXPR sizes it with a C expression over the arguments instead,
and the C function returns how many elements it wrote. The C function is
handed only the pointer, never the length, so an output with none of these
is refused rather than guessed at (gh-1888).
Method param types¶
Methods on stateful objects (jm method OBJ METHOD) accept the same
set as module-function params (--param plus --out-param semantics),
extended with these knobs:
| TOML field | CLI | Effect |
|---|---|---|
variable_output = true |
--variable-output |
Method returns up to max(<pkg>_<comp>_<verb>_max_out(state, n), n) samples; the binding allocates a NumPy-owned array per call. See Array memory ownership. |
out_type = "T" |
--out-type T |
The method writes a fresh T[] buffer sized from an array param length (or a scalar integer param, per gh-65). |
result_fields = [{name, type}, …] |
--result-field name:type (repeatable) |
The method emits a list of records; each tuple becomes a row in the returned list. Field types follow the state variable allowlist. |
enum = "<name>" |
TOML only on a method (jm function --param name:enum:<ename> is the function's spelling) |
The param is int in C and the choice string in Python, validated against a top-level [[enum]] (gh-1021). Same spelling as a module-function param. |
Enum params: which spelling goes where¶
An enum reaches the generated code by two different spellings, and they are not interchangeable — the same declaration in the wrong place is either inert or an error:
| Where | Spelling |
|---|---|
| method param | type = "int" + enum = "<name>" |
| module-function param | type = "int" + enum = "<name>" |
| object property | type = "int" + enum = "<name>" |
| init_param | type = "enum:<name>" |
The enum key keeps the enum's name, so the generated C indexes the
[[enum]] SSOT table directly. The init_param form flattens to
string_enum:a,b,c at read time and loses the name, which is why it is
spelled differently and why writing it on a method param is refused with a
diagnostic naming the form that works.
[[enum]]
name = "wfm_stage_kind"
values = ["none", "rs", "conv"] # order IS the C int
[[frame.methods]]
name = "add_stage"
return_type = "int"
params = [
{ name = "depth", type = "int" },
{ name = "kind", type = "int", enum = "wfm_stage_kind", default = "rs" },
]
frame.add_stage(2) # kind defaults to "rs"
frame.add_stage(2, "conv")
frame.add_stage(2, "nope") # ValueError: invalid kind 'nope'
# (choices: none, rs, conv)
A default is the choice string, not the C index — an int default is
refused at generation time rather than failing on the first call that omits
the argument.
Sacred vs glue files¶
The manifest (just-makeit.toml) is the source of truth, but not every
generated file is rewritten the same way when you re-run jm apply:
- Glue — regenerated every apply.
<comp>_ext.c,src/<pkg>/<comp>.pyiand the umbrella<pkg>.hare derived from the manifest, and the componentCMakeLists.txtis reconciled against it, keeping your riders (your own CMake goes in<comp>_extra.cmake). Edit the TOML and they refresh on the next apply. <comp>_core.h— mixed.applyinjects a missing method/property declaration, but the inlinestep()body and the state struct are sacred — never re-rendered. A new state field reaches the struct via a rebuild, notapply.<comp>_core.c— sacred. Once it exists,jm applynever splices or re-renders it; thesteps()/ lifecycle bodies are yours.
So editing the manifest propagates to glue and injects missing declarations,
but a signature change or a new state field is structural — use
jm regenerate (or jm add for state) to rebuild the sacred body. A new
method or computed property is additive: jm method / jm property inject a
declaration and append a fresh stub.
jm regenerate <component> is the deliberate-refresh half: it deletes
every file the component owns and re-runs jm apply, rebuilding a clean
scaffold from the manifest (the manifest itself is untouched, unlike
jm remove). By default it lifts hand-written _core.c / _core.h bodies
and splices them back into the fresh scaffold (best-effort); --discard
drops them for a clean reset. git stash first either way. --force skips
the single confirmation. Works for both standalone and module objects.
See also¶
- Template gallery — each preset declares its slot allowlist concretely at the bottom of its page.
- doppler — Type System —
how doppler uses these C types in its DSP APIs (CF32, CF64, integer IQ
pairs,
dp_sample_type_t).