A seeded generative engine in TouchDesigner: one master seed drives every layer through a declarative parameter table, and every render ships with a SHA-256 verification hash.
metacells was built to produce a numbered series of looping video works, released as an NFT series on a platform that has since shut down. Each piece had to be unique, reproducible from its seed, and verifiable after the fact.
The interesting part is the system rather than the release: randomness is controlled from one place, parameters are declared as data instead of being wired by hand, and every output can be traced back to its seed and checked against its hash.
flowchart LR
S[Master seed] --> N[Local noise<br/>one sample per parameter]
N --> T[Param table<br/>par_name · op · par · min · max]
T --> L[Layer<br/>TouchDesigner network]
L --> R[Render<br/>TIFF sequence → H.264 MP4]
R --> H[SHA-256 hash]
-
Master seed. A single
constant_master_seedvalue is bound to the seed of every layer's local noise (execute_startup.py). Each render uses the next seed (0, 1, 2…), so any output can be regenerated from its number. -
Local noise. Each layer has a noise CHOP whose resolution is set to the number of parameters in its table, so every parameter gets its own deterministic value in
[0, 1]. -
Param table. Each layer declares what the seed controls in a tab-separated
table_pars.py:par_name op par min max pos_seednoise_posseed0 9000 cin_periodnoise_cineticperiod0.01 1.0 geo_rowtube1rows2 20 blur_sizeblur1size1 32 On startup each row is connected to its operator parameter with an expression; on every value change the noise sample is mapped into
[min, max](rounded whenminis an integer) bycommon.mapTableRange. Adding a parameter is one row, not new wiring.base_layer_0is the fully mapped example, with 17 parameters across noise, geometry, levels and blur. -
Layer. Each
base_layer_Nis built from a shared template (texture selector, palette selector from a generated 900-palette library, local noise, mapping tables); the rest of the network is unique to the layer. -
Render. With rendering enabled,
execute_rendering.pyturns off real-time playback, plays the timeline through so the output loops seamlessly, saves a TIFF sequence, and encodes it to a 60 fps H.264 MP4 with ffmpeg. -
Hash. ffmpeg's
hashmuxer writes a SHA-256 of the video's decoded frames next to it (render/hashes/metacells.<seed>.sha256), so a piece can be verified later independently of its container metadata. Then the seed increments and the next piece renders.
Output layout:
render/
img-sequence/metacells.<seed>/metacells.<seed>.<frame>.tif
video/metacells.<seed>.mp4
hashes/metacells.<seed>.sha256
- Copy
_base_layer_templatein the project and rename it to the next number (e.g.base_layer_7). - Build the network and pick the parameters worth modulating.
- Create
src/base_layer_7/table_pars.pywith one row per parameter. - Restart the project, or press restart in
container_debug, to reconnect all parameters.
Requirements: Windows x64, TouchDesigner 2021.16270 or later (the free non-commercial licence works, capped at 1280×1280), and ffmpeg.
git clone https://github.com/somaticbits/metacells.git
cd metacells- Download the ffmpeg release essentials build, unzip it into the repo and rename the folder to
ffmpeg(the renderer callsffmpeg/bin/ffmpeg.exe). - Open
metacells.toe. - Click render. Renders land in
render/(gitignored).
macOS may work with path changes in execute_rendering.py; it has not been tested.
Built 2021–22 in TouchDesigner 2021.x on Windows. Kept as a reference project; not actively maintained.
Related: tezos-art-oracles, my 2022 thesis project on recording physical installations' sensor data on Tezos.
MIT.
