Godstrument/workers/sim_esp32.py
monsterrobotparty 8deb65b4fe Godstrument: a multi-modal sensor-fusion instrument
Everything — your hand, the room, a satellite, the markets, the sky — becomes an
OSC control signal fused through a modulation matrix. Includes:
- hub + normalize (One-Euro) + matrix (curves/gates/quantize) + transform (tweaks/groups)
- 20+ workers: sim sensors, 8 keyless world feeds, ephemeris/almanac/clock (computed),
  time-warp replay (quakes/weather/db), and the dealgod market warehouse feed
- planetary orbital LFOs, SQLite recorder, city targeting
- live browser console: mute, group macros, drag-to-patch, inspector, Web MIDI

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 02:13:05 +10:00

96 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Simulated ESP32 sensor rig: a virtual hand gliding over a VL53L5CX ToF matrix
plus a BH1750 ambient-light cell. It is musical because the smooth Lissajous
motion gives the visualizer a living, breathing performer to react to before any
real hardware exists — hand position and light become continuous playable knobs.
"""
import argparse
import math
import random
import time
from pythonosc.udp_client import SimpleUDPClient
NAME = "sim_esp32"
RATE_HZ = 60.0
DT = 1.0 / RATE_HZ
def clamp(v, lo, hi):
return lo if v < lo else hi if v > hi else v
def main():
ap = argparse.ArgumentParser(description="Simulate the future ESP32 ToF + light sensor rig.")
ap.add_argument("--host", default="127.0.0.1")
ap.add_argument("--port", type=int, default=9000)
args = ap.parse_args()
client = SimpleUDPClient(args.host, args.port)
# A fixed phase offset so multiple instances don't march in lockstep.
seed = random.random() * 1000.0
t0 = time.time()
# Schedule the first simulated flash 8-20s out.
next_flash = t0 + random.uniform(8.0, 20.0)
print(f"[{NAME}] emitting /gs/tof/*, /gs/light/* every {DT:.4f}s")
while True:
try:
now = time.time()
t = (now - t0) + seed
# --- Hand X/Y: sums of a few sines at irrational-ish rates + slow drift.
# Lissajous-feel motion, never white noise. Kept smoothly inside 0..1.
cx = (
0.50
+ 0.30 * math.sin(t * 0.31700)
+ 0.12 * math.sin(t * 0.71300 + 1.3)
+ 0.06 * math.sin(t * 1.61803 + 2.1) # golden-ratio wobble
)
cy = (
0.50
+ 0.28 * math.sin(t * 0.27100 + 0.7)
+ 0.13 * math.sin(t * 0.61800 + 2.4) # 1/phi
+ 0.05 * math.sin(t * 1.41421 + 0.2) # sqrt(2)
)
# --- Proximity: slow breathing in/out, hand drifting toward/away.
near = (
0.50
+ 0.35 * math.sin(t * 0.19000)
+ 0.10 * math.sin(t * 0.53000 + 1.1)
)
# --- Ambient light: drifts in the hundreds of lux, with slow occasional dips.
base = 500.0 + 250.0 * math.sin(t * 0.041 + 0.5) + 90.0 * math.sin(t * 0.113 + 2.0)
# A slow, mostly-shallow dip that deepens rarely (raising to a power keeps it near 1
# most of the time and only occasionally sinks toward a shadow).
dip = 0.55 + 0.45 * math.sin(t * 0.023 + 1.7)
dip = clamp(dip, 0.0, 1.0) ** 3 # mostly bright, sometimes dark
lux = clamp(base * (0.35 + 0.65 * dip), 0.0, 1000.0)
client.send_message("/gs/tof/cx", clamp(cx, 0.0, 1.0))
client.send_message("/gs/tof/cy", clamp(cy, 0.0, 1.0))
client.send_message("/gs/tof/near", clamp(near, 0.0, 1.0))
client.send_message("/gs/light/lux", lux)
# --- Occasional strobe/camera flash event.
if now >= next_flash:
client.send_message("/gs/light/flash/event", 1.0)
next_flash = now + random.uniform(8.0, 20.0)
except OSError as e:
# Transient network/socket hiccup: warn, back off briefly, keep going forever.
print(f"[{NAME}] send failed ({e}); retrying")
time.sleep(0.5)
continue
time.sleep(DT)
if __name__ == "__main__":
main()