Operator Calculus, Co.
Browse Studio documentation
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):

outi={inputiif mi≥Tfillotherwise\text{out}_i = \begin{cases} \text{input}_i & \text{if } m_i \geq T \\ \text{fill} & \text{otherwise} \end{cases}

where mim_i is the mask value and TT is the threshold.

Soft feathering (feather > 0):

αi=smoothstep(mi−(T−f)2f)\alpha_i = \text{smoothstep}\left(\frac{m_i - (T - f)}{2f}\right) outi=αi⋅inputi+(1−αi)⋅fill\text{out}_i = \alpha_i \cdot \text{input}_i + (1 - \alpha_i) \cdot \text{fill}

where ff is the feather half-width and smoothstep provides a smooth transition:

smoothstep(t)=3t2−2t3for t∈[0,1]\text{smoothstep}(t) = 3t^2 - 2t^3 \quad \text{for } t \in [0, 1]

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_value and fill_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 a=lhs⋅gaa = \text{lhs} \cdot g_a and b=rhs⋅gbb = \text{rhs} \cdot g_b be the gain-scaled inputs.

Linear Modes:

  • linear: out=a+b+bias\text{out} = a + b + \text{bias}
  • sum: out=a+b\text{out} = a + b
  • difference: out=a−b\text{out} = a - b
  • abs_diff: out=∣a−b∣\text{out} = |a - b|
  • average: out=a+b2\text{out} = \frac{a + b}{2}
  • crossfade: out=(1−m)⋅a+m⋅b\text{out} = (1 - m) \cdot a + m \cdot b where mm is mix

Multiplicative Modes:

  • multiply: out=a⋅b\text{out} = a \cdot b
  • power: out=ab\text{out} = a^b (complex power for complex fields)

Extrema:

  • max: out=max⁡(a,b)\text{out} = \max(a, b)
  • min: out=min⁡(a,b)\text{out} = \min(a, b)

Modulation Modes:

  • am (amplitude modulation): out=(1+b)⋅a+bias\text{out} = (1 + b) \cdot a + \text{bias}
  • fm (frequency modulation): out=a⋅ei⋅b\text{out} = a \cdot e^{i \cdot b}
  • pm (phase modulation): out=∣a∣⋅ei(arg⁡(a)+b)\text{out} = |a| \cdot e^{i(\arg(a) + b)}
  • ring_mod: out=a⋅b\text{out} = a \cdot b (removes DC component)

Feedback Mode:

Leaky integrator with configurable splitting:

outn+1=(1−ε)⋅outn+ε⋅input\text{out}_{n+1} = (1 - \varepsilon) \cdot \text{out}_n + \varepsilon \cdot \text{input}

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 ε∈[0,1]\varepsilon \in [0, 1]; 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

L=gL⋅lhs,R=gR⋅rhsL = g_L \cdot \text{lhs}, \qquad R = g_R \cdot \text{rhs}

Then the selected mode computes:

  • linear: M=L+RM = L + R
  • crossfade: M=(1−m)L+mRM = (1-m)L + mR

Finally:

out=M+bias\text{out} = M + \text{bias}

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
  • crossfade preserves convex blending when mix stays 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
})