ꘇ chop.gesture
Record a mouse drag once, replay it forever
- channels
- x
- y
- value
ꘇ chop.gesture records you dragging a mouse - X, Y, or both at once - and then plays that motion back as a control signal. Draw a curve with your hand once, and it drives a parameter on every run, in the workflow file, with no mouse involved at playback. It's the least obvious node in the Genereti CHOP family and the most fun.
What it is
Genereti's chop.* nodes are a control-signal layer for ComfyUI in TouchDesigner's vocabulary: named float channels you can wire into any numeric widget. Most of the family generates signals mathematically - chop.constant, chop.lfo, chop.expression. chop.gesture is the one that generates them from you.
Some motions are easier to demonstrate than to describe - a sway, a hand-drawn envelope, a two-axis wander a sine can't fake. This is a performance-capture node for mouse gestures, in a pack built around live performance.
How it works
Recording is explicit. You press the record control on the node, drag in the node's surface, and stop. The take is stored as JSON inside the workflow - the recording field is a socketless string, and its default is an empty clip:
{"version": 1, "duration": 1, "points": []}
That's the whole storage format: a duration and a list of {t, x, y} points. Because it saves with the workflow, a shared graph carries its gestures with it, and a queued run samples the saved recording rather than asking you to perform again. Live, the node interpolates the recorded points against the current phase; queued, it uses the explicit time and samples you set.
Recording validation is strict, and usefully so: duration must be between 0 and 600 seconds, no more than 8192 points, and every point's time must be finite, non-decreasing and inside the duration, with x and y normalised to 0–1. Feed it a hand-edited JSON blob that violates any of that and you get "Invalid gesture recording" or "Invalid gesture point" instead of silent garbage.
An empty recording isn't an error - it flattens both axes to 0.5, a straight line through the middle. That's your tell that you're driving a parameter with a gesture that was never recorded.
The inputs and outputs that matter
mode-XY,XorY. Which axes the drag captures. InYmode thevalueoutput follows Y; otherwise it follows X.x_low/x_highandy_low/y_high- remap the recorded 0–1 range onto whatever your parameter wants. The recorded data stays normalised, so one take can drive a 0–1 opacity and a −50..50 displacement with two nodes.loop- wraps the recording so it cycles. Off, it plays once and holds the end.quantize- snaps the captured timing to a grid as you record, handy for gestures aimed at musical intervals.interval,speed-intervalpreserves real timing (free) or retimes the take to a musical interval;speed(0.01–16) scales playback rate.smooth- an exponential easing applied to the output (same maths aschop.lag), for taking the hand tremor out.time,samples,sample_rate- the usual CHOP timing controls;samples = 1for responsive scalar control.
Outputs: channels (a CHOP block carrying x and y), plus x, y and value as separate FLOATs. Wire value into a converted numeric input for the simple case, or take channels when you want both axes in a chain.
Install
Manager → search Genereti, or from your ComfyUI directory:
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 and reload the browser page. requirements.txt is a single line (comfyui-frontend-package>=1.53.6), the rest is already in ComfyUI, and there's nothing to download - no models, no samples, no external service. Comfy ≥ 0.38.2, Python ≥ 3.11, modern browser. There's a gesture demo referenced in the pack's workflow list; open it before hand-building one, because the recording widget is custom UI.
Common issues
The output is a flat line at 0.5. Nothing was recorded, or the take didn't survive - the empty clip defaults to the midpoint on both axes. Re-record and save the workflow; the take lives in the node's recording field, so if it isn't there after a reload, it never got captured.
"Invalid gesture recording." Usually a hand-edited JSON blob. The constraints are 0–600 s duration, ≤8192 points, monotonic timestamps, and x/y within 0–1.
The gesture plays back but the effect is barely visible. Check x_low/x_high - the recording is normalised, so an unmapped take gives you 0–1 while a parameter might want tens or hundreds. Also check smooth: a large value can flatten a fast gesture into near-stillness.
Playback feels too fast or too slow. speed is a multiplier; interval retimes to musical values and follows project transport, so a take set to 4n runs four-to-the-bar rather than in seconds.
And the pack-level caveat, which matters more than usual for a node whose data format is versioned: this is a solo MIT alpha by Liuboto, v0.1.4, self-described as "super alpha - early classroom testing", with an explicit warning to back up workflows before updating.
Inputs (15)
| Name | Type | Default | Description |
|---|---|---|---|
| mode | COMBO | 3 options: XY, X, Y | |
| x_low | FLOAT | 0.00-100000–100000 | — |
| x_high | FLOAT | 1.00-100000–100000 | — |
| y_low | FLOAT | 0.00-100000–100000 | — |
| y_high | FLOAT | 1.00-100000–100000 | — |
| loop | BOOLEAN | true | — |
| quantize | BOOLEAN | true | — |
| recording | STRING | {"version": 1, "duration": 1, "points": []} | — |
| interval | COMBO | free | 19 options: free, 4m, 2m, 1m, 1n, 2nd, +13 |
| speed | FLOAT | 1.000.01–16 | — |
| smooth | FLOAT | 0.000–10 | — |
| time | FLOAT | 0.00-100000–100000 | — |
| samples | INT | 11–4096 | — |
| sample_rate | FLOAT | 60.001–1000 | — |
| performanceopt | STRING | {} | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| channels | GENERETI_CHOP | — |
| x | FLOAT | — |
| y | FLOAT | — |
| value | FLOAT | — |