Monorepo for Aesthetic.Computer
aesthetic.computer
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);