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>
89 lines
3.5 KiB
Python
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()
|