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

109 lines
4.1 KiB
Python

"""
world_ephemeris.py — as above, so below. The sky itself, as control signals.
Computes real planetary positions with the Swiss Ephemeris (Moshier mode — NO
data files, no downloads, good from 3000 BC to 3000 AD). Nothing is stored or
fetched; the whole sky is generated on demand, like world_sky.py does for the
sun. Astrology data is ideal for an instrument because it's inherently cyclic at
wildly different rates — the Moon wheels monthly, the Sun yearly, Saturn every 29
years — so you get a bank of natural LFOs spanning seconds to a lifetime.
Emits (all normalized-friendly):
/gs/astro/moon moon phase, 0 (new) .. 1 (full)
/gs/astro/moon_lon Moon ecliptic longitude 0..360 (fast wheel ~13deg/day)
/gs/astro/sun_lon Sun longitude 0..360 (the seasons)
/gs/astro/<planet> ecliptic longitude 0..360 for mercury..saturn (slow LFOs)
/gs/astro/tension closeness to HARD aspects (square/opposition) — dissonance
/gs/astro/harmony closeness to SOFT aspects (trine/sextile) — consonance
/gs/astro/retro fraction of planets retrograde 0..1
/gs/astro/mercury_retro 1 if Mercury is retrograde else 0
"""
from __future__ import annotations
import argparse
import datetime
import itertools
import math
import time
import swisseph as swe
from pythonosc.udp_client import SimpleUDPClient
FLG = swe.FLG_MOSEPH | swe.FLG_SPEED
BODIES = {
"sun": swe.SUN, "moon": swe.MOON, "mercury": swe.MERCURY, "venus": swe.VENUS,
"mars": swe.MARS, "jupiter": swe.JUPITER, "saturn": swe.SATURN,
"uranus": swe.URANUS, "neptune": swe.NEPTUNE, "pluto": swe.PLUTO,
}
# which longitudes are worth emitting as sources (the rest still feed aspects)
EMIT_LON = ["moon", "sun", "mercury", "venus", "mars", "jupiter", "saturn"]
HARD = [(0, 8), (180, 8), (90, 7)] # conjunction/opposition/square + orbs
SOFT = [(120, 7), (60, 5)] # trine/sextile
def sky(jd):
lon, speed = {}, {}
for name, body in BODIES.items():
xx, _ = swe.calc_ut(jd, body, FLG)
lon[name], speed[name] = xx[0], xx[3]
return lon, speed
def aspects(lon):
hard = soft = 0.0
for a, b in itertools.combinations(lon, 2):
d = abs(lon[a] - lon[b]) % 360
d = min(d, 360 - d)
for ang, orb in HARD:
if abs(d - ang) < orb:
hard += 1 - abs(d - ang) / orb
for ang, orb in SOFT:
if abs(d - ang) < orb:
soft += 1 - abs(d - ang) / orb
return hard, soft
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=20.0)
args = ap.parse_args()
client = SimpleUDPClient(args.host, args.port)
print(f"[ephemeris] emitting /gs/astro/* (Swiss Ephemeris, no files) "
f"every {args.interval:g}s")
while True:
try:
now = datetime.datetime.now(datetime.timezone.utc)
jd = swe.julday(now.year, now.month, now.day,
now.hour + now.minute / 60 + now.second / 3600)
lon, speed = sky(jd)
phase_angle = (lon["moon"] - lon["sun"]) % 360
illum = (1 - math.cos(math.radians(phase_angle))) / 2
client.send_message("/gs/astro/moon", float(illum))
for name in EMIT_LON:
addr = "moon_lon" if name == "moon" else \
"sun_lon" if name == "sun" else name
client.send_message(f"/gs/astro/{addr}", float(lon[name]))
hard, soft = aspects(lon)
client.send_message("/gs/astro/tension", float(hard))
client.send_message("/gs/astro/harmony", float(soft))
retro = [n for n, s in speed.items() if s < 0 and n != "moon"]
client.send_message("/gs/astro/retro", len(retro) / len(BODIES))
client.send_message("/gs/astro/mercury_retro",
1.0 if speed["mercury"] < 0 else 0.0)
except Exception as e: # noqa
print(f"[ephemeris] warn: {e}")
time.sleep(args.interval)
if __name__ == "__main__":
main()