Skip to content

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()'s calloc and untouched by destroy() / reset() — the acquire / release code is yours, as create_impl / destroy_impl in 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:opaque flag is pending. Today, marking a state field as opaque requires one line in just-makeit.toml (opaque = true on the entry). Because _core.c is sacred — jm apply never overwrites it — your hand-written lifecycle bodies survive later edits, except jm add, which rebuilds the component from the manifest (keep the bodies in create_impl / destroy_impl, or git stash first). 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 step T[]
  • I — init param (--init-param)
  • P — function / method param (--param)
  • A — element of an array parameter or result: T[] in --param, --out-param and --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, so bool[], int[] and long double _Complex[] arrive as np.bool_, np.int32 and np.clongdouble arrays. An array parameter (and --out-type) takes only the A column: spell those three uint8_t[], int32_t[] and double _Complex[] there. A state T[N] takes the A column plus int and long double _Complex, whose width the struct fixes; bool[N] is refused, so use uint8_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 a str. If you need a string in state, declare an opaque field and copy / strdup it in your _core.c create() body.

  • An integer the C type cannot hold is refused, not wrapped (gh-2144). Passing 300 to an int8_t raises OverflowError: 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 refuses np.int8(300) the same way. uint64_t and size_t included: -1 raises OverflowError rather than arriving as 2**64 - 1 (gh-2220). Every integer face takes any object with __index__, a numpy integer such as np.uint64(5) included, and refuses a float with TypeError. A generated C command line -- a jm app executable, a composer's CLI -- refuses a flag the same way, -1 for a uint64_t included: it prints <prog>: --gain: '300' is out of range for int8_t [-128, 127] and exits 2 (gh-2225). --state, jm add and a manifest [[<obj>.state]] entry all refuse it, as a scalar and as a T[N] element, and say so. So do --arg-type / --return-type and a manifest arg_type / return_type, as a scalar and as a T[] element (see Scalar shapes).

  • A long double _Complex scalar is truncated to double _Complex at the Python boundary. Its arrays are not: a step T[] and a state T[N] of it cross as np.clongdouble.

  • There is no complex-integer type, and integer IQ is a structured array. C's _Complex is float-only and numpy has no complex-integer dtype to map one onto, so int16_t _Complex has nothing to convert through; the CLI and jm apply both refuse it by name.

    Integer IQ is returned as a two-field record — record_dtype over int16_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 + 1 yields [1, 1, 3, 3, 5, 5, 7, 7], incrementing I only, because int32 addition carries across the I/Q boundary. arr * 2 or arr - dc corrupts 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 operations complex64 gets is tracked separately as gh-1314.

  • void is special — only legal as --arg-type or --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

jm object my_fir --state "coeffs:float[64]" --state "n_taps:uint8_t:0"

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:

--state gain:double        # 0.0
--state count:uint8_t      # 0U
--state "pole:double _Complex"  # 0.0 + 0.0 * I

Explicit defaults must be valid C literals for the type:

--state gain:double:1.0
--state order:int32_t:4
--state mask:uint8_t:255

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__, so Component() with no arguments is valid. Once init-params are declared they alone form the constructor, and a :required one must be passed.
  • reset() restores every field to its declared default, not the zero literal.
  • The state struct is public in <comp>_core.h unless --opaque-state; from Python, reach fields through the generated get_<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.

jm object xform --arg-type "float[]" --return-type "float[]"

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>.pyi and the umbrella <pkg>.h are derived from the manifest, and the component CMakeLists.txt is 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. apply injects a missing method/property declaration, but the inline step() body and the state struct are sacred — never re-rendered. A new state field reaches the struct via a rebuild, not apply.
  • <comp>_core.c — sacred. Once it exists, jm apply never splices or re-renders it; the steps() / 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).