OSC & MIDI in TouchDesigner
Receive and send OSC and MIDI messages in TouchDesigner to connect mobile controllers, DAWs, and hardware instruments to your visual network.
Open Sound Control (OSC) and MIDI are the two dominant protocols for connecting performance controllers, DAWs, and other software to TouchDesigner. OSC travels over UDP or TCP on your network; MIDI travels over USB or virtual ports. Both have dedicated operators in TD, and knowing their quirks saves you hours of debugging on stage.
OSC In DAT
The OSC In DAT listens on a UDP port and appends incoming messages as rows in its table. Configure the port in the parameters (default 9000) and leave Local Address blank to listen on all interfaces.
Each incoming message produces a row with columns: address, args, timeStamp, and peer. For real-time response, script against it in a DAT Execute:
# DAT Execute DAT — set "DAT" to oscin1, Table Change callback active
def onTableChange(dat):
# dat is the OSC In DAT; last row is the newest message
if dat.numRows == 0:
return
row = dat.numRows - 1
address = dat[row, 'address'].val
args = dat[row, 'args'].val # space-separated string
if address == '/synth/filter':
try:
freq = float(args.split()[0])
op('filter_chop').par.cutoff = freq
except (IndexError, ValueError):
pass
return
For a dedicated callback-per-message approach, use the OSC In DAT’s Python callback directly. Open the DAT’s Callbacks tab:
def onReceiveOSC(dat, rowIndex, message, bytes, timeStamp, address, args, peer):
# address is the OSC path string, e.g. '/1/fader1'
# args is a list of typed Python values (float, int, string)
if address == '/1/fader1':
op('noise1').par.amplitude = args[0]
elif address == '/1/toggle1':
op('feedback1').par.resetpulse.pulse() if args[0] == 1 else None
return
OSC Out DAT
The OSC Out DAT sends messages. Set its network address and port. To send a message, execute a Python script that calls sendOSC() on the operator:
# Send a single OSC message
osc_out = op('oscout1')
osc_out.sendOSC('/visual/speed', [float(op('lfo1')['chan1'][0])])
# Send an OSC bundle (multiple messages with the same timestamp)
bundle = [
('/colour/r', [0.8]),
('/colour/g', [0.2]),
('/colour/b', [0.5]),
]
for address, args in bundle:
osc_out.sendOSC(address, args)
MIDI In CHOP
The MIDI In CHOP receives MIDI data and exposes it as continuously updating channels. In its parameters, select your MIDI device from the Device dropdown. Common channel outputs:
- Note on/off: one channel per note, named
note_60,note_61, etc. (middle C = 60). Value is velocity (0–127, normalized to 0–1). - Control Change: channels named
cc_1,cc_7,cc_74, etc. - Pitch Bend: channel named
pitchbend, range –1 to 1. - Channel Pressure / Aftertouch: channel named
aftertouch.
Wire the MIDI In CHOP into a Select CHOP to isolate specific channels, then into a Math CHOP to rescale the 0–1 range to whatever your parameter needs.
MIDI In Map COMP
For a visual, no-code approach to MIDI mapping, use the MIDI In Map COMP. It presents a patchbay interface where you drag MIDI channels to operator parameters. Under the hood it generates the same Select CHOP + Export chains you would build manually, but it’s faster for live setups. Access it via Op Palette > COMP > MIDI In Map.
MIDI Out CHOP
The MIDI Out CHOP sends MIDI. Connect a CHOP driving note numbers and velocities to its inputs. Set the Device and channel. To trigger a specific note from Python:
midi_out = op('midiout1')
# Arguments: note number, velocity, channel, duration in samples
midi_out.sendNote(60, 100, 1, 100)
# Send CC
midi_out.sendCC(74, int(op('lfo1')['chan1'][0] * 127), 1)
Network Setup: TouchOSC over WiFi
TouchOSC (Hexler) is a mobile app for designing custom OSC control surfaces. Setup:
- Put your computer and phone on the same WiFi network.
- In TouchDesigner, add an OSC In DAT, set Port to
8000. - Find your computer’s local IP address (run
ipconfigon Windows orifconfigon macOS/Linux in a terminal). - In TouchOSC (the editor or the app), set Host to that IP address and Port to
8000. - Hit play in TouchOSC — messages arrive in the OSC In DAT immediately.
Latency over WiFi is typically 5–20ms, acceptable for visual control but not for musical tempo-sync. For lower latency, use a USB tether (Personal Hotspot on iOS) or a direct Ethernet adapter.
Connecting Ableton Live via Virtual MIDI
On macOS, create a virtual MIDI port in Audio MIDI Setup (IAC Driver). On Windows, install loopMIDI (free). Then:
- In Ableton, assign an instrument or clip to output to the virtual port.
- In TD, set the MIDI In CHOP’s Device to that virtual port.
- MIDI notes from Ableton’s clips now appear as MIDI In CHOP channels in real time.
For timecode sync (Ableton → TD), use MIDI Clock or MTC — both arrive through the same MIDI In CHOP. The Timecode CHOP can decode MTC.
Full Example: MIDI Keyboard → Visual Bursts
The goal: each keypress on a MIDI keyboard triggers a particle burst, with the note pitch controlling the burst direction and velocity controlling size.
Network layout:
midi_keyboard_in (MIDI In CHOP)
└─→ select_notes (Select CHOP) ── channels: note_36 to note_96
└─→ trigger_chop (Trigger CHOP) ── Threshold: 0.1
└─→ chop_execute (CHOP Execute DAT)
# CHOP Execute DAT attached to trigger_chop
def onOffToOn(channel, sampleIndex, val, prev):
# channel.name is e.g. 'note_60'
note_num = int(channel.name.split('_')[1])
# Map note (36–96) to angle in degrees
angle = (note_num - 36) / 60.0 * 360.0
# Map velocity (val 0–1) to particle count
count = int(val * 500)
particles = op('particlesop1')
# Set birth direction
angle_rad = angle * 0.01745329 # degrees to radians
particles.par.birthvx = math.cos(angle_rad) * 50
particles.par.birthvy = math.sin(angle_rad) * 50
particles.par.birthvz = 0
# Pulse the birth rate up then let it decay
op('birth_const').par.value0 = count
return
def onValueChange(channel, sampleIndex, val, prev):
return
Add a Lag CHOP after birth_const with a decay of 0.5 seconds so each burst fades quickly. The Particle SOP uses birth_const’s output as its birth rate.
Latency Considerations
UDP OSC has no acknowledgement — packets can arrive out of order or not at all on congested networks. For critical triggers (e.g. syncing to a music downbeat), use TCP OSC (OSC In DAT > Protocol: TCP) or MIDI, which has tighter timing guarantees over USB. The MIDI In CHOP also has a small internal buffer; increase it in the parameters if you are sending bursts of many simultaneous notes.