Monorepo for Aesthetic.Computer aesthetic.computer
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 249 lines 6.1 kB view raw
1#!/usr/bin/env node 2/** 3 * 🎨 Artery Emacs Bridge 4 * 5 * Connects artery services to Emacs via JSON lines on stdin/stdout. 6 * Designed to be spawned as a subprocess from artery.el 7 * 8 * Protocol: 9 * Input (from Emacs): 10 * {"type": "eval", "code": "..."} - Eval JS in DAW 11 * {"type": "connect-daw"} - Connect to DAW CDP 12 * {"type": "disconnect-daw"} - Disconnect from DAW 13 * {"type": "get-state"} - Get current state 14 * 15 * Output (to Emacs): 16 * {"type": "daw-state", "bpm": 120, "playing": true} 17 * {"type": "daw-log", "timestamp": "...", "logType": "info", "message": "..."} 18 * {"type": "daw-connected", "title": "...", "url": "..."} 19 * {"type": "daw-disconnected"} 20 * {"type": "server-status", "status": "running|error|unknown"} 21 * {"type": "error", "message": "..."} 22 */ 23 24import * as readline from 'readline'; 25import DAWDebugger from './daw-debug.mjs'; 26import http from 'http'; 27import https from 'https'; 28 29// State 30let dawDebugger = null; 31let dawStateInterval = null; 32let serverCheckInterval = null; 33 34// Send JSON message to Emacs 35function emit(msg) { 36 console.log(JSON.stringify(msg)); 37} 38 39// Check AC server status 40async function checkServerStatus() { 41 const serverUrl = process.env.AC_SERVER_URL || 'https://localhost:8888'; 42 43 return new Promise((resolve) => { 44 const protocol = serverUrl.startsWith('https') ? https : http; 45 const req = protocol.get(serverUrl, { 46 timeout: 3000, 47 rejectUnauthorized: false // Allow self-signed certs in dev 48 }, (res) => { 49 resolve(res.statusCode < 400 ? 'running' : 'error'); 50 }); 51 52 req.on('error', () => resolve('error')); 53 req.on('timeout', () => { 54 req.destroy(); 55 resolve('error'); 56 }); 57 }); 58} 59 60// Connect to DAW 61async function connectDAW() { 62 if (dawDebugger && dawDebugger.connected) { 63 emit({ type: 'error', message: 'Already connected to DAW' }); 64 return; 65 } 66 67 dawDebugger = new DAWDebugger({ 68 json: true, 69 onLog: (entry) => { 70 emit({ 71 type: 'daw-log', 72 timestamp: new Date(entry.timestamp).toISOString(), 73 logType: entry.type, 74 message: entry.text, 75 source: entry.source 76 }); 77 }, 78 onConnect: (targetInfo) => { 79 emit({ 80 type: 'daw-connected', 81 title: targetInfo.title, 82 url: targetInfo.url 83 }); 84 85 // Start polling DAW state 86 if (dawStateInterval) clearInterval(dawStateInterval); 87 dawStateInterval = setInterval(async () => { 88 try { 89 const state = await dawDebugger.getDAWState(); 90 if (state) { 91 emit({ 92 type: 'daw-state', 93 bpm: state.bpm, 94 playing: state.playing, 95 time: state.time 96 }); 97 } 98 } catch (e) { 99 // Ignore polling errors 100 } 101 }, 500); 102 }, 103 onDisconnect: () => { 104 emit({ type: 'daw-disconnected' }); 105 if (dawStateInterval) { 106 clearInterval(dawStateInterval); 107 dawStateInterval = null; 108 } 109 } 110 }); 111 112 try { 113 await dawDebugger.connect(); 114 } catch (err) { 115 emit({ type: 'error', message: `DAW connection failed: ${err.message}` }); 116 dawDebugger = null; 117 } 118} 119 120// Disconnect from DAW 121function disconnectDAW() { 122 if (dawDebugger) { 123 dawDebugger.disconnect(); 124 dawDebugger = null; 125 } 126 if (dawStateInterval) { 127 clearInterval(dawStateInterval); 128 dawStateInterval = null; 129 } 130 emit({ type: 'daw-disconnected' }); 131} 132 133// Evaluate code in DAW 134async function evalInDAW(code) { 135 if (!dawDebugger || !dawDebugger.connected) { 136 emit({ type: 'error', message: 'Not connected to DAW' }); 137 return; 138 } 139 140 try { 141 const result = await dawDebugger.evaluate(code); 142 emit({ type: 'eval-result', result }); 143 } catch (err) { 144 emit({ type: 'error', message: `Eval failed: ${err.message}` }); 145 } 146} 147 148// Get current state 149async function getState() { 150 const serverStatus = await checkServerStatus(); 151 152 let dawState = null; 153 if (dawDebugger && dawDebugger.connected) { 154 try { 155 dawState = await dawDebugger.getDAWState(); 156 } catch (e) { 157 // Ignore 158 } 159 } 160 161 emit({ 162 type: 'state', 163 server: serverStatus, 164 daw: dawDebugger?.connected ? { 165 connected: true, 166 title: dawDebugger.targetInfo?.title, 167 ...dawState 168 } : { connected: false } 169 }); 170} 171 172// Handle incoming messages from Emacs 173async function handleMessage(msg) { 174 try { 175 const { type, ...params } = msg; 176 177 switch (type) { 178 case 'connect-daw': 179 await connectDAW(); 180 break; 181 182 case 'disconnect-daw': 183 disconnectDAW(); 184 break; 185 186 case 'eval': 187 await evalInDAW(params.code); 188 break; 189 190 case 'get-state': 191 await getState(); 192 break; 193 194 default: 195 emit({ type: 'error', message: `Unknown message type: ${type}` }); 196 } 197 } catch (err) { 198 emit({ type: 'error', message: err.message }); 199 } 200} 201 202// --- Main --- 203 204// Start server status polling 205serverCheckInterval = setInterval(async () => { 206 const status = await checkServerStatus(); 207 emit({ type: 'server-status', status }); 208}, 5000); 209 210// Initial server check 211checkServerStatus().then(status => { 212 emit({ type: 'server-status', status }); 213}); 214 215// Auto-connect to DAW on startup 216setTimeout(connectDAW, 1000); 217 218// Read JSON lines from stdin 219const rl = readline.createInterface({ 220 input: process.stdin, 221 terminal: false 222}); 223 224rl.on('line', async (line) => { 225 try { 226 const msg = JSON.parse(line); 227 await handleMessage(msg); 228 } catch (e) { 229 emit({ type: 'error', message: `Parse error: ${e.message}` }); 230 } 231}); 232 233// Handle shutdown 234process.on('SIGINT', () => { 235 disconnectDAW(); 236 if (serverCheckInterval) clearInterval(serverCheckInterval); 237 process.exit(0); 238}); 239 240process.on('SIGTERM', () => { 241 disconnectDAW(); 242 if (serverCheckInterval) clearInterval(serverCheckInterval); 243 process.exit(0); 244}); 245 246// Send ready message 247emit({ type: 'ready', version: '1.0.0' }); 248 249console.error('🎨 Artery Emacs Bridge started');