WebToe
GitHub Open editor

WebToe A web-native dataflow engine for real-time visuals.

Operators, wires and a live cook loop, TouchDesigner-style, in a browser tab. TOPs run as GPU passes, CHOPs drive parameters through expressions, SOPs and MATs feed a 3D renderer. Drop in a .toe and your network opens.

MIT · zero runtime dependencies · WebGL2 + WebGPU · 260 KB of JavaScript

This drawing is example 03 itself, read from its project file: three ramp chains rotated by LFOs through expressions (dashed), composited, hue-shifted.trans1.rotate = op('lfo1')['chan1']trans2.rotate = op('lfo2')['chan1']trans3.rotate = op('lfo3')['chan1']lfo1CHOP · lfolfo2CHOP · lfolfo3CHOP · lforamp1TOP · ramptrans1TOP · transformƒramp2TOP · ramptrans2TOP · transformƒramp3TOP · rampƒtrans3TOP · transformƒcomp1TOP · compositehsv1TOP · hsvadjustƒout1TOP · out
  • wire output into input
  • dashed a CHOP channel read by an expression
  • ƒ expression-driven parameter
  • ● display flag
03 This drawing is example 03 itself, read from its project file: three ramp chains rotated by LFOs through expressions (dashed), composited, hue-shifted. Open it →
The WebToe editor running example 03, lfo garden: a network of ramp, transform, composite and hsv adjust operators with live previews on every node, and the output in the viewer.
The editor at /app/, running the same patch on WebGL2. TOP nodes preview live: one GPU compositor paints the viewer and every visible thumbnail each frame.

In the editor

  1. Example 10, 3d lines, in the editor: geometry, camera, light, render, blur and composite nodes over the rendered scene of line ribbons and orange spheres.

    3D pipeline

    Example 10: SOP ribbons and instanced spheres in Geometry COMPs, a camera, two lights, a Render TOP and a glow chain. The output is painted behind the network, as in TouchDesigner.

  2. Example 09, showcase: a dense network of TOP, CHOP, COMP and DAT nodes with the composited output in the viewer.

    Every family at once

    Example 09: 27 nodes, a kaleidoscope COMP with in/out tunnels, and a CHOP rig driving the whole patch through live expressions.

  3. Example 02, feedback trails: rectangle, transform, composite, blur, feedback and level operators wired in a loop.

    Feedback

    A feedback TOP hands back the previous frame; a fading level turns mouse movement into trails.

  4. Example 05, chop playground: LFO, noise, math, lag and merge CHOPs, with the merge selected and its channels drawn in the viewer.

    CHOP scope

    Select a CHOP and the viewer scopes its channels, here a raw sum against its lagged copy.

  5. The create-operator dialog over a small noise patch, listing TOP operators in a searchable grid.

    Create operator

    Tab or double-click opens the palette: a tab per family, one search across all of them.

  6. Example 03 running on the WebGPU backend.

    WebGPU

    The same project on ?backend=webgpu. Every 2D TOP ships hand-written GLSL and WGSL.

01 · Import

Drop a .toe. It opens.

Drag a TouchDesigner project onto the editor and it becomes a live WebToe network. Supported operators run; everything else is kept as a stub with its name, wires, parameters and code, and the import report says exactly how much of each.

Decoded in the browser

WebToe reads the binary .toe / .tox container itself: no TouchDesigner install, no bridge, and the file never leaves your machine.

to decode a 20 MB show file in the browser (33,923 files)
0.35 s
production project files decoded byte-exact against TouchDesigner’s own toeexpand
125 / 125

Native .toe decoding is provided for research purposes only.

The bridge, as fallback

The local bridge runs the toeexpand of your own TouchDesigner install on 127.0.0.1 and hands the expansion to the page. One command, or bridge.py run with the Python that ships inside TouchDesigner.

terminal
npx webtoe        # → http://127.0.0.1:9881/app/

Binds to 127.0.0.1 by default, ships nothing of Derivative’s, and deletes every upload when it is done.

Measured on a real show file

A 20 MB production project dropped on the page and decoded natively, in Chrome on a MacBook Pro (M4 Max), 2026-10-04. The bridge path took 8.0 s on a comparable file in August.

nodes imported
14,656
runnable (10,343 nodes)
71%
Python expressions translated
2,246
files decoded from the container
33,923
from drop to a running graph
0.54 s
The TouchDesigner import report: 213 nodes, 71 runnable, 142 kept as stubs with structure, wires and layout preserved; 9 expressions translated, 14 kept inert.
The import report for a 213-node production project at v1.1: 71 runnable. The same file reached 88 after the first evolution cycle.

Recovered: node types and hierarchy, wires across COMP boundaries and through in/out tunnels, parameter values and modes, DAT text and tables, network layout, and live Python expressions, translated where faithful (absTime.seconds*0.2 → time.seconds*0.2) and kept inert otherwise. Imported Python never runs.

Importing TouchDesigner projects →

02 · Dataflow

Operators, wires, expressions, one cook loop.

A network is operators wired together. Each frame the engine pulls from the output, cooks what it depends on once, and memoizes the result for that frame. Feedback is the one deliberate cycle: it reads the previous frame.

81 operator types, counted from the engine’s registry at build time. All operators →

Every parameter can be an expression

Expressions are JavaScript evaluated against a fixed scope: time, me, op(), parent(), ext() and a math library (sin, clamp, fract, lerp, rand …). They can read channels and other parameters, live, with a cycle guard.

Expressions →

expressions
op('lfo1')['chan1']          // a CHOP channel, by name
parent().par.speed * 0.5     // a parameter up the hierarchy
time.seconds * 0.2           // engine time
ext('energy', 0.5)           // a value the host page sends in

03 · Parity

Measured against real TouchDesigner work.

“Complete” is defined by data, not by a feature list: 60 real TouchDesigner projects from a daily generative-art practice (28,698 nodes, 2022–2026), expanded with the official toeexpand and counted by operator type. Only aggregate numbers are published.

Corpus nodes that run as WebToe operators
  1. v1 first release32.3%
  2. Cycle 1 expressions v2, routing TOPs, DAT tables47.1%
  3. R3 the 3D pipeline: SOPs, MATs, geometry, camera, render62.3%

Each step was re-measured on the same 28,698 nodes (docs/ROADMAP.md). The rest import as stubs: structure, wires, layout and code survive.

runnable nodes in a 213-node reference project, across the same three steps
56 → 71 → 88
operators in the official TouchDesigner inventory, across 7 families
~675
operator types WebToe ships today
81
runnable in a 2025 POP-heavy project once POPs mapped onto the SOP implementations
43% → 73%

Next, in order

  1. Multi-sample CHOPs and time slicing, the prerequisite for audio, trail, resample and wave.
  2. GLSL TOP: TouchDesigner’s injected shader contract, shimmed onto WebGL2.
  3. POPs on WebGPU compute, the reason the second backend exists.

Python is a permanent boundary. Imported Python is kept and shown, never executed; the planned answer is an opt-in JavaScript callback DAT.

The TouchDesigner parity charter →

04 · Engine

Small, explicit, no dependencies.

An original engine written for the web, not a port. It implements the workflow (operator families, wired networks, expression-driven parameters, a live cook loop) on browser GPUs.

Zero runtime dependencies
The app is engine, editor and shaders, nothing else: 260 KB of JavaScript, 76 KB gzipped.
Two GPU backends
WebGL2 by default, WebGPU with ?backend=webgpu, both behind one backend-agnostic pass contract. 2D is at parity; the 3D scene pass is WebGL2 today.
One dependency rule
Imports flow downward only, and core imports nothing. Operators register through the public registerOp API.
Embeddable
mountEditor(el, options) puts the framework-free editor in any page. Hosts load projects and drive expressions over postMessage.
A versioned file format
.webtoe.json carries a version and a migration chain; unknown operator types degrade to family stubs instead of failing.
WASM where it pays
The NDI pixel conversion runs in a 1 KB WebAssembly kernel with a JavaScript fallback. WASM is adopted only on a measured win.
apps/web ──▶ @webtoe/editor ──▶ @webtoe/ops ──┐
                       │            │          ├──▶ @webtoe/core
                       ├──▶ @webtoe/gpu ───────┤
                       └──▶ @webtoe/io ────────┘
npm workspaces, imports flowing one way. Architecture →

05 · Engine ↔ show

WebToe is the engine. open-audiovisual is the show.

open-audiovisual is the sister project: a web-native framework for audiovisual performance, with MIDI, chord and pose inputs, a signal-to-parameter mapping layer, a timeline with scenes and cues, and a backstage monitor.

Its @openav/world-webtoe adapter embeds this editor in an iframe, reads the show’s resolved parameters every frame and posts the ones that changed into the patch. Inside the network they are plain numbers: ext('energy') in any expression.

host page
// the host page drives the patch; expressions read ext('energy')
frame.contentWindow.postMessage(
  { type: 'webtoe:ext', values: { energy: 0.8 } }, '*');

// swap the project
frame.contentWindow.postMessage(
  { type: 'webtoe:load', url: 'https://example.com/patch.webtoe.json' }, '*');

06 · Start

Three ways in.

  1. In the browser

    Nothing to install. Open the editor, load one of the ten examples from the toolbar, or drop a project file on the page.

    Open the editor

  2. On your machine

    Serves the editor and the import bridge on 127.0.0.1 and opens it. Needs Node 20 or newer.

    terminal
    npx webtoe        # → http://127.0.0.1:9881/app/
  3. From source

    The monorepo: engine, operators, both GPU backends, editor, importer, bridges. npm run check runs the type check and the test suite.

    terminal
    git clone https://github.com/frank890417/WebToe.git
    cd WebToe && npm install
    npm run dev       # → http://localhost:8643/app/

Documentation