Godstrument/workers/world_iss.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

86 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""ISS live position -> control signals. The space station's motion drifts the timbre.
It circles Earth every ~90 min at ~7.66 km/s; lat/lon sweep continuously while the
computed ground speed wobbles gently around orbital velocity -> a slow, living modulator.
"""
import argparse
import json
import math
import time
import urllib.request
import urllib.error
from pythonosc.udp_client import SimpleUDPClient
URL = "http://api.open-notify.org/iss-now.json"
USER_AGENT = "godstrument-iss/1.0"
EARTH_RADIUS_M = 6371000.0
def haversine_m(lat1, lon1, lat2, lon2):
"""Great-circle ground distance in meters between two lat/lon points."""
p1 = math.radians(lat1)
p2 = math.radians(lat2)
dphi = math.radians(lat2 - lat1)
dlam = math.radians(lon2 - lon1)
a = math.sin(dphi / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dlam / 2) ** 2
return 2 * EARTH_RADIUS_M * math.asin(math.sqrt(a))
def fetch():
req = urllib.request.Request(URL, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode("utf-8"))
pos = data["iss_position"]
return float(pos["latitude"]), float(pos["longitude"])
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="127.0.0.1")
ap.add_argument("--port", type=int, default=9000)
ap.add_argument("--interval", type=float, default=5.0)
args = ap.parse_args()
client = SimpleUDPClient(args.host, args.port)
print(f"[world_iss] emitting /gs/iss/lat,lon,vel every {args.interval:g}s")
prev = None # (lat, lon)
prev_t = None # monotonic timestamp of prev fix
smooth_vel = None # exponentially smoothed ground speed (m/s)
backoff = args.interval
while True:
try:
lat, lon = fetch()
now = time.monotonic()
client.send_message("/gs/iss/lat", float(lat))
client.send_message("/gs/iss/lon", float(lon))
if prev is not None and prev_t is not None:
dt = now - prev_t
if dt > 0:
dist = haversine_m(prev[0], prev[1], lat, lon)
vel = dist / dt # m/s ground speed
if smooth_vel is None:
smooth_vel = vel
else:
smooth_vel = 0.6 * smooth_vel + 0.4 * vel
client.send_message("/gs/iss/vel", float(smooth_vel))
prev = (lat, lon)
prev_t = now
backoff = args.interval
except (urllib.error.URLError, OSError, ValueError, KeyError, json.JSONDecodeError) as e:
print(f"[world_iss] warning: {e}; retrying in {backoff:g}s")
time.sleep(backoff)
backoff = min(backoff * 2, 60.0)
continue
time.sleep(args.interval)
if __name__ == "__main__":
main()