Coupling Operators
On this page
Mask 🎭
add_mask_operator(ctx, input, mask, out, opts)
Gate a field by a mask with optional feathering. Implements selective region processing, apertures, conditional field operations, and smooth spatial transitions.
Method Signature
add_mask_operator(ctx, input, mask, output, [options]) -> operator
Returns: Operator handle (userdata)
Mathematical Formulation
Hard threshold (feather = 0):
where is the mask value and is the threshold.
Soft feathering (feather > 0):
where is the feather half-width and smoothstep provides a smooth transition:
Inverted mode:
Swaps the active and inactive regions (mask < threshold becomes active).
Parameters
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
mode |
enum |
"apply" |
apply, invert |
Mask application mode |
threshold |
double |
0.5 |
unbounded | Mask threshold for gating |
feather |
double |
0.0 |
≥0 | Soft transition half-width |
fill_value |
double |
0.0 |
unbounded | Value when mask is inactive (real part) |
fill_value_im |
double |
0.0 |
unbounded | Imaginary fill value for complex output |
accumulate |
boolean |
false |
— | Add to output instead of overwriting |
scale_by_dt |
boolean |
true |
— | Scale accumulated writes by dt |
Boundary & Initial Conditions
- Operates elementwise; no boundary handling required
- Input and mask fields must have compatible dimensions
- Complex fields: fill value uses both
fill_valueandfill_value_im
Stability & Convergence
- Unconditionally stable (bounded output for bounded input)
- Hard threshold (feather = 0) produces discontinuous results
- Soft feathering (feather > 0) provides C¹ continuous transitions
- Larger feather values produce smoother but wider transition zones
Performance Notes
- Hard threshold: simple comparison, very fast
- Soft feathering: requires smoothstep computation per sample
- Inverted mode has no additional cost (threshold comparison is reversed)
Examples
-- Hard threshold mask
ooc.add_mask_operator(ctx, input, mask, out, {
threshold = 0.5
})
-- Soft feathered mask (smooth transitions)
ooc.add_mask_operator(ctx, input, mask, out, {
threshold = 0.5,
feather = 0.1
})
-- Wide feather for gradient blending
ooc.add_mask_operator(ctx, input, mask, out, {
threshold = 0.5,
feather = 0.25
})
-- Inverted mask (keep where mask < threshold)
ooc.add_mask_operator(ctx, input, mask, out, {
mode = "invert",
threshold = 0.25
})
-- Custom fill value for inactive regions
ooc.add_mask_operator(ctx, input, mask, out, {
threshold = 0.5,
fill_value = -1.0
})
-- Complex field with complex fill value
ooc.add_mask_operator(ctx, complex_field, mask, out, {
threshold = 0.3,
feather = 0.05,
fill_value = 0.0,
fill_value_im = 0.0
})
-- Circular aperture (using radial mask)
-- First create radial coordinate mask, then apply
ooc.add_coordinate_operator(ctx, radial_mask, {
mode = "coord",
coord_mode = "radial",
coord_center_x = 128,
coord_center_y = 128,
normalize = "unit"
})
ooc.add_mask_operator(ctx, input, radial_mask, apertured, {
mode = "invert", -- keep center, block edges
threshold = 0.3,
feather = 0.05
})
Mixer 🥄
add_mixer_operator(ctx, lhs, rhs, out, opts)
Combine two input fields using various mixing formulas including linear blending, modulation, nonlinear operations, and feedback integration. Essential for signal processing, synthesis, and field coupling.
Method Signature
add_mixer_operator(ctx, lhs, rhs, output, [options]) -> operator
Returns: Operator handle (userdata)
Mathematical Formulation
Let and be the gain-scaled inputs.
Linear Modes:
- linear:
- sum:
- difference:
- abs_diff:
- average:
- crossfade: where is
mix
Multiplicative Modes:
- multiply:
- power: (complex power for complex fields)
Extrema:
- max:
- min:
Modulation Modes:
- am (amplitude modulation):
- fm (frequency modulation):
- pm (phase modulation):
- ring_mod: (removes DC component)
Feedback Mode:
Leaky integrator with configurable splitting:
Split modes affect the order of decay and injection:
- none: Combined update
- lie: Decay, then inject
- strang: Half decay, inject, half decay (symmetric)
Parameters
Core Parameters:
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
mode |
enum |
"linear" |
see below | Mixing formula |
lhs_gain |
double |
1.0 |
unbounded | Gain applied to left input |
rhs_gain |
double |
1.0 |
unbounded | Gain applied to right input |
bias |
double |
0.0 |
unbounded | Constant offset added after mixing |
accumulate |
boolean |
false |
— | Add to output instead of overwriting |
scale_by_dt |
boolean |
true |
— | Scale accumulated writes by dt |
Mode options: linear, multiply, crossfade, sum, power, am, fm, pm, ring_mod, max, min, average, difference, abs_diff, feedback
Crossfade Parameters:
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
mix |
double |
0.5 |
[0, 1] | Crossfade weight (0 = lhs, 1 = rhs) |
Feedback Parameters:
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
feedback_epsilon |
double |
0.1 |
[0, 1] | Feedback/injection strength |
feedback_epsilon_mode |
enum |
"input" |
see below | How epsilon is applied |
feedback_split |
enum |
"none" |
see below | Operator splitting strategy |
Epsilon mode options: input (scales injection), feedback (scales decay)
Split options: none, lie, strang
Boundary & Initial Conditions
- Operates elementwise; no boundary handling required
- Both input fields must have compatible dimensions
- Feedback mode maintains internal state; initialize carefully
Stability & Convergence
- Linear modes: Unconditionally stable
- Multiply/power: Can produce large values; consider clamping
- FM/PM: Unitary for real modulation; preserves magnitude
- Feedback: Stable for ; larger values respond faster but may oscillate
- Strang splitting: Second-order accurate; preferred for feedback with varying inputs
Performance Notes
- Linear modes are highly optimized
- Modulation modes (fm, pm) require transcendental functions
- Power mode with complex fields uses cpow (expensive)
- Feedback requires internal state storage
Examples
-- Linear combination with gains
ooc.add_mixer_operator(ctx, a, b, out, {
mode = "linear",
lhs_gain = 0.7,
rhs_gain = 0.3,
bias = 0.0
})
-- Simple sum
ooc.add_mixer_operator(ctx, a, b, sum, {
mode = "sum"
})
-- Element-wise product
ooc.add_mixer_operator(ctx, a, b, product, {
mode = "multiply"
})
-- Crossfade (35% toward rhs)
ooc.add_mixer_operator(ctx, a, b, blended, {
mode = "crossfade",
lhs_gain = 1.0,
rhs_gain = 1.0,
mix = 0.35
})
-- FM synthesis
ooc.add_mixer_operator(ctx, carrier, modulator, out, {
mode = "fm",
lhs_gain = 1.0,
rhs_gain = 0.2 -- modulation depth
})
-- Amplitude modulation
ooc.add_mixer_operator(ctx, carrier, envelope, am_signal, {
mode = "am",
lhs_gain = 1.0,
rhs_gain = 0.8
})
-- Phase modulation
ooc.add_mixer_operator(ctx, signal, phase_mod, pm_signal, {
mode = "pm",
rhs_gain = 0.5 -- radians
})
-- Ring modulation
ooc.add_mixer_operator(ctx, a, b, ring, {
mode = "ring_mod"
})
-- Element-wise maximum
ooc.add_mixer_operator(ctx, a, b, max_field, {
mode = "max"
})
-- Signed difference
ooc.add_mixer_operator(ctx, a, b, diff, {
mode = "difference"
})
-- Absolute difference
ooc.add_mixer_operator(ctx, a, b, abs_diff, {
mode = "abs_diff"
})
-- Power (a^b)
ooc.add_mixer_operator(ctx, base, exponent, power, {
mode = "power"
})
-- Feedback with input injection mode
ooc.add_mixer_operator(ctx, input, state, state, {
mode = "feedback",
feedback_epsilon = 0.2,
feedback_epsilon_mode = "input"
})
-- Feedback with Strang splitting (higher accuracy)
ooc.add_mixer_operator(ctx, input, state, state, {
mode = "feedback",
feedback_epsilon = 0.1,
feedback_epsilon_mode = "feedback",
feedback_split = "strang"
})
-- Accumulate average into running sum
ooc.add_mixer_operator(ctx, a, b, running_avg, {
mode = "average",
accumulate = true,
scale_by_dt = true
})
Metal Mix ⚙️
add_metal_mix_operator(ctx, lhs, rhs, out, opts)
Blend two fields using the Metal-friendly subset of mixer modes. This operator is intended for GPU-safe linear and crossfade workflows where the full add_mixer_operator feature set is unnecessary or unavailable.
Experimental: API shape and backend coverage may change as Metal-specific execution paths mature.
Method Signature
add_metal_mix_operator(ctx, lhs, rhs, output, [options]) -> operator
Returns: Operator handle (userdata)
Mathematical Formulation
Let
Then the selected mode computes:
- linear:
- crossfade:
Finally:
When accumulate = true, the result is added into the destination; scale_by_dt applies the usual timestep factor to accumulated writes.
Parameters
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
mode |
enum |
"linear" |
linear, crossfade |
Metal-safe mixing strategy |
lhs_gain |
double |
1.0 |
unbounded | Gain applied to the left-hand input |
rhs_gain |
double |
1.0 |
unbounded | Gain applied to the right-hand input |
mix |
double |
0.5 |
[0, 1] | Crossfade amount used when mode = "crossfade" |
bias |
double |
0.0 |
unbounded | Additive bias applied after mixing |
accumulate |
boolean |
false |
— | Add into the destination instead of overwriting |
scale_by_dt |
boolean |
true |
— | Scale accumulated writes by dt |
Boundary & Initial Conditions
- Operates elementwise; no spatial boundary handling is required
- Input fields must have matching dimensions and storage type
- Complex fields are mixed component-wise
Stability & Convergence
- Unconditionally stable for bounded inputs
crossfadepreserves convex blending whenmixstays in[0, 1]- Large gains or bias can still drive downstream operators into unstable regimes
Performance Notes
- Cheaper than the full mixer because it avoids modulation and nonlinear branches
- Suitable for simple fades, summation, and GPU-portable compositing
- Prefer this operator when targeting Metal-specific execution paths
Examples
-- Simple sum of two fields
ooc.add_metal_mix_operator(ctx, a, b, out, {
mode = "linear"
})
-- GPU-safe crossfade
ooc.add_metal_mix_operator(ctx, dry, wet, mix_out, {
mode = "crossfade",
mix = 0.35
})
-- Weighted blend with accumulation
ooc.add_metal_mix_operator(ctx, lhs, rhs, accum, {
mode = "crossfade",
lhs_gain = 0.8,
rhs_gain = 1.2,
mix = 0.5,
accumulate = true,
scale_by_dt = true
})