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>
109 lines
4.1 KiB
Python
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()
|