ꘇ chop.todat
Your signal, as a CSV you can actually read
- input
- table
- text
ꘇ chop.todat turns a signal block into a table: channel names along the top as columns, samples down the rows. In practice it's the "show me the numbers" node - the one you reach for when a value is misbehaving and you'd rather read it than guess from a waveform.
What it is
It's one of the six conversion directions the pack supports between images, signals and tables, and it's the pair to ꘇ dat.tochop. Where most conversion nodes exist to move data into a renderer, this one exists to make data legible: a CHOP block is a dictionary of names to float arrays, and a DAT table is something you can display, copy, and address a single cell in.
Two things it's genuinely good for beyond debugging. First, feeding a table consumer: ꘇ dat.expression can scale a captured block, ꘇ dat.select can crop it to a window of samples, ꘇ dat.totop can rasterize it back into an image. Second, taking a snapshot of live control data for use further down a batch pipeline - this is the CHOP node that behaves best under Queue, because a queued run has an explicit block to convert and no device state to miss.
Worth knowing where to find it: it displays as ꘇ chop.todat and lives in the Genereti / CHOP category, even though it's implemented alongside the DAT nodes and its outputs are table and text. Search chop.todat, todat or genereti and it comes up; don't go hunting in the DAT section of the menu.
How it works
The first row of the table is the channel names, in the order the block holds them. Then the samples are transposed: for each sample index, one row containing that sample from every channel. So a block with three channels of 64 samples becomes 65 rows by 3 columns, and reading down a column gives you one channel's history.
There's one structural gotcha in the mechanism: the transposition zips the channels together, so it stops at the shortest channel. If a merge or a resample left one channel shorter than the others, the table silently truncates to the shortest length rather than padding or erroring. Values arrive as floats and are serialized as strings in the immutable table; the text output is the same content as CSV, which is what makes it paste-able and diff-able.
There's no rate, no sampling control, no window - the node converts what the block already is. Whatever samples and sample_rate the upstream generator chose is what you get, so this is a good reason to keep block sizes small: a 4096-sample spectrum in a table is 4097 rows, which is nobody's idea of legible.
The inputs and outputs that matter
- input - the
GENERETI_CHOPto tabulate. Anything works: a generator, a processor, a switched source, a device reader. - table out -
GENERETI_DAT, channel names as a header row, one row per sample. - text out - STRING, the table as CSV. This is the output to use if your consumer wants a string, and the one that survives a Queue usefully.
Two outputs, one input, no parameters - and note there's no scalar passthrough, unlike almost every other CHOP node in the pack. If you need the raw FLOAT for a widget as well as the table, take it from the node upstream. Which is a decent sign you're using the right node for the job: conversions stay boring on purpose.
Install
All of these operators ship in the genereti pack. Manager → search genereti (title "ꘇ Genereti") → install → restart ComfyUI → hard-refresh the browser. Manual:
cd ComfyUI
git clone https://github.com/languel/genereti.git custom_nodes/genereti
./.venv/bin/python -m pip install -r custom_nodes/genereti/requirements.txt
Use your ComfyUI's own Python for that pip step. The requirements file is a single frontend pin, comfyui-frontend-package>=1.53.6 - no models, no server, no torch rebuild. Workflows → Genereti → ꘇ-Inspect-Values shows tables being read live, and ꘇ-OpenTouch-Operators-and-Lessons covers the conversion family as a set.
Common issues
The table is shorter than your block. Channel lengths differ and the transpose stops at the shortest. Line your channels up - ꘇ chop.merge resamples other to the input's length, so a merge upstream of this node usually fixes it.
Rows and columns feel swapped from what you expected. It's channels across, samples down. If you want the opposite shape, ꘇ dat.transpose exists.
Huge blocks produce useless tables. A spectrum node's thousands of bins become thousands of rows. Put ꘇ chop.select in front to keep the interesting channel, or reduce the block upstream.
Queue behaviour is actually good here. Unlike the live device nodes, a queued run converts the explicit block the graph evaluated - so this is one of the few CHOP-family nodes whose output means the same thing in a batch run as it does on screen. Just remember the block itself is deterministic under Queue.
Alpha. Super alpha, early classroom testing, per the README. Back up workflows before pack updates.
Inputs (1)
| Name | Type | Default | Description |
|---|---|---|---|
| input | GENERETI_CHOP | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| table | GENERETI_DAT | — |
| text | STRING | — |