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>
86 lines
2.9 KiB
Python
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()
|