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>
110 lines
4.1 KiB
Python
110 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""BTC/USD live trade ticks from Binance — the fastest, most chaotic source.
|
|
Every sub-second trade fires the market's pulse; price-to-price velocity is raw
|
|
volatility you can hear, driving rhythm and jitter in the modulation matrix.
|
|
"""
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
import urllib.request
|
|
|
|
from pythonosc.udp_client import SimpleUDPClient
|
|
|
|
NAME = "world_crypto"
|
|
WS_URL = "wss://stream.binance.com:9443/ws/btcusdt@trade"
|
|
REST_URL = "https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT"
|
|
POLL_S = 2.0 # REST fallback poll interval
|
|
USER_AGENT = "godstrument/1.0 (+world_crypto)"
|
|
|
|
|
|
def emit(client, last_price, price):
|
|
"""Send price + tick-to-tick velocity. Returns the new last_price."""
|
|
client.send_message("/gs/crypto/price", float(price))
|
|
vel = 0.0 if last_price is None else abs(price - last_price)
|
|
client.send_message("/gs/crypto/vel", float(vel))
|
|
return price
|
|
|
|
|
|
async def run_websocket(client):
|
|
"""Stream trades over the websocket. Returns True if it ever connected
|
|
(so the caller keeps favoring the socket), False if it never connected
|
|
(so the caller can fall back to REST)."""
|
|
import websockets # part of the required stack; import here to keep top clean
|
|
|
|
ever_connected = False
|
|
last_price = None
|
|
async with websockets.connect(WS_URL, ping_interval=20, ping_timeout=20) as ws:
|
|
ever_connected = True
|
|
async for raw in ws:
|
|
try:
|
|
msg = json.loads(raw)
|
|
price = float(msg["p"])
|
|
except (ValueError, KeyError, TypeError):
|
|
continue # malformed / non-trade frame; skip
|
|
last_price = emit(client, last_price, price)
|
|
return ever_connected
|
|
|
|
|
|
async def poll_rest(client):
|
|
"""Fallback: poll the REST ticker forever at POLL_S. Never returns."""
|
|
last_price = None
|
|
loop = asyncio.get_running_loop()
|
|
while True:
|
|
try:
|
|
def fetch():
|
|
req = urllib.request.Request(REST_URL, headers={"User-Agent": USER_AGENT})
|
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
return json.loads(resp.read().decode())
|
|
|
|
data = await loop.run_in_executor(None, fetch)
|
|
price = float(data["price"])
|
|
last_price = emit(client, last_price, price)
|
|
except Exception as e: # transient network / parse error — keep going
|
|
print(f"[{NAME}] REST poll error: {e}; retrying")
|
|
await asyncio.sleep(POLL_S)
|
|
|
|
|
|
async def main():
|
|
parser = argparse.ArgumentParser(description="Binance BTCUSDT trade ticks -> OSC")
|
|
parser.add_argument("--host", default="127.0.0.1")
|
|
parser.add_argument("--port", type=int, default=9000)
|
|
args = parser.parse_args()
|
|
|
|
client = SimpleUDPClient(args.host, args.port)
|
|
print(f"[{NAME}] emitting /gs/crypto/price,/gs/crypto/vel every <1s (ws) / {POLL_S:g}s (rest)")
|
|
|
|
backoff = 1.0
|
|
rest_only = False
|
|
while True:
|
|
if rest_only:
|
|
# We could not open the socket at all; stay on REST but keep
|
|
# occasionally retrying the socket by resetting the flag on error.
|
|
try:
|
|
await poll_rest(client)
|
|
except Exception as e:
|
|
print(f"[{NAME}] REST loop error: {e}; retrying")
|
|
await asyncio.sleep(POLL_S)
|
|
continue
|
|
|
|
try:
|
|
await run_websocket(client)
|
|
# Clean disconnect (stream ended): reconnect quickly.
|
|
print(f"[{NAME}] websocket closed; reconnecting")
|
|
backoff = 1.0
|
|
except Exception as e:
|
|
print(f"[{NAME}] websocket error: {e}; backoff {backoff:g}s")
|
|
await asyncio.sleep(backoff)
|
|
backoff = min(backoff * 2, 30.0)
|
|
# If we have never managed to connect, drop to REST fallback,
|
|
# but keep trying the socket periodically via a bounded retry.
|
|
if backoff >= 8.0 and not rest_only:
|
|
print(f"[{NAME}] websocket unreachable; falling back to REST polling")
|
|
rest_only = True
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
asyncio.run(main())
|
|
except KeyboardInterrupt:
|
|
pass
|