Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

feat: dj.mjs — full theme support, USB hot-plug play/stop, TTS

- All colors now use __theme (T.bg, T.fg, T.bar, T.border, T.accent,
T.ok, T.warn) — auto light/dark based on time of day
- USB plug: "USB DJ on" TTS, auto-scan + play first track
- USB unplug: "USB DJ off" TTS, stop playback, clear track list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+36 -24
+36 -24
fedac/native/pieces/dj.mjs
··· 244 244 angle += (d.speed || 1) * 0.05; 245 245 } 246 246 247 - // Platter 247 + // Theme-derived helpers 248 248 const fg = T.fg || 200; 249 249 const dim = T.fgMute || 60; 250 - ink(T.bg[0] + 8, T.bg[1] + 8, T.bg[2] + 12); 250 + const s = T.dark ? 1 : -1; // sign: +brightness for dark, -brightness for light 251 + 252 + // Platter 253 + ink(T.bg[0] + 8 * s, T.bg[1] + 8 * s, T.bg[2] + 12 * s); 251 254 circle(cx, cy, r, true); 252 255 253 256 // Grooves 254 257 for (let g = Math.floor(r * 0.3); g < r; g += 2) { 255 - const brightness = dragging ? 10 : 6; 256 - ink(T.bg[0] + brightness, T.bg[1] + brightness, T.bg[2] + brightness + 4); 258 + const b = dragging ? 10 : 6; 259 + ink(T.bg[0] + b * s, T.bg[1] + b * s, T.bg[2] + (b + 4) * s); 257 260 circle(cx, cy, g, false); 258 261 } 259 262 260 263 // Rim 261 - ink(dragging ? 120 : dim, dragging ? 120 : dim, dragging ? 140 : dim + 15); 264 + ink(T.border[0], T.border[1], T.border[2]); 262 265 circle(cx, cy, r, false); 263 266 264 267 // Label (center area) 265 268 const labelR = Math.floor(r * 0.25); 266 - ink(dragging ? 60 : 30, dragging ? 40 : 25, dragging ? 30 : 20); 269 + const lf = T.dark ? 1 : -1; 270 + ink(T.bg[0] + 20 * lf, T.bg[1] + 15 * lf, T.bg[2] + 10 * lf); 267 271 circle(cx, cy, labelR, true); 268 - ink(dragging ? 100 : 50, dragging ? 80 : 40, dragging ? 60 : 35); 272 + ink(T.bg[0] + 40 * lf, T.bg[1] + 30 * lf, T.bg[2] + 25 * lf); 269 273 circle(cx, cy, labelR, false); 270 274 271 275 // Center dot 272 - ink(fg, fg - 30, fg - 60); 276 + ink(T.accent[0], T.accent[1], T.accent[2]); 273 277 circle(cx, cy, 3, true); 274 278 275 279 // Spinning needle line 276 280 const needleLen = r - 6; 277 281 const nx = cx + Math.cos(angle) * needleLen; 278 282 const ny = cy + Math.sin(angle) * needleLen; 279 - ink(d.playing ? 80 : 40, d.playing ? 200 : 80, d.playing ? 80 : 40); 283 + const nOn = T.ok, nOff = [dim, dim, dim]; 284 + const nc = d.playing ? nOn : nOff; 285 + ink(nc[0], nc[1], nc[2]); 280 286 line(cx, cy, Math.floor(nx), Math.floor(ny)); 281 287 // Needle tip 282 - ink(d.playing ? 120 : 60, d.playing ? 255 : 120, d.playing ? 120 : 60); 288 + ink(d.playing ? nOn[0] + 40 : dim + 20, d.playing ? nOn[1] + 40 : dim + 20, d.playing ? nOn[2] + 40 : dim + 20); 283 289 circle(Math.floor(nx), Math.floor(ny), Math.max(2, Math.floor(r * 0.04)), true); 284 290 285 291 // --- Track info --- ··· 287 293 const maxC = Math.floor(w / CW) - 2; 288 294 289 295 // Title at top 290 - ink(fg, fg, fg + 10); 296 + ink(fg, fg, fg); 291 297 write(title.slice(0, maxC), { x: 4, y: 3, size: 1, font: F }); 292 298 293 299 // Track counter + USB (top right) 294 300 if (files.length > 0) { 295 - ink(dim, dim, dim + 10); 301 + ink(dim, dim, dim); 296 302 const tc = `${trackIdx + 1}/${files.length}`; 297 303 write(tc, { x: w - tc.length * CW - 4, y: 3, size: 1, font: F }); 298 304 } 299 - ink(usbConnected ? 60 : dim, usbConnected ? 180 : dim, usbConnected ? 60 : dim); 305 + const uc = usbConnected ? T.ok : [dim, dim, dim]; 306 + ink(uc[0], uc[1], uc[2]); 300 307 write(usbConnected ? "USB" : "---", { x: w - 22, y: 14, size: 1, font: F }); 301 308 302 309 // --- Bottom panel (no overlaps) --- ··· 309 316 const barY = h - 34; 310 317 const barW = w - 8; 311 318 const progress = d.duration > 0 ? d.position / d.duration : 0; 312 - ink(T.bg[0] + 15, T.bg[1] + 15, T.bg[2] + 20); 319 + ink(T.bar[0], T.bar[1], T.bar[2]); 313 320 box(4, barY, barW, 4); 314 - ink(d.playing ? 60 : 40, d.playing ? 180 : 100, d.playing ? 60 : 40); 321 + const pb = d.playing ? T.ok : [dim, dim + 20, dim]; 322 + ink(pb[0], pb[1], pb[2]); 315 323 box(4, barY, Math.max(1, Math.floor(barW * progress)), 4); 316 324 317 325 // Time + speed 318 - ink(dim + 20, dim + 20, dim + 30); 326 + ink(T.fgDim, T.fgDim, T.fgDim); 319 327 write(`${fmt(d.position)} / ${fmt(d.duration)}`, { x: 4, y: barY + 7, size: 1, font: F }); 320 328 if (d.loaded) { 321 329 const spd = `${(d.speed || 1).toFixed(2)}x`; ··· 341 349 const bd = btnDefs[i]; 342 350 buttons.push({ x: bx, y: btnY, w: btnW, h: btnH, id: bd.id, label: bd.label }); 343 351 // Button background 344 - ink(T.bg[0] + 18, T.bg[1] + 18, T.bg[2] + 24); 352 + ink(T.bar[0], T.bar[1], T.bar[2]); 345 353 box(bx, btnY, btnW, btnH); 346 354 // Button border 347 - ink(dim + 10, dim + 10, dim + 20); 355 + ink(T.border[0], T.border[1], T.border[2]); 348 356 box(bx, btnY, btnW, btnH, "outline"); 349 357 // Button label (centered) 350 358 const lx = bx + Math.floor((btnW - bd.label.length * CW) / 2); 351 359 const ly = btnY + 2; 352 - ink(fg, fg, fg + 10); 360 + ink(fg, fg, fg); 353 361 write(bd.label, { x: lx, y: ly, size: 1, font: F }); 354 362 } 355 363 356 364 // Drag state 357 365 if (dragging) { 358 - ink(255, 100, 60); 366 + ink(T.accent[0], T.accent[1], T.accent[2]); 359 367 write("SCRATCH", { x: cx - 21, y: cy - 5, size: 1, font: F }); 360 368 } 361 369 362 370 // Message toast 363 371 if (message && frame - messageFrame < 120) { 364 372 const fade = Math.max(0, 255 - Math.floor((frame - messageFrame) * 2.5)); 365 - ink(255, 220, 60, fade); 373 + ink(T.warn[0], T.warn[1], T.warn[2], fade); 366 374 write(message.slice(0, maxC), { x: 4, y: 16, size: 1, font: F }); 367 375 } 368 376 } ··· 374 382 const nowMounted = system?.mountMusic?.() || false; 375 383 if (nowMounted && !usbConnected) { 376 384 usbConnected = true; mounted = true; 377 - if (tts) tts.speak("USB connected"); 378 - scan(system, tts); 385 + if (tts) tts.speak("USB DJ on"); 386 + scan(system, null); 379 387 if (files.length > 0) { trackIdx = 0; loadTrack(sound, tts); } 380 388 } else if (!nowMounted && usbConnected) { 381 389 usbConnected = false; 382 - if (tts) tts.speak("USB removed"); 390 + const dk = sound?.deck; 391 + const d = dk?.decks?.[0]; 392 + if (d?.playing) { dk.pause(0); spinSpeed = 0; } 393 + files = []; 394 + if (tts) tts.speak("USB DJ off"); 383 395 msg("USB removed"); 384 396 } 385 397 }