Monorepo for Aesthetic.Computer aesthetic.computer
at main 250 lines 7.4 kB view raw
1#!/usr/bin/env node 2 3import fs from "fs"; 4import { SaxesParser } from "saxes"; 5 6class AbletonSessionParser { 7 constructor() { 8 this.tracks = []; 9 this.currentTrack = null; 10 this.currentClip = null; 11 this.currentElement = ""; 12 this.currentText = ""; 13 this.tempo = 120; 14 } 15 16 parseFile(filePath) { 17 const data = fs.readFileSync(filePath, "utf8"); 18 const parser = new SaxesParser({ fragment: false }); 19 20 parser.on("opentag", (tag) => { 21 this.currentElement = tag.name; 22 23 if (tag.name === "GroupTrack" || tag.name === "AudioTrack" || tag.name === "MidiTrack") { 24 this.currentTrack = { 25 name: "Track", 26 clips: [], 27 id: tag.attributes?.Id?.value 28 }; 29 } else if (tag.name === "ClipSlot" || tag.name === "GroupTrackSlot") { 30 this.currentClip = { 31 id: tag.attributes?.Id?.value, 32 hasClip: false, 33 name: "", 34 length: 0, 35 activity: 0, 36 color: 0 37 }; 38 } else if (tag.name === "AudioClip" || tag.name === "MidiClip") { 39 if (this.currentClip) { 40 this.currentClip.hasClip = true; 41 this.currentClip.id = tag.attributes?.Id?.value; 42 this.currentClip.length = parseFloat(tag.attributes?.Length?.value || 1); 43 } 44 } 45 }); 46 47 parser.on("text", (text) => { 48 this.currentText = text.trim(); 49 }); 50 51 parser.on("closetag", (tag) => { 52 if (tag.name === "EffectiveName" && this.currentText) { 53 if (this.currentTrack && !this.currentClip) { 54 this.currentTrack.name = this.currentText; 55 } 56 } else if (tag.name === "ClipSlot" || tag.name === "GroupTrackSlot") { 57 if (this.currentTrack && this.currentClip) { 58 this.currentTrack.clips.push({ ...this.currentClip }); 59 } 60 this.currentClip = null; 61 } else if (tag.name === "GroupTrack" || tag.name === "AudioTrack" || tag.name === "MidiTrack") { 62 if (this.currentTrack) { 63 this.tracks.push({ ...this.currentTrack }); 64 } 65 this.currentTrack = null; 66 } 67 68 this.currentText = ""; 69 this.currentElement = ""; 70 }); 71 72 parser.write(data); 73 parser.close(); 74 75 return { 76 tracks: this.tracks, 77 tempo: this.tempo 78 }; 79 } 80} 81 82class SimpleSessionVisualizer { 83 constructor(sessionData) { 84 this.tracks = sessionData.tracks; 85 this.tempo = sessionData.tempo; 86 this.isPlaying = true; 87 this.beat = 0; 88 this.trackActivity = new Map(); 89 this.aggregateOutput = 0; 90 91 // Initialize activity 92 this.tracks.forEach((track, i) => { 93 this.trackActivity.set(i, 0); 94 }); 95 96 this.startUpdateLoop(); 97 } 98 99 clearScreen() { 100 process.stdout.write('\x1B[2J\x1B[0f'); 101 } 102 103 render() { 104 this.clearScreen(); 105 106 // Header 107 const playIcon = this.isPlaying ? "▶" : "⏸"; 108 const activeClips = this.getActiveClipCount(); 109 console.log(`🎛 ${playIcon} Beat: ${this.beat.toFixed(1)} | ${this.tempo} BPM | Active: ${activeClips}`); 110 console.log("━".repeat(60)); 111 112 // Session Grid (simplified) 113 console.log("Session Grid:"); 114 for (let scene = 0; scene < 8; scene++) { 115 let line = `${(scene + 1)}`; 116 117 for (let t = 0; t < Math.min(this.tracks.length, 12); t++) { 118 const track = this.tracks[t]; 119 const clip = track.clips[scene]; 120 const activity = this.trackActivity.get(t) || 0; 121 122 if (clip && clip.hasClip) { 123 const intensity = Math.min(Math.floor(activity / 25), 3); 124 const symbols = ["▁", "▃", "▅", "█"]; 125 line += `${symbols[intensity]} `; 126 } else { 127 line += "░ "; 128 } 129 } 130 console.log(line); 131 } 132 133 console.log("━".repeat(60)); 134 135 // Track Activity (condensed) 136 console.log("Track Activity:"); 137 for (let i = 0; i < Math.min(this.tracks.length, 8); i++) { 138 const track = this.tracks[i]; 139 const activity = this.trackActivity.get(i) || 0; 140 const barLength = 20; 141 const filled = Math.min(Math.floor((activity / 100) * barLength), barLength); 142 const empty = barLength - filled; 143 144 const trackName = track.name.length > 12 ? track.name.substring(0, 12) : track.name; 145 const bar = "█".repeat(filled) + "░".repeat(empty); 146 const percentage = Math.floor(activity); 147 148 console.log(`${trackName.padEnd(12)}${bar}${percentage}%`); 149 } 150 151 // Aggregate 152 const aggBar = "█".repeat(Math.floor(this.aggregateOutput / 2)) + "░".repeat(50 - Math.floor(this.aggregateOutput / 2)); 153 console.log("━".repeat(60)); 154 console.log(`Aggregate: │${aggBar}${Math.floor(this.aggregateOutput)}%`); 155 console.log("SPACE=pause, 1-8=scenes, T=trigger, Q=quit"); 156 } 157 158 triggerScene(sceneIndex) { 159 this.tracks.forEach((track, trackIndex) => { 160 const clip = track.clips[sceneIndex]; 161 if (clip && clip.hasClip) { 162 const activity = 50 + Math.random() * 50; 163 this.trackActivity.set(trackIndex, activity); 164 } 165 }); 166 this.updateAggregateActivity(); 167 } 168 169 updateAggregateActivity() { 170 let total = 0; 171 let count = 0; 172 173 this.trackActivity.forEach((activity) => { 174 if (activity > 0) { 175 total += activity; 176 count++; 177 } 178 }); 179 180 this.aggregateOutput = count > 0 ? total / count : 0; 181 } 182 183 getActiveClipCount() { 184 let count = 0; 185 this.trackActivity.forEach((activity) => { 186 if (activity > 10) count++; 187 }); 188 return count; 189 } 190 191 startUpdateLoop() { 192 setInterval(() => { 193 if (this.isPlaying) { 194 this.beat += 0.3; // Fast beat progression 195 } 196 197 // Decay activity 198 this.trackActivity.forEach((activity, index) => { 199 const newActivity = Math.max(0, activity - 4); // Fast decay 200 this.trackActivity.set(index, newActivity); 201 }); 202 203 // Frequent random activity spikes 204 if (Math.random() < 0.2) { 205 const randomTrack = Math.floor(Math.random() * this.tracks.length); 206 const currentActivity = this.trackActivity.get(randomTrack) || 0; 207 const spike = Math.random() * 50; 208 this.trackActivity.set(randomTrack, Math.min(100, currentActivity + spike)); 209 } 210 211 // Auto-trigger scenes frequently 212 if (Math.random() < 0.1) { 213 const randomScene = Math.floor(Math.random() * 8); 214 this.triggerScene(randomScene); 215 } 216 217 this.updateAggregateActivity(); 218 this.render(); 219 }, 100); // Fast updates 220 } 221} 222 223// Handle keyboard input 224process.stdin.setRawMode(true); 225process.stdin.resume(); 226process.stdin.setEncoding('utf8'); 227 228process.stdin.on('data', (key) => { 229 if (key === '\u0003' || key === 'q') { // Ctrl+C or Q 230 process.exit(); 231 } 232}); 233 234// Main execution 235const xmlPath = process.argv[2] || "/workspaces/aesthetic-computer/system/public/assets/wipppps/zzzZWAP_extracted.xml"; 236 237if (!fs.existsSync(xmlPath)) { 238 console.error(`XML file not found: ${xmlPath}`); 239 console.error("Usage: node ableton-simple-viewer.mjs [path-to-extracted.xml]"); 240 process.exit(1); 241} 242 243console.log("🎵 Parsing Ableton Live project..."); 244const parser = new AbletonSessionParser(); 245const sessionData = parser.parseFile(xmlPath); 246 247console.log(`📊 Found ${sessionData.tracks.length} tracks`); 248console.log(`🚀 Starting simple session viewer (auto-play)...`); 249 250new SimpleSessionVisualizer(sessionData);