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>
107 lines
4.5 KiB
Python
107 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Every human edit to Wikipedia on Earth, right now, as one live pulse.
|
|
The Wikimedia recentchange SSE firehose is a chaotic global stream of edits;
|
|
each edit's byte-delta becomes a percussive glitch and the edits/sec is a
|
|
breathing tempo — the collective heartbeat of humanity writing itself down.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import time
|
|
import urllib.request
|
|
from collections import deque
|
|
|
|
from pythonosc.udp_client import SimpleUDPClient
|
|
|
|
STREAM_URL = "https://stream.wikimedia.org/v2/stream/recentchange"
|
|
USER_AGENT = "Godstrument/1.0 (live instrument; monsterrobotparty@gmail.com)"
|
|
|
|
MAX_EVENTS_PER_SEC = 30 # throttle emitted /event messages
|
|
RATE_EMIT_INTERVAL = 1.0 # emit /gs/wiki/rate about once per second
|
|
RATE_WINDOW = 5.0 # rolling window (s) for edits-per-second
|
|
|
|
|
|
def run(host, port):
|
|
client = SimpleUDPClient(host, port)
|
|
print(f"[world_wiki] emitting /gs/wiki/... every ~1s", flush=True)
|
|
|
|
backoff = 1.0
|
|
# rolling timestamps of ALL counted edits (for edits-per-second)
|
|
edit_times = deque()
|
|
# timestamps of emitted /event messages within the current second (throttle)
|
|
emit_window = deque()
|
|
last_rate_emit = time.monotonic()
|
|
|
|
while True:
|
|
try:
|
|
req = urllib.request.Request(STREAM_URL, headers={"User-Agent": USER_AGENT})
|
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
backoff = 1.0 # connected cleanly, reset backoff
|
|
for raw in resp:
|
|
line = raw.decode("utf-8", "replace").rstrip("\n")
|
|
|
|
# SSE: a "data: " line carries the JSON payload.
|
|
if line.startswith("data: "):
|
|
payload = line[6:]
|
|
try:
|
|
change = json.loads(payload)
|
|
except (ValueError, TypeError):
|
|
continue
|
|
|
|
if change.get("type") in ("edit", "new"):
|
|
length = change.get("length") or {}
|
|
new = length.get("new")
|
|
old = length.get("old")
|
|
if new is not None and old is not None:
|
|
edit_size = abs(int(new) - int(old))
|
|
else:
|
|
edit_size = 0
|
|
|
|
now = time.monotonic()
|
|
edit_times.append(now)
|
|
|
|
# Throttle emitted events to ~MAX_EVENTS_PER_SEC.
|
|
while emit_window and now - emit_window[0] >= 1.0:
|
|
emit_window.popleft()
|
|
if len(emit_window) < MAX_EVENTS_PER_SEC:
|
|
try:
|
|
client.send_message("/gs/wiki/edit/event", float(edit_size))
|
|
emit_window.append(now)
|
|
except OSError as exc:
|
|
print(f"[world_wiki] OSC send failed: {exc}", flush=True)
|
|
|
|
# A blank line ends an SSE event; use this beat to
|
|
# trim the rolling window and emit the rate.
|
|
now = time.monotonic()
|
|
while edit_times and now - edit_times[0] >= RATE_WINDOW:
|
|
edit_times.popleft()
|
|
|
|
if now - last_rate_emit >= RATE_EMIT_INTERVAL:
|
|
span = min(RATE_WINDOW, max(now - last_rate_emit, 1e-6))
|
|
rate = len(edit_times) / span if edit_times else 0.0
|
|
try:
|
|
client.send_message("/gs/wiki/rate", float(rate))
|
|
except OSError as exc:
|
|
print(f"[world_wiki] OSC send failed: {exc}", flush=True)
|
|
last_rate_emit = now
|
|
|
|
# Stream ended without error; loop to reconnect.
|
|
print("[world_wiki] stream closed, reconnecting...", flush=True)
|
|
|
|
except Exception as exc: # noqa: BLE401 — never die on a transient error
|
|
print(f"[world_wiki] stream error: {exc}; retrying in {backoff:.0f}s", flush=True)
|
|
time.sleep(backoff)
|
|
backoff = min(backoff * 2, 30.0)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Wikipedia recentchange SSE -> OSC")
|
|
parser.add_argument("--host", default="127.0.0.1")
|
|
parser.add_argument("--port", type=int, default=9000)
|
|
args = parser.parse_args()
|
|
run(args.host, args.port)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|