Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

docs(reports): session report on notepat percussion + dj hotplug + help panel

Progress report covering the full session's work: two-step DOWN/UP
drum architecture across all 12 pads, stochastic variation, BPM-locked
flam, animated graphic notation, kit-geometry pan, full-screen drum
flash visuals, reverse loop pedal on space bar, USB /media stale-mount
fix, categorized adaptive-width help panel rewrite, and install-to-HD
robustness work. Lists all commits and build pipeline history.

+277
+277
reports/2026-04-10-notepat-percussion-and-dj-hotplug.md
··· 1 + # 2026-04-10 — notepat percussion kit + dj hotplug + help panel 2 + 3 + Session focus: turn notepat's percussion mode into a fully realized drum 4 + instrument (synth + sampling + visuals + stereo + reverse-loop pedal), fix a 5 + USB hotplug regression that broke DJ track detection, and rebuild the meta-held 6 + shortcut help panel so it's readable at any screen size. 7 + 8 + All work shipped via the oven build pipeline (compush → oven → `ac-os pull`). 9 + Final combined build at the time of writing is `57b818dd-8` on commit 10 + `18dbc5986`, carrying every feature listed below. 11 + 12 + --- 13 + 14 + ## 1. Percussion kit — complete rework 15 + 16 + ### 1.1 Two-step DOWN / UP architecture 17 + Every drum in the 12-pad kit now fires as a two-stage pair instead of a 18 + simultaneous layer stack: 19 + 20 + | Drum | DOWN (impact) | UP (release) | 21 + |---|---|---| 22 + | **kick** | beater click + body thump + sawtooth grit | sub-bass wobble bloom + mid fill | 23 + | **snare** | stick-on-head pop | wire rattle + shell ring | 24 + | **clap** | dark palm strike | bright release transient | 25 + | **snap** | finger-release click | skin slap body ring | 26 + | **closed hat** | stick contact tick | tight metallic sizzle | 27 + | **open hat** | metallic chip strike | sustained airy shimmer | 28 + | **ride** | bell-like ping | long shimmer wash | 29 + | **crash** | explosive metal impact | long sustained wash | 30 + | **splash** | thin high metal tick | short bright wash | 31 + | **cowbell** | stick-on-metal tink | resonant detuned ring | 32 + | **wood block** | stick contact tick | hollow wood body | 33 + | **tambourine** | frame strike | jingle rattle | 34 + 35 + Gap between DOWN and UP is BPM-locked (`flam × rn(...)` per hit) so rolls 36 + tighten at higher tempos and widen at slower ones. Kick and crash use tight 37 + 4–9 ms gaps so the punch+bloom feels unified; ride and open hi-hat use longer 38 + gaps for audible strike→shimmer separation. 39 + 40 + Commits: [`46ab9b132`](../fedac/native/pieces/notepat.mjs), [`cebfb6737`](../fedac/native/pieces/notepat.mjs) 41 + 42 + ### 1.2 Stochastic variation on every drum 43 + Per-hit jitter helpers `rj(center, frac)` / `rn(min, max)` applied across all 44 + 12 drums: 45 + 46 + - **Tone jitter**: ±3–12% on every oscillator frequency 47 + - **Volume jitter**: ±5–12% per voice 48 + - **Duration jitter**: ±5–14% on longer tails 49 + - **Pan jitter**: ±0.05 offset per DOWN/UP step for subtle stereo movement 50 + - **Flam timing jitter**: `rn(...)` inside the `setTimeout` delay for the UP step 51 + 52 + No two hits sound identical. Tambourines get the widest jitter because real 53 + tambourines never sound the same twice. 54 + 55 + Commit: [`b520136ee`](../fedac/native/pieces/notepat.mjs) 56 + 57 + ### 1.3 Animated graphic notation inside drum pads 58 + Each pad renders an animated glyph showing the drum's internal structure: 59 + tonal bars for sine/triangle/square/sawtooth layers at log-frequency Y 60 + positions, random dots for noise bursts. `PERCUSSION_NOTATION` table describes 61 + each drum's voices; `NOTATION_WAVE_RGB` maps wave types to accent colors. 62 + Drawn with per-frame random jitter so viewers see the stochastic mechanism in 63 + action while the pad is idle. 64 + 65 + Commit: [`d1064820b`](../fedac/native/pieces/notepat.mjs) 66 + 67 + ### 1.4 Kit-geometry pan (not pitch-based) 68 + Tones pan by pitch (`(semitones − 12) / 15`) — low notes left, high notes 69 + right. Drums were inheriting that same formula so a kick (C) was getting 70 + hard-panned left just because C is the lowest note. 71 + 72 + New `drumPanFor(letter, gridOffset)` combines two factors: 73 + 74 + - **Grid bias**: left grid → -0.32, right grid → +0.32 75 + - **Drum-type offset** (`DRUM_PAN_OFFSET`) per kit geometry: 76 + kick 0.00, snare -0.05, clap +0.10, snap -0.10, closed hat +0.25, 77 + open hat +0.32, ride +0.45 (far right), crash -0.45, splash -0.55 78 + (far left), cowbell +0.22, block -0.18, tambo +0.30 79 + 80 + Final pan = `clamp(gridBias + drumOffset * 0.7, ±0.9)`. Grid side dominates 81 + so both grids stay distinguishable; drum offset modulates for kit color. 82 + Wired through keyboard, touch-tap, and drag-rollover drum paths. 83 + 84 + Commit: [`665ef824f`](../fedac/native/pieces/notepat.mjs) 85 + 86 + ### 1.5 Full-screen background flash per drum 87 + Tones already painted the whole background from their note colors while held. 88 + Drums, being one-shots, never got that visual response. Now they do. 89 + 90 + - New `drumFlashes` queue (up to 8 concurrent entries, life = 14 frames 91 + ≈ 230 ms) holds `{color, frame, life}` 92 + - `flashDrum(letter)` pushes an entry on every hit 93 + - Alpha curve: full 1.0 for first 2 frames (punchy blink), then linear decay 94 + - Paint-time blend: tone layer + drum layer are weighted-summed (drums get 95 + 1.2× weight so a single hit still reads against held tones) 96 + - Tone layer snaps instantly; drum-only decays smoothly at 0.55/frame so 97 + rapid hits stack into a color wash but single hits fade out cleanly 98 + 99 + Colors sourced from existing `PERCUSSION_COLORS` palette (kick = deep orange, 100 + snare = tan, ride = silver-blue, crash = lavender, splash = pink, etc). 101 + 102 + Wired into all 4 drum trigger sites (keyboard, touch-tap, drag-rollover, 103 + reverse-playback replay). 104 + 105 + Commit: [`18dbc5986`](../fedac/native/pieces/notepat.mjs) 106 + 107 + ### 1.6 Earlier percussion work 108 + - **Percussion layout toggle**: PgUp (left grid) / PgDn (right grid) map the 109 + 7 naturals + 5 sharps of that octave to drum pads with synth recipes and 110 + optional per-drum recorded samples (existing from before this session, 111 + verified still working) 112 + - **Per-drum microphone sampling**: hold `End` to arm, then tap any drum pad 113 + to record a sample directly into `percussionSampleBank[drumName]`; release 114 + to save (confirmed working on build) 115 + - **Wub/wobble kick + sharper clap + whistle de-air**: earlier audio tuning 116 + commit [`2e4333327`](../fedac/native/pieces/notepat.mjs) 117 + 118 + --- 119 + 120 + ## 2. Reverse playback loop pedal (space bar) 121 + 122 + Pressing and holding space starts playing back the last 12 seconds of 123 + keyboard/drum hits in reverse, in real time, while the user can keep playing 124 + new notes on top. Releasing ends the phase; the next press unwinds whatever 125 + was played during that press — creating a "bounce loop" where repeated 126 + presses alternate forward→reverse→forward→reverse. 127 + 128 + Implementation: 129 + - `playbackHistory` rolling buffer (256 events / 12 s), recorded from every 130 + note + drum trigger site 131 + - `startReversePlayback()`: snapshots history, **clears** `playbackHistory` 132 + for the new phase, builds `reverseQueue` with time-mirrored delays 133 + - `stopReversePlayback()` drains the queue 134 + - `playReverseEvent()` fires the sound AND re-records it into the new phase, 135 + which is what creates the bounce loop semantics 136 + - `sim()` tick drains `reverseQueue` each frame (~16 ms resolution) 137 + - Fallback kick drum on first press (empty history) 138 + 139 + The duration of a held press effectively sets the loop's "out point" — very 140 + dynamic, very fun. 141 + 142 + Commit: [`da8a795b3`](../fedac/native/pieces/notepat.mjs) 143 + 144 + --- 145 + 146 + ## 3. USB hotplug → DJ regression fix 147 + 148 + ### Symptom 149 + Plug a USB stick with music → DJ detects it once. Unplug → DJ stops working. 150 + Re-plug → nothing happens. Browsing tracks never starts again. 151 + 152 + ### Root cause 153 + `mountMusic()` in `js-bindings.c` used `stat("/media/.").st_dev != 154 + stat("/.").st_dev` to decide "already mounted". When a USB is physically 155 + yanked, `/media` stays as a **stale mount point** — `st_dev` still differs 156 + from root, so the function returned `JS_TRUE` forever. The JS hot-plug loop 157 + then thought USB was still connected and never re-scanned. 158 + 159 + ### Fix 160 + Parse `/proc/mounts` to find the actual device backing `/media`. Verify: 161 + 162 + 1. Device file still exists (`stat` + `S_ISBLK`) 163 + 2. `opendir("/media")` succeeds 164 + 165 + If either check fails (stale mount from a yanked USB), `umount2(MNT_DETACH)` 166 + lazily before falling through to the normal mount loop. Re-plugs now get 167 + detected instead of seeing a stale "already mounted" response forever. 168 + 169 + Commit: [`325209975`](../fedac/native/src/js-bindings.c) 170 + 171 + --- 172 + 173 + ## 4. Meta-held shortcut help panel rewrite 174 + 175 + ### Problems with the old panel 176 + - Fixed 280 px width wasted screen space on wider displays 177 + - 24 single-column rows bled off the bottom on small screens 178 + - Two flat colors gave no visual grouping 179 + - Bindings were stale: `space` still listed as "kick drum" when it's now the 180 + reverse loop pedal; F1–F4 listed as "Fn+F1" when the media-key aliases 181 + had been removed weeks ago 182 + 183 + ### New panel 184 + - **9 color-coded categories**: NOTES (cyan), DRUMS (orange), WAVE (magenta), 185 + DECK (green), HOLD (yellow), TEMPO (amber), SAMPLE (pink), FX (teal), 186 + SYSTEM (gray) — colors applied to both category headers and the 187 + key-column text 188 + - **Adaptive 1/2/3 columns** based on screen width (`COL_W = 172 px`) 189 + - **Hard-clamped panel height** so it never bleeds off the bottom; overflow 190 + rows are clipped rather than pushed off-screen 191 + - **Dark/light theme-aware**: key colors brightened in dark mode, darkened 192 + in light mode for legibility on both 193 + - **Drop shadow + outline + title underline** for depth 194 + - **All bindings refreshed** to reflect current code state (space = reverse 195 + loop pedal, F1/F2/F3/F4 direct deck control, delete = clear sample bank, 196 + etc.) 197 + 198 + Commit: [`325209975`](../fedac/native/pieces/notepat.mjs) (shipped alongside 199 + the DJ fix) 200 + 201 + --- 202 + 203 + ## 5. Install-to-HD robustness 204 + 205 + From earlier in the session, before the main percussion work: 206 + 207 + - **SIGPIPE handler**: added `signal(SIGPIPE, SIG_IGN)` so websocket close 208 + doesn't kill the process (fix for observed exit=141 crash) 209 + - **setTimeout polyfill**: QuickJS has no timers. Added `__pendingTimeouts` 210 + queue flushed from `sim()` so pieces can use `setTimeout()` naturally 211 + (fix for "setTimeout is not defined" on ESC) 212 + - **Loop-unmount install path**: `force_unmount_disk()` parses `/proc/mounts` 213 + until no entries match, `blkrrpart_with_retry()` uses exponential backoff 214 + (0 → 250 → 500 → 1000 → 2000 ms), install-debug.log on tmpfs survives 215 + /mnt repartition (copied back at end of auto_install_to_hd) 216 + - **Flash verify instrumentation**: dedicated `/tmp/flash-trace.log` so the 217 + ac_log_pause() pattern no longer eats diagnostics; added size-check first 218 + in verify; bumped sync wait 500 ms → 3 s 219 + 220 + Commits: [`7c11b5ca7`](../fedac/native/src/ac-native.c), [`ea6cf8f24`](../fedac/native/src/js-bindings.c) 221 + 222 + --- 223 + 224 + ## 6. Commit list (this session, newest first) 225 + 226 + ``` 227 + 18dbc5986 feat(percussion): drums pulse full-screen background in drum colors 228 + 665ef824f feat(percussion): drums pan by kit geometry + grid bias (not tone pitch) 229 + 325209975 fix(dj): detect stale /media mount on unplug + rebuild notepat help panel 230 + cebfb6737 feat(percussion): every drum is now two-step (DOWN impact + UP release) 231 + 46ab9b132 feat(percussion): two-step clap + open hi-hat with down/up bursts 232 + 846782476 Merge remote-tracking branch 'tangled/main' 233 + da8a795b3 feat(notepat): space bar = instant-replay reverse loop pedal 234 + d1064820b feat(notepat): animated stochastic graphic notation inside drum pads 235 + b520136ee feat(percussion): stochastic per-hit variation + BPM-locked flam timing 236 + 2e4333327 fix(percussion): wub kick + sharper clip-clap + aggressive whistle de-air 237 + 7c11b5ca7 fix(native): loop unmount until /mnt stack is clean + tmpfs debug log 238 + ``` 239 + 240 + All commits pushed to both `origin` (github) and `tangled` (knot) remotes. 241 + 242 + --- 243 + 244 + ## 7. Build pipeline state 245 + 246 + Current in-flight build: **`57b818dd-8`** on ref `18dbc5986`, triggered via 247 + `POST /native-build` to `oven.aesthetic.computer`. Carries every feature 248 + above in one artifact. 249 + 250 + Watcher `/tmp/watch-build.sh` (PID at time of report) polls the oven every 251 + 20 s and will auto-flash via `ssh me@172.17.0.1 'cd 252 + /home/me/aesthetic-computer/fedac/native && ./ac-os pull'` on success. 253 + 254 + Several earlier builds in this session were cancelled mid-flight so 255 + sequential features could be combined into one flash cycle: 256 + 257 + - `1ff8e31b-7` — cancelled (was running on stale `f58d1bc57` after push/merge 258 + confusion; reconciled tangled/main into origin/main as `846782476`) 259 + - `2491da75-5` (clap + hat only) — cancelled to combine with the remaining 260 + 10 drums 261 + - `23e163b2-7` (all 12 two-step drums) — cancelled to combine with USB 262 + hotplug + help panel fix 263 + - `121e68d3-9` (+ hotplug + help panel) — cancelled at 55% kernel stage to 264 + combine with drum pan geometry 265 + - `fd5cc814-a` (+ drum pan) — cancelled at 55% kernel stage to combine with 266 + full-screen drum visuals 267 + - `57b818dd-8` (+ drum visuals) — **running to completion** (combined final) 268 + 269 + --- 270 + 271 + ## 8. Outstanding / deferred 272 + 273 + - `native-notepat-*` → `ac-native-*` OTA artifact rename (user flagged as 274 + "vestigial" earlier; analysis done, implementation deferred to avoid 275 + mid-session pipeline disruption) 276 + - Delete key in sample mode currently clears only the melodic sample bank, 277 + not `percussionSampleBank` — small one-line follow-up if desired