ꘇ chop.expression
Sine waves, maths and a text box
- input
- controls
- channels
- value
ꘇ chop.expression evaluates a small arithmetic expression once per sample, per channel, and hands you the result as a control signal. The default is sin(t*tau + c) - a sine wave where each channel gets its own phase offset. It's the node you reach for when you want a parameter to move in a way an LFO preset can't quite do.
What it is
Genereti's chop.* family is a control-signal layer for ComfyUI, borrowed straight from TouchDesigner's vocabulary: a CHOP is a named block of float samples with a sample rate, and any numeric widget on any node can be converted to an input and driven by one. chop.expression sits at the flexible end of that family, next to chop.constant (fixed), chop.lfo (oscillating) and chop.lag (smoothed).
Why a text box instead of a knob: because an expression can read the project clock, so you tie a parameter to musical position rather than to a stopwatch.
How it works
Two modes, decided by whether input is connected:
- Generate mode (no input) - it writes a fresh signal from scratch.
tis the sample time,ithe sample index, andxthe normalised sample coordinate across the block (0 to 1).wis the block size,hthe channel count, andcis the index of the channel being computed, sosin(t*tau + c)gives you channels that are out of phase with each other - exactly what you want for per-channel colour or rotation. - Process mode (input wired) - it computes per channel from the incoming data.
vis the current input value andais an alias for it,truns from the input block's start time, and channel names are preserved through to the output. That's how you reshape an analysis tap or a gesture.
The parser is deliberately not JavaScript. The docs describe it as a bounded arithmetic parser: numbers, operators, and a fixed function set - sine, cosine, and the coherent-noise helpers perlin, simplex and value, which are the ones to use when you want noise rather than flicker. It also exposes the project performance fields, so __.beat, __.bar, __.bpm, __.ticks, __.phase and __.playing (as 0 or 1) are readable inside the expression, even when the node's own t is running in free time.
That distinction matters and is easy to miss: local t is the node's own time domain, editable via time, offset_t, sample_rate and samples; __.time always reads the project clock.
The inputs and outputs that matter
The required widgets: expression (multiline, default sin(t*tau + c)), channels (1–64), samples (1–4096, default 1), sample_rate (default 60), time (default 0). The samples and sample_rate tooltips are the honest ones from the pack: sample_rate is samples per second, not blocks per second, and live updates per second = sample_rate / samples, limited by browser frames. Use samples = 1 for responsive scalar control; 8 at 60 Hz gives you 7.5 blocks per second.
Optional inputs: input (the CHOP to process), performance (a JSON clock snapshot from a transport), the four domain-offset ports offset_x, offset_y, offset_z, offset_t, plus parameters and controls - the mechanism for naming values and driving them from other nodes instead of baking constants into the text.
offset_x shifts the normalised sample coordinate, y and z supply extra domain coordinates (feed them into noise functions), and offset_t adds seconds to the sample time. This is what lets you animate a noise field rather than watch a static slice of it.
Outputs: channels (the block) and value (a FLOAT - the last sample of the first channel). Wire value into a converted numeric widget; use channels when the signal needs to be part of a bigger control chain.
Install
Manager → search Genereti - or:
git clone https://github.com/languel/genereti.git custom_nodes/genereti
./.venv/bin/python -m pip install -r custom_nodes/genereti/requirements.txt
Restart ComfyUI, then reload the browser. requirements.txt is a single line (comfyui-frontend-package>=1.53.6); NumPy and the rest come from ComfyUI, and there's nothing to download. Comfy ≥ 0.38.2, Python ≥ 3.11. Restarting matters here more than usual because the expression text box is a custom frontend widget.
Common issues
The expression is rejected or silently returns zero. The parser is bounded arithmetic, not a scripting language - no variable assignments, no object access, no if. Rewrite conditionals as arithmetic (multiply by a comparison, use abs, or clamp via a downstream chop.math with the clamp operation).
Nothing moves while the workflow runs. Live evaluation happens in the browser; queued runs evaluate explicit samples and time. If you're queueing and expecting continuous drift, that's the mismatch.
c does nothing visible. c is the channel index and only pays off when channels is greater than 1; with one channel it's a constant.
Noise looks like mush. Use perlin/simplex/value for coherent motion, and animate it by feeding offset_t from a clock rather than cranking speed. At the default samples = 1 you're evaluating single points, so the block-wide helpers (w, x, i) only earn their keep at higher samples.
Timing drifts when the tab is backgrounded. Browser throttling can interfere with control updates and event lookahead; keep the tab focused when you're timing by eye.
And the standing caveat for this pack: solo MIT alpha by Liuboto, v0.1.4, "super alpha - early classroom testing", with a warning to back up workflows before updating.
Inputs (13)
| Name | Type | Default | Description |
|---|---|---|---|
| expression | STRING | sin(t*tau + c) | — |
| channels | INT | 11–64 | — |
| samples | INT | 11–4096 | Samples per delivered block. Live updates/s = sample_rate / samples, limited by browser frames. Use 1 for responsive scalar control; 8 at 60 Hz delivers 7.5 blocks/s. |
| sample_rate | FLOAT | 60.001–1000 | Samples per second, not blocks per second. A block contains samples values; use samples=1 for updates near this rate. |
| time | FLOAT | 0.00-100000–100000 | — |
| inputopt | GENERETI_CHOP | — | |
| performanceopt | STRING | {} | — |
| offset_xopt | FLOAT | 0.00-100000–100000 | — |
| offset_yopt | FLOAT | 0.00-100000–100000 | — |
| offset_zopt | FLOAT | 0.00-100000–100000 | — |
| offset_topt | FLOAT | 0.00-100000–100000 | — |
| parametersopt | STRING | {} | — |
| controlsopt | COMFY_AUTOGROW_V3 | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| channels | GENERETI_CHOP | — |
| value | FLOAT | — |