Timeline & Animation in TouchDesigner
Create keyframe animations with the Animation COMP, sequence scenes with the Segment COMP, sync to external timecode, and build crossfading scene sequencers.
Real-time production often requires more than live-driven visuals — you need pre-programmed cues, keyframed movements, and synchronised playback. TouchDesigner provides several complementary systems: the Animation COMP for keyframe editing, the Segment COMP for sequential cue triggering, the Time COMP for local time control, and Timecode CHOP for LTC/MTC sync with external sources.
The Animation COMP
The Animation COMP is TouchDesigner’s built-in keyframe editor. Add one from the Op Palette. Double-click it to open the animation editor — a timeline with channel lanes, a curve editor, and a scrubber.
Creating Channels and Keyframes
In the animation editor, click the + button (or right-click the channel list) to add a new channel. Name it to match the parameter you intend to animate, e.g. noise_amplitude, camera_tz, colour_hue.
Add keyframes by:
- Right-clicking on the timeline at a specific frame and selecting “Add Key”
- Moving the playhead to a frame and pressing
K
Each key has a value and an interpolation handle. Drag the bezier handles to control easing. Available interpolation types per-key: Constant, Linear, Ease In, Ease Out, Ease In-Out, Cubic, and Custom.
The Animation COMP produces CHOP output — one channel per animation lane, one sample per frame of the current timeline.
Exporting Animation Channels to Parameters
Wire the Animation COMP’s output into a Null CHOP. Then, right-click any operator parameter in your network → Export → select the null CHOP and the matching channel name. The parameter is now driven by the keyframe animation.
For a camera dolly move:
animation1 (Animation COMP)
└─→ null_anim (Null CHOP)
├─→ Export: camera1.par.tz ← channel 'camera_tz'
└─→ Export: camera1.par.ty ← channel 'camera_ty'
Alternatively, use a Python expression in the parameter field directly:
# Camera COMP tz parameter expression
op('null_anim')['camera_tz'][0]
Playback Control
The Animation COMP’s playback is controlled by the Time COMP it belongs to. By default it follows the global timeline. To play it independently:
- Set the Animation COMP’s Timeline parameter to a specific Time COMP path.
- Control play/pause/loop from that Time COMP’s parameters or Python:
# Python — play and stop a local Time COMP
time = op('time1')
time.par.play = 1 # play
time.par.play = 0 # pause
time.par.frame = 0 # seek to start
Segment COMP
The Segment COMP plays back content in named sequential segments, each triggered on cue. It is the primary tool for theatrical cueing: each cue is a row in a DAT, and pressing Go (or a trigger) advances to the next cue.
Add a Segment COMP from the palette. In its Cue List (a Table DAT), each row defines one cue:
| Name | Duration | TransitionIn | TransitionOut |
|---|---|---|---|
| intro | 60 | fade | fade |
| scene_A | 120 | cut | wipe |
| scene_B | 90 | dissolve | dissolve |
| outro | 45 | fade | fade |
Each segment can play a video, render from a Container COMP, or drive any content you assign. The Segment COMP provides channels indicating the current active segment, time within segment, and transition progress.
To advance to the next cue from Python:
op('segment1').par.gonext.pulse()
# Or jump to a specific cue by name
op('segment1').par.cuename = 'scene_B'
op('segment1').par.go.pulse()
Time COMP: Local Time Control
Every Container and Base COMP in TouchDesigner can have its own Time COMP attached, giving it an independent local timeline. Parameters on the Time COMP:
- Rate — playback speed multiplier (1.0 = real-time, 0.5 = half speed, -1.0 = reverse)
- Start/End Frame — loop region
- Loop — whether playback loops
- Play — run/pause toggle
This lets individual scenes run at their own tempo regardless of the global frame rate or other scenes:
# Gradually slow down a scene's local time
def onFrameStart(frame):
t = op('scene_a/local_time')
current_rate = t.par.rate.val
t.par.rate = max(0.0, current_rate - 0.01)
return
Animating Between States: Blend CHOP
The Blend CHOP interpolates between multiple CHOP inputs based on a weight. Use it for smooth state transitions:
state_idle (Null CHOP — baseline values)
state_peak (Null CHOP — peak-energy values)
└─→ blend1 (Blend CHOP, Weight driven by audio level)
└─→ exports to visual parameters
The Blend CHOP’s Weight parameter (0.0 → first input, 1.0 → last input) can be animated, exported, or driven by any CHOP channel. Combine with a Lag CHOP (Lag value 0.3–0.8 seconds) after the driving signal to get smooth, physics-like interpolation.
# Trigger a blend transition via Python
def trigger_transition(target_state_index, duration_seconds):
blend = op('blend1')
current = blend.par.weight.val
target = float(target_state_index)
# Animate from current to target over duration
blend.par.weight.expr = '' # clear any expression
blend.par.weight = current
# Use a CHOP animation to drive it (cleaner approach)
op('transition_const').par.value0 = current
op('transition_target').par.value0 = target
op('transition_lag').par.lag = duration_seconds
External Timecode: LTC via Audio Device
Linear Timecode (LTC) arrives as an audio signal — typically on a spare audio input from a timecode generator or DAW. The workflow in TD:
- Audio Device In CHOP — receives the LTC audio on a specific input channel.
- Timecode CHOP — decodes LTC from the audio channel into hours, minutes, seconds, frames channels.
- Use the decoded frame counter to control playback.
audio_in (Audio Device In CHOP, Input 1 = timecode source)
└─→ timecode1 (Timecode CHOP)
├─→ hours channel
├─→ minutes channel
├─→ seconds channel
└─→ frames channel
Convert to an absolute frame count for seeking:
# Execute DAT — sync TD playback to incoming LTC
def onFrameStart(frame):
tc = op('timecode1')
h = tc['hours'][0]
m = tc['minutes'][0]
s = tc['seconds'][0]
f = tc['frames'][0]
# Assume 30fps project
abs_frame = int((h * 3600 + m * 60 + s) * 30 + f)
# Seek the animation comp to match
op('animation1').par.frame = abs_frame
return
MIDI Timecode (MTC) Sync
MIDI Timecode arrives via the standard MIDI In CHOP. Select your MIDI device; MTC data is automatically decoded into the channel mtc_frame (absolute frame count in the session’s frame rate).
# Execute DAT — sync to MTC
def onFrameStart(frame):
mtc_frame = op('midiIn1')['mtc_frame'][0]
if mtc_frame > 0:
op('animation1').par.frame = int(mtc_frame)
return
Full Example: 4-Scene Sequencer with Crossfades
Goal: 4 scenes (Container COMPs), advanced by keyboard, crossfaded with a 4-second Blend TOP transition.
Network layout:
scene_intro (Container COMP → out_top) ──┐
scene_main (Container COMP → out_top) ──┼─→ blend_top (Blend TOP, Mix driven by transition_lag)
scene_climax (Container COMP → out_top) ──┤ └─→ window1 (Window COMP)
scene_outro (Container COMP → out_top) ──┘
Python controller (Script DAT, triggered by keyboard Execute DAT):
SCENES = ['scene_intro', 'scene_main', 'scene_climax', 'scene_outro']
TRANSITION_FRAMES = 4 * 60 # 4 seconds at 60fps
current_scene_idx = 0
def advance_scene():
global current_scene_idx
next_idx = (current_scene_idx + 1) % len(SCENES)
crossfade_to(next_idx)
current_scene_idx = next_idx
def crossfade_to(idx):
# Tell the Blend TOP which input to transition toward
# Blend TOP input order matches SCENES list
blend = op('blend_top')
# Set input weights: smoothly move weight to idx
for i, _ in enumerate(SCENES):
blend.par[f'weight{i}'] = 1.0 if i == idx else 0.0
# Drive the mix with a lag (4 second lag = 4-second crossfade)
op('blend_lag').par.lag = TRANSITION_FRAMES / 60.0
def go_to_scene(name):
global current_scene_idx
if name in SCENES:
idx = SCENES.index(name)
crossfade_to(idx)
current_scene_idx = idx
Keyboard Execute DAT:
def onKey(key, character, alt, lshift, rshift, lctrl, rctrl, lAlt, rAlt, state):
if state == 'press':
if character == ' ':
run('advance_scene()', fromOP=op('sequencer_script'))
elif character in '1234':
idx = int(character) - 1
run(f'go_to_scene(SCENES[{idx}])', fromOP=op('sequencer_script'))
return
The Blend TOP with four inputs and a Lag CHOP-driven mix parameter handles the visual crossfade. Each Container COMP runs its own Time COMP independently, so the scenes can be in mid-playback at any point when they come into focus — no hard jumps.