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

89 lines
3.5 KiB
Python

"""
world_almanac.py — the planting calendar. Sowing by the moon.
For millennia people planted by an almanac: the moon's phase and the zodiac sign
it's passing through told you what to sow. It's not fetched data — it's computed
from the same sky world_ephemeris uses. The zodiac's four elements cycle in a
fixed order (fire, earth, air, water), and biodynamic tradition maps them to a
crop type — a slow four-step sequencer the moon walks through, changing every
~2.25 days:
earth signs -> ROOT days water signs -> LEAF days
air signs -> FLOWER days fire signs -> FRUIT/SEED days
Musically it's a modal calendar: the instrument's character can follow the moon
through the garden.
Emits:
/gs/almanac/element 0..1 stepped — the day's element (root/leaf/flower/fruit)
/gs/almanac/waxing 1 waxing (sow above-ground), 0 waning (sow roots)
/gs/almanac/dec 0..1 — Moon's declination (ascending/descending, ~27.3d)
/gs/almanac/fertile 0..1 — a simple biodynamic favourability score
"""
from __future__ import annotations
import argparse
import datetime
import math
import time
import swisseph as swe
from pythonosc.udp_client import SimpleUDPClient
FLG = swe.FLG_MOSEPH | swe.FLG_SPEED
# element index by sign (Aries..Pisces): fire,earth,air,water repeating.
# biodynamic crop: 0 fire=fruit, 1 earth=root, 2 air=flower, 3 water=leaf
ELEMENTS = ["fruit", "root", "flower", "leaf"]
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=30.0)
args = ap.parse_args()
client = SimpleUDPClient(args.host, args.port)
print(f"[almanac] emitting /gs/almanac/* (root/leaf/flower/fruit by the moon) "
f"every {args.interval:g}s")
last_label = None
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)
moon, _ = swe.calc_ut(jd, swe.MOON, FLG)
sun, _ = swe.calc_ut(jd, swe.SUN, FLG)
eq, _ = swe.calc_ut(jd, swe.MOON,
swe.FLG_MOSEPH | swe.FLG_EQUATORIAL | swe.FLG_SPEED)
sign = int(moon[0] // 30) % 12
elem = sign % 4 # 0 fire,1 earth,2 air,3 water
phase_angle = (moon[0] - sun[0]) % 360
waxing = 1.0 if phase_angle < 180 else 0.0
illum = (1 - math.cos(math.radians(phase_angle))) / 2
dec = eq[1] # Moon declination (deg)
dec01 = max(0.0, min(1.0, (dec + 28.5) / 57.0))
ascending = 1.0 if eq[4] > 0 else 0.0
fertile = 0.45 * illum + 0.35 * ascending + 0.20 * (1.0 if elem in (1, 3) else 0.0)
client.send_message("/gs/almanac/element", elem / 3.0)
client.send_message("/gs/almanac/waxing", waxing)
client.send_message("/gs/almanac/dec", dec01)
client.send_message("/gs/almanac/fertile", fertile)
label = ELEMENTS[elem]
if label != last_label:
print(f" 🌱 {label} day (moon in sign {sign + 1}, "
f"{'waxing' if waxing else 'waning'}, "
f"{'ascending' if ascending else 'descending'})")
last_label = label
except Exception as e: # noqa
print(f"[almanac] warn: {e}")
time.sleep(args.interval)
if __name__ == "__main__":
main()