Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

fix: DJ radial scratch, speed arrows, skip dot-prefix metadata files

Scratch:
- Radial drag around platter center (atan2 angular delta)
- Modulates playback speed (-3x to 3x) based on drag direction
- Release returns to normal speed or pauses
- Visual rotation follows actual speed including reverse

Controls:
- Left/right arrows: speed/stretch (pitch fader style)
- Up/down arrows: seek
- Z: reset to 1x speed

Also skip dot-prefixed files (._ metadata from macOS)

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

+54 -18
+54 -18
fedac/native/pieces/dj.mjs
··· 10 10 let trackIdx = 0; // current track index 11 11 let mounted = false; 12 12 let dragging = false; 13 + let dragLastX = 0; 13 14 let dragLastY = 0; 14 15 let angle = 0; // visual rotation angle 15 16 let message = ""; ··· 17 18 let frame = 0; 18 19 let lastUsbCheck = 0; 19 20 let usbConnected = false; 20 - let spinSpeed = 0; // current spin velocity (auto-decays) 21 + let scratchSpeed = 0; // current scratch velocity (-2 to 2) 22 + let wasPlaying = false; // was playing before scratch started 21 23 22 24 function isAudio(name) { 25 + if (name.startsWith(".")) return false; // skip metadata (._xxx.m4a etc) 23 26 const dot = name.lastIndexOf("."); 24 27 return dot >= 0 && AUDIO_EXTS.has(name.slice(dot + 1).toLowerCase()); 25 28 } ··· 85 88 const w = screen?.width || 320; 86 89 const h = screen?.height || 240; 87 90 88 - // --- Mouse/touchpad scratch --- 91 + // --- Mouse/touchpad scratch (radial around platter center) --- 89 92 if (e.is("touch")) { 90 93 dragging = true; 94 + dragLastX = e.x; 91 95 dragLastY = e.y; 92 - spinSpeed = 0; 93 - if (d?.playing) dk.pause(0); 96 + wasPlaying = d?.playing || false; 97 + // Keep playing but we'll modulate speed 98 + scratchSpeed = 0; 94 99 return; 95 100 } 96 101 if (e.is("draw") && dragging) { 97 102 if (d?.loaded) { 98 - const dy = e.y - dragLastY; 99 - const seekAmt = dy * 0.03; // drag sensitivity 100 - const pos = Math.max(0, Math.min(d.duration, d.position + seekAmt)); 101 - dk.seek(0, pos); 102 - angle += dy * 0.02; 103 + const cx = (screen?.width || 320) / 2; 104 + const cy = (screen?.height || 240) / 2 - 10; 105 + // Compute angular change around platter center 106 + const prevAngle = Math.atan2(dragLastY - cy, dragLastX - cx); 107 + const curAngle = Math.atan2(e.y - cy, e.x - cx); 108 + let delta = curAngle - prevAngle; 109 + // Normalize to [-PI, PI] 110 + if (delta > Math.PI) delta -= Math.PI * 2; 111 + if (delta < -Math.PI) delta += Math.PI * 2; 112 + // Convert angular delta to speed: positive = forward, negative = reverse 113 + scratchSpeed = delta * 8; // sensitivity multiplier 114 + // Clamp 115 + if (scratchSpeed > 3) scratchSpeed = 3; 116 + if (scratchSpeed < -3) scratchSpeed = -3; 117 + dk.setSpeed(0, scratchSpeed); 118 + if (!d.playing) dk.play(0); 119 + angle += delta; 120 + dragLastX = e.x; 103 121 dragLastY = e.y; 104 122 } 105 123 return; 106 124 } 107 125 if (e.is("lift")) { 126 + if (dragging && d?.loaded) { 127 + // Release: resume normal speed or stop 128 + if (wasPlaying) { 129 + dk.setSpeed(0, 1); 130 + dk.play(0); 131 + } else { 132 + dk.setSpeed(0, 1); 133 + dk.pause(0); 134 + } 135 + scratchSpeed = 0; 136 + } 108 137 dragging = false; 109 138 return; 110 139 } ··· 143 172 return; 144 173 } 145 174 146 - // Arrow keys: seek 175 + // Arrow keys: speed/stretch (like a pitch fader) 147 176 if (e.is("keyboard:down:arrowleft")) { 148 - if (d?.loaded) dk.seek(0, Math.max(0, d.position - 5)); 177 + if (d?.loaded) dk.setSpeed(0, Math.max(-2, (d.speed || 1) - 0.1)); 149 178 return; 150 179 } 151 180 if (e.is("keyboard:down:arrowright")) { 152 - if (d?.loaded) dk.seek(0, Math.min(d.duration, d.position + 5)); 181 + if (d?.loaded) dk.setSpeed(0, Math.min(3, (d.speed || 1) + 0.1)); 182 + return; 183 + } 184 + // Arrow up/down: seek 185 + if (e.is("keyboard:down:arrowup")) { 186 + if (d?.loaded) dk.seek(0, Math.min(d.duration, d.position + 10)); 187 + return; 188 + } 189 + if (e.is("keyboard:down:arrowdown")) { 190 + if (d?.loaded) dk.seek(0, Math.max(0, d.position - 10)); 153 191 return; 154 192 } 155 193 ··· 157 195 if (e.is("keyboard:down:-")) { if (d) dk.setVolume(0, Math.max(0, d.volume - 0.05)); return; } 158 196 if (e.is("keyboard:down:=")) { if (d) dk.setVolume(0, Math.min(1, d.volume + 0.05)); return; } 159 197 160 - // Speed 161 - if (e.is("keyboard:down:z")) { if (d?.loaded) dk.setSpeed(0, Math.max(0.5, d.speed - 0.05)); return; } 162 - if (e.is("keyboard:down:x")) { if (d?.loaded) dk.setSpeed(0, Math.min(2.0, d.speed + 0.05)); return; } 198 + // Z: reset speed to 1x 199 + if (e.is("keyboard:down:z")) { if (d?.loaded) dk.setSpeed(0, 1); msg("1.00x"); return; } 163 200 } 164 201 165 202 function paint({ wipe, ink, box, line, write, circle, screen, sound }) { ··· 177 214 const cy = Math.floor(h / 2) - 10; 178 215 const r = Math.min(cx - 8, cy - 16); 179 216 180 - // Rotate angle 217 + // Rotate angle — follows playback speed (including negative for reverse scratch) 181 218 if (d.playing && !dragging) { 182 219 angle += (d.speed || 1) * 0.05; 183 - spinSpeed = d.speed || 1; 184 220 } 185 221 186 222 // Platter ··· 256 292 // Status bar 257 293 const sY = h - 10; 258 294 ink(dim, dim, dim + 10); 259 - write("Spc:play N/P:track R:scan Esc:exit", { x: 4, y: sY, size: 1, font: F }); 295 + write("Spc:play N/P:trk </>:spd Z:1x R:scan", { x: 4, y: sY, size: 1, font: F }); 260 296 261 297 // Drag state 262 298 if (dragging) {