Monorepo for Aesthetic.Computer
aesthetic.computer
1#!/usr/bin/env node
2/**
3 * Automated Notepat Playlist Player
4 *
5 * Plays a playlist of folk and traditional melodies
6 */
7
8import Artery from '../../artery/artery.mjs';
9import { melodies, noteToKey } from './melodies.mjs';
10
11const PURPLE_BG = '\x1b[45m';
12const WHITE = '\x1b[97m';
13const RESET = '\x1b[0m';
14const GREEN = '\x1b[92m';
15const CYAN = '\x1b[96m';
16const YELLOW = '\x1b[93m';
17
18const testLog = (msg) => console.log(`${PURPLE_BG}${WHITE}🧪${RESET} ${msg}`);
19const successLog = (msg) => console.log(`${GREEN}✅ ${msg}${RESET}`);
20const playingLog = (msg) => console.log(`${YELLOW}🎵 ${msg}${RESET}`);
21
22// Default playlist: all folk/traditional songs
23const defaultPlaylist = [
24 'twinkle',
25 'mary',
26 'old-macdonald',
27 'yankee-doodle',
28 'frere-jacques',
29 'london-bridge',
30 'row-row-row',
31 'skip-to-my-lou',
32 'camptown-races',
33 'oh-susanna',
34 'amazing-grace',
35 'auld-lang-syne',
36 'shenandoah',
37 'home-on-the-range',
38 'red-river-valley',
39 'scarborough-fair',
40 'greensleeves',
41 'when-the-saints',
42 'danny-boy',
43 'ode-to-joy',
44 'jingle-bells',
45 'happy-birthday',
46];
47
48async function playMelody(client, melodyName) {
49 const melody = melodies[melodyName];
50 if (!melody) {
51 console.error(`Unknown melody: ${melodyName}`);
52 return 0;
53 }
54
55 playingLog(`Now playing: ${melodyName} (${melody.length} notes)`);
56
57 let notesPlayed = 0;
58 for (const {note, octave, duration} of melody) {
59 const key = noteToKey(note, octave);
60 const noteDisplay = `${note.toUpperCase()}${octave}`;
61
62 // Show the note being played
63 process.stdout.write(`${CYAN}♪ ${noteDisplay.padEnd(4)} ${RESET}`);
64
65 // Dispatch keydown
66 await client.send('Input.dispatchKeyEvent', {
67 type: 'keyDown',
68 text: key,
69 key: key,
70 code: `Key${key.toUpperCase()}`,
71 windowsVirtualKeyCode: key.charCodeAt(0)
72 });
73
74 await new Promise(r => setTimeout(r, duration));
75
76 // Dispatch keyup
77 await client.send('Input.dispatchKeyEvent', {
78 type: 'keyUp',
79 key: key,
80 code: `Key${key.toUpperCase()}`,
81 windowsVirtualKeyCode: key.charCodeAt(0)
82 });
83
84 notesPlayed++;
85
86 // Line break every 8 notes
87 if (notesPlayed % 8 === 0) {
88 console.log('');
89 }
90
91 // Small gap between notes
92 await new Promise(r => setTimeout(r, 50));
93 }
94
95 if (notesPlayed % 8 !== 0) {
96 console.log(''); // Final line break
97 }
98
99 console.log('');
100 return notesPlayed;
101}
102
103async function playPlaylist(playlist = defaultPlaylist) {
104
105 try {
106 console.log('');
107 testLog(`Starting Notepat Playlist Player`);
108 playingLog(`Playlist: ${playlist.length} melodies`);
109 console.log('');
110
111 // Ensure panel is open
112 await Artery.openPanelStandalone();
113 await new Promise(resolve => setTimeout(resolve, 500));
114
115 // Connect
116 const client = new Artery();
117 await client.connect();
118 testLog('Connected to AC');
119
120 await client.jump('notepat');
121 testLog('Navigated to notepat');
122
123 // Wait for page to fully load and reconnect context
124 await new Promise(r => setTimeout(r, 2000));
125
126 // Reconnect after navigation
127 client.close();
128 await client.connect();
129 testLog('Reconnected after navigation');
130 await new Promise(r => setTimeout(r, 500));
131
132 // Activate audio context
133 await client.activateAudio();
134 testLog('Audio context activated');
135
136 console.log('');
137 console.log(`${CYAN}${'='.repeat(60)}${RESET}`);
138 console.log('');
139
140 let totalNotes = 0;
141 let songsPlayed = 0;
142
143 // Play each melody in the playlist
144 for (const melodyName of playlist) {
145 const notesPlayed = await playMelody(client, melodyName);
146 totalNotes += notesPlayed;
147 songsPlayed++;
148
149 // Pause between songs
150 await new Promise(r => setTimeout(r, 1000));
151 }
152
153 console.log(`${CYAN}${'='.repeat(60)}${RESET}`);
154 console.log('');
155 successLog(`Playlist complete! Played ${songsPlayed} songs, ${totalNotes} total notes`);
156 console.log('');
157
158 // Return to prompt
159 await client.jump('prompt');
160 testLog('Returned to prompt');
161 await new Promise(r => setTimeout(r, 500));
162
163 // Close the AC panel
164 await Artery.closePanelStandalone();
165
166 client.close();
167 process.exit(0);
168
169 } catch (error) {
170 console.error(`💔 Playlist failed: ${error.message}`);
171 process.exit(1);
172 }
173}
174
175// Parse custom playlist from command line or use default
176const args = process.argv.slice(2);
177let playlist;
178
179if (args.length > 0) {
180 // Custom playlist from command line
181 playlist = args;
182 console.log(`${CYAN}🎵 Custom playlist: ${playlist.join(', ')}${RESET}`);
183} else {
184 // Use default playlist
185 playlist = defaultPlaylist;
186 console.log(`${CYAN}🎵 Playing all ${playlist.length} melodies${RESET}`);
187}
188
189playPlaylist(playlist);