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_sky.py — the local sky over the venue, as a slow LFO.
|
|
|
|
Computes the sun's elevation above the horizon at a given latitude/longitude and
|
|
the current instant, using the NOAA solar-position algorithm. No network, no API
|
|
key, never rate-limited — just astronomy. This is the most place-specific signal
|
|
in the whole instrument: playing Brisbane at 2am sounds nothing like London at
|
|
sunset, because the sun is literally somewhere else in the sky.
|
|
|
|
Emits:
|
|
/gs/sky/elev solar elevation in degrees (-90 night .. +90 overhead)
|
|
/gs/sky/day 0..1 smooth day/night (0 deep night, 1 broad daylight)
|
|
/gs/sky/az solar azimuth in degrees (0=N, 90=E, 180=S, 270=W)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
import argparse
|
|
import datetime
|
|
import math
|
|
import time
|
|
|
|
from pythonosc.udp_client import SimpleUDPClient
|
|
|
|
|
|
def solar_position(lat: float, lon: float, when: datetime.datetime):
|
|
"""Return (elevation_deg, azimuth_deg) for lat/lon at UTC time `when`."""
|
|
n = when.timetuple().tm_yday
|
|
hour = when.hour + when.minute / 60 + when.second / 3600
|
|
g = 2 * math.pi / 365 * (n - 1 + (hour - 12) / 24) # fractional year
|
|
|
|
eqtime = 229.18 * (0.000075 + 0.001868 * math.cos(g)
|
|
- 0.032077 * math.sin(g) - 0.014615 * math.cos(2 * g)
|
|
- 0.040849 * math.sin(2 * g)) # minutes
|
|
decl = (0.006918 - 0.399912 * math.cos(g) + 0.070257 * math.sin(g)
|
|
- 0.006758 * math.cos(2 * g) + 0.000907 * math.sin(2 * g)
|
|
- 0.002697 * math.cos(3 * g) + 0.00148 * math.sin(3 * g)) # radians
|
|
|
|
time_offset = eqtime + 4 * lon # minutes (E +)
|
|
tst = hour * 60 + time_offset # true solar time
|
|
ha = math.radians(tst / 4 - 180) # hour angle
|
|
|
|
lat_r = math.radians(lat)
|
|
cos_zen = (math.sin(lat_r) * math.sin(decl)
|
|
+ math.cos(lat_r) * math.cos(decl) * math.cos(ha))
|
|
cos_zen = max(-1.0, min(1.0, cos_zen))
|
|
zen = math.acos(cos_zen)
|
|
elev = 90 - math.degrees(zen)
|
|
|
|
# azimuth
|
|
sin_az = -math.sin(ha) * math.cos(decl)
|
|
cos_az = (math.sin(decl) - math.sin(lat_r) * math.cos(zen)) / \
|
|
(math.cos(lat_r) * math.sin(zen) + 1e-9)
|
|
az = (math.degrees(math.atan2(sin_az, cos_az)) + 360) % 360
|
|
return elev, az
|
|
|
|
|
|
def day_fraction(elev: float) -> float:
|
|
"""Smooth 0..1 across the twilight band (civil dusk .. full day)."""
|
|
return max(0.0, min(1.0, (elev + 6.0) / 18.0))
|
|
|
|
|
|
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("--lat", type=float, default=-27.47, help="latitude (default Brisbane)")
|
|
ap.add_argument("--lon", type=float, default=153.02, help="longitude (default Brisbane)")
|
|
ap.add_argument("--interval", type=float, default=10.0)
|
|
args = ap.parse_args()
|
|
|
|
client = SimpleUDPClient(args.host, args.port)
|
|
print(f"[sky] emitting /gs/sky/elev|day|az for ({args.lat},{args.lon}) "
|
|
f"every {args.interval:g}s")
|
|
|
|
while True:
|
|
try:
|
|
elev, az = solar_position(args.lat, args.lon,
|
|
datetime.datetime.now(datetime.timezone.utc))
|
|
client.send_message("/gs/sky/elev", float(elev))
|
|
client.send_message("/gs/sky/day", day_fraction(elev))
|
|
client.send_message("/gs/sky/az", float(az))
|
|
except Exception as e: # noqa
|
|
print(f"[sky] warn: {e}")
|
|
time.sleep(args.interval)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|