← All docs
Ideas

OSC inbound listener (osc-in node)

> Proposal for v0.5 backlog item #3.

Why we need it

Today XOSC has two source kinds:

  • input — local device (kbd / MIDI / gamepad / serial).
  • resolume-in — Resolume's WebSocket parameter feedback.
There is no way to receive OSC packets from arbitrary external senders. That misses several common use cases:

1. Stream Deck via Companion sending OSC into XOSC.
2. Reaper / Ableton / OSC-capable DAW sending track/clip state.
3. TouchOSC / TouchDesigner / Lemur custom layouts sending control values.
4. A second XOSC instance on another laptop sending OSC across LAN.
5. Resolume's OSC out (different from its WS) — some users prefer OSC over WS.

Shape

A new node kind:

export interface OscInSpec {
  port: number;            // local UDP port to bind
  address: string;         // OSC address pattern. Supports wildcards ( and ?).
  // Optional bind interface; default 0.0.0.0.
  bindHost?: string;
}

export interface OscInNodeData {
id: string;
kind: "osc-in";
label: string;
color: string;
spec: OscInSpec;
// The OSC argument we treat as the value for continuous routing.
// Default: index 0, only if it's i/f.
valueArgIndex?: number;
transform?: InputTransform;
position: { x: number; y: number };
}

Source-handle on the right; emits onto inputBus when a matching packet arrives.

Main process

A single UDP socket per (bindHost, port) is shared across all osc-in nodes targeting it. The bridge module owns the sockets; it parses every datagram with node-osc's parser, then dispatches to renderer via a new osc:incoming IPC channel:

interface OscIncomingEvent {
  bindHost: string;
  port: number;
  remote: { address: string; port: number };
  address: string;
  args: { type: string; value: number | string | boolean | null }[];
  ts: number;
}

Renderer's useOscInputs.ts (new) subscribes once, looks up matching osc-in nodes by (port, address-pattern-match), and emits on the inputBus with a synthetic descriptor.

Files

  • New: electron/osc-listener.ts (UDP listener, address-pattern matcher).
  • New IPC handlers in electron/main.ts registration: osc-in:open, osc-in:close, plus the broadcast event.
  • Update electron/preload.ts + src/types/api.ts.
  • New: src/features/inputs/useOscInputs.ts (renderer-side reconciler — opens listeners for any present osc-in node, closes when removed).
  • New node + inspector: src/features/mapping/OscInNode.tsx, src/features/inspector/OscInInspector.tsx.
  • src/store/store.ts — add OscInNodeData to AnyNode; add addOscInNode action; extend isSourceNode.
  • src/components/Topbar.tsx — new "OSC In" button.
  • src/features/mapping/GraphCanvas.tsx — register node type.

Address pattern matching

OSC has its own pattern syntax (, ?, [], {}). Implement a simple matcher; we don't need to support the full grammar in v1, but * and ? cover most users. Library option: osc npm package has a matcher; or we hand-roll one. Hand-rolling is ~30 LoC.

Continuous-value detection

If the matched packet's first i/f argument exists, set value = arg0Float (clamped 0..1; if the arg is outside 0..1, the node's transform.scale is the user's hook to remap). If no numeric arg, treat as discrete pulse (no value on the event).

This mirrors the heuristic the OSC out side already uses — symmetric.

Echo-suppression interplay

Once we have osc-in, the same loop hazard from architecture-limits.md Loop 1 applies to OSC. If a user wires osc-in listening on /x to an OSC out sending to /x on the same target, and that target is ourselves… infinite loop.

The lineage-cap from v0.5 backlog #1 catches it. We can also implement an opt-in echo guard on osc-in (same shape as the resolume-in echo guard).

Air-gap considerations

Opening a UDP listener on 0.0.0.0 exposes XOSC to the LAN. We should:

  • Default bindHost to 127.0.0.1, so the listener is loopback-only by default.
  • Surface the bind host in the inspector with a clear "loopback only" / "LAN visible" indicator.
  • Document the LAN-visible mode in the main troubleshooting doc.

Default port

Pick 9000 as the default. 8000 and 7000 are common but reserved for OSC out traffic; XOSC defaults its OSC-out node to 7000. Using 9000 for inbound avoids confusion.

Estimate

M (≤half day). Most of the work is plumbing; the UDP listener is ~50 LoC.