Hardware integration
OSC over WiFi / Ethernet — wire ESP32 or Arduino devices into Grafer's Live mode to drive the conductor from buttons and sensors, or to control lights, motors, and robotics from cell graphs.
How OSC flows in Grafer
┌─────────────────────────┐
│ Grafer app │
sensors ─┼──► OSC In (UDP listen) │ Phase 3: triggers cell
pedals │ port: 7400 │ transitions / pause / etc.
ESP32 ─┼──► │ Phase 5: fires reactive
│ │ osc.in → out.chord
│ │
│ score.osc-send ──────┼──► robotics / synths
│ (per-cell node) │ Phase 1
└─────────────────────────┘
Two independent flows. Sending OSC into Grafer is the Live page's OSC input feature. Receiving OSC from Grafer is the score.osc-send graph node — drop it inside any cell and wire a Voice into it.
You don't need both — pick whichever side(s) your project needs.
Library
For Arduino-flavoured platforms (ESP32, ESP8266, Uno + Ethernet shield, Teensy, etc.), use the CNMAT OSC library — the de-facto standard, maintained, supports both sending and receiving:
- Library Manager → search "OSC" → install "OSC by CNMAT".
- Or from Git: github.com/CNMAT/OSC
All four sketches below use this library. If you're on a non-Arduino platform (Raspberry Pi, MaxMSP, SuperCollider, Pd, TouchOSC), use whatever native OSC support that platform has — Grafer's side is plain OSC over UDP, no proprietary extras.
Network setup
1. Same network
Hardware and the Grafer host (your computer or VPS) must reach each other over IP. The simplest setup is both on the same WiFi router, or both on the same wired LAN. If your router has client isolation enabled (common on guest networks), disable it for the network you're using.
2. Find Grafer's IP
On the machine running Grafer:
| OS | Command |
|---|---|
| macOS | ipconfig getifaddr en0 (or en1) |
| Linux | hostname -I |
| Windows | ipconfig (look for IPv4 Address) |
Or on the Live → OSC input panel, the address you'll see (<host>:7400) shows the host as the browser's hostname. If that's localhost you're testing on the same machine; otherwise it's the LAN/VPS hostname your hardware should target.
3. Find the OSC In port
Open Grafer's Live page → expand the OSC input panel → click Listen. The panel shows the allocated port (default range 7400-7463). Each user gets their own port — share that exact number with your hardware. The port persists for ~5 minutes after you close the panel, so a quick page reload doesn't re-allocate.
4. Pick a port for hardware to listen on
For Grafer → hardware (the score.osc-send direction), the hardware chooses. Anything in the user-port range works; common choices are 9000, 9001, etc. Configure that port both in your sketch (Udp.begin(9000)) and in the cell's score.osc-send node (port: 9000, host: <ESP32's IP>).
5. Firewall
Most home routers don't block UDP between LAN clients. Local OS firewalls (Windows Defender, macOS Application Firewall, ufw) might — allow UDP on the chosen port if traffic isn't getting through.
Hardware → Grafer: triggering the conductor
Add OSC mappings in Live → OSC input → Score-level mappings:
| Address | Action |
|---|---|
/grafer/next | next |
/grafer/skip | skip cell |
/grafer/pause | pause |
/grafer/resume | resume |
/grafer/panic | stop |
Or set per-edge triggerAddress on a rhizome edge — that edge wins the next boundary when the matching packet arrives. Or wire osc.in → out.chord inside a cell graph — the chord layers in mid-cell when the matching packet arrives.
On the hardware, just send a UDP packet:
OSCMessage msg("/grafer/next");
Udp.beginPacket(graferHost, graferPort);
msg.send(Udp);
Udp.endPacket();
msg.empty();
Grafer → Hardware: driving lights / motors / robotics
Inside any cell graph, drop a score.osc-send node:
- host: the ESP32's IP (from your serial monitor on first boot)
- port: whatever port the ESP32 listens on (
9000in the examples) - address: any path you like, e.g.
/lights/flash
Wire a Voice into the node and Grafer fires one OSC message per note:
<address> <onset_seconds> <duration_seconds> <pitch_midicents> <velocity_0..1>
Your sketch handles each address however you want — flash an LED, spin a motor, trigger a sample.
Example sketches
Each sketch is paste-credentials-and-run. Right-click each link to download, or open it to read the source in your browser.
Single button: short press fires /grafer/next, long press fires /grafer/skip. Debounced, reconnect-tolerant.
LED handlers for /lights/flash (with note duration), /lights/level (PWM brightness), /motor/spin.
Three buttons (next / skip / pause-toggle) + analog pot at 50 Hz with dead-band + the same three output handlers. The realistic foot-controller skeleton.
Wired version with DHCP + lease maintenance for long-running sessions. For when WiFi isn't reliable enough.
Latency
Round-trip latency on a typical home network sits around 5-15 ms for a single packet — well under the threshold a performer would notice as "delayed". Two things to watch:
- WiFi congestion: noisy 2.4GHz networks (lots of devices, microwave, bluetooth) can spike to 100ms+ occasionally. For a high-stakes performance, prefer 5GHz WiFi or wired Ethernet.
- Bursts: a sensor that spams 1000 packets/second will drop some. Throttle on the hardware side (
if (millis() - lastSent > 20)). For continuous sensor data, sample at 50-100 Hz.
Troubleshooting
Nothing happens when I press the button.
- Check the serial monitor — is the ESP32 actually firing? It should log
Sent /grafer/nexton every press. - Wrong IP. Verify the Grafer host's IP again — did your laptop's IP change? Re-check with
ipconfig/ifconfig. - Wrong port. Re-check the OSC input panel — did the listener restart and pick a different port? It's stable while open but reallocates after a reload past the 5-minute grace.
- Firewall blocking UDP on that port. Test with
oscsendfrom the Grafer host's terminal first:
If# macOS / Linux (from `liblo` or `oscsend` package) oscsend localhost 7400 /grafer/next # Windows (PowerShell — install: pip install python-osc) python -c "from pythonosc.udp_client import SimpleUDPClient; SimpleUDPClient('127.0.0.1', 7400).send_message('/grafer/next', [])"oscsendworks but the ESP32 doesn't, it's a network/firewall issue, not Grafer.
Recent packets monitor shows my packets but the conductor isn't reacting.
Either the address doesn't match a mapping/edge, or the conductor isn't running. Check:
- Is the address spelled exactly the same on both sides?
/Grafer/next≠/grafer/next(case-sensitive). - Is the conductor actually playing (Stop button visible, not Play)?
- Score-level mappings fire at cell boundaries, not mid-cell — there's a delay until the current cell finishes. Use a per-edge
triggerAddressor an in-cellosc.infor instant response.
Receiver sketch sees no packets from Grafer.
- Wrong host IP in the
score.osc-sendnode — has your ESP32's IP changed? Print it to serial on every boot. - The cell isn't actually playing (the node only fires when the cell evaluates as part of the playing voice).
- Check the node's "Last send" status field — Grafer logs the most recent send result there.
OSC input panel says "Stream not reaching tab" / mobile says "composer's tab not subscribed".
The composer's browser can't keep an open Server-Sent Events connection to the API server. Almost always a reverse-proxy / hosting-layer issue. Common fixes per platform:
| Platform | Fix |
|---|---|
| IIS + ARR | In web.config on the <proxy> element: responseBufferLimit="0" + timeout="01:00:00". ARR buffers responses by default, which deadlocks SSE. |
| nginx | In the /api/ location: proxy_buffering off; proxy_cache off; proxy_read_timeout 1h; |
| Cloudflare | Enable WebSockets on the zone (Network → WebSockets). Cloudflare's SSE pass-through rides the same flag. |
| Apache (mod_proxy) | Add SetEnv proxy-sendchunked and ProxyTimeout 3600 for the API location. |
You can verify the SSE endpoint directly with curl: curl -N https://your-host/api/osc/in/stream?token=YOUR_JWT. A working stream prints retry: 3000 immediately and a hello event within ~100ms. If curl hangs with no output for >1s, the proxy is buffering.
Going further
- Continuous values (sensor amplitude, knob position): send floats with each packet —
OSCMessage("/sensor/x").add(0.42f). Grafer's Recent packets monitor shows the args; future graph nodes will be able to read these as live-modulation sources. - MIDI footswitches: most commercial controllers (FCB1010, etc.) don't speak OSC natively — bridge through a small ESP32 sketch that reads MIDI in and forwards as OSC.
- Multiple devices: each can target the same
/grafer/...address with no conflicts. Grafer doesn't care which device sent a packet.