Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

notepat: fix needle-is-playhead + UDP MIDI default ON + bigger QR

Three fixes for the live-test feedback on turbo-swallow-sail:

1. UDP MIDI relay defaulted to TRUE in loadUdpMidiConfig().
Previously the flag required an explicit {"udpMidiBroadcast": true}
in /mnt/config.json, so every device that was OTA-updated into
the new build kept a pre-existing config (no flag) and silently
stayed off, showing "UDP MIDI OFF" even with WiFi + socket healthy.
Now the flag defaults on; an explicit {"udpMidiBroadcast": false}
still opts out.

2. Waveform strip — needle is now the PLAYHEAD where audio is written.
Previously the strip spanned the full width with oldest→newest
left-to-right, and the center needle was just a decoration overlaid
on the middle of a 4-second buffer. Now:
normal mode → the NEW sample lands immediately adjacent to the
needle; older samples extend away (left of needle).
Right side of strip stays empty — no future audio
to display.
reverse mode → when spacebar is held, the active half flips to
the RIGHT side of the needle. The new sample still
sits at the needle; past samples now extend to
the right, matching the backwards-replay cursor's
direction of travel.
This makes the needle read as a turntable stylus: "now" is always
under it, "then" stretches away in the direction of the playhead.

3. QR code bigger — scale=2 + 2-module quiet zone = 50×50 px. The top
status bar grows from 26 to 54 px tall, text row re-centered via
barY=22. Short URL still fits in version-1 (21 modules), so the
Reed-Solomon cost is unchanged — graph_qr's encode cache still
applies.

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

+70 -29
+23 -14
fedac/native/pieces/notepat.mjs
··· 1821 1821 } 1822 1822 1823 1823 function loadUdpMidiConfig(system) { 1824 + // Default ON: every notepat install should broadcast to the amxd plugin 1825 + // unless the user explicitly opts out by writing 1826 + // {"udpMidiBroadcast": false} 1827 + // into /mnt/config.json. Previously the flag defaulted to false and 1828 + // required `true` to enable, which silently blocked the relay on every 1829 + // OTA-updated device whose pre-existing config.json predates the flag. 1830 + udpMidiBroadcast = true; 1824 1831 try { 1825 1832 const raw = system?.readFile?.("/mnt/config.json"); 1826 - if (!raw) { 1833 + if (!raw) return; 1834 + const cfg = JSON.parse(raw); 1835 + if (cfg.udpMidiBroadcast === false || cfg.udpMidiBroadcast === "false") { 1827 1836 udpMidiBroadcast = false; 1828 - return; 1829 1837 } 1830 - const cfg = JSON.parse(raw); 1831 - udpMidiBroadcast = cfg.udpMidiBroadcast === true || cfg.udpMidiBroadcast === "true"; 1832 1838 } catch (_) { 1833 - udpMidiBroadcast = false; 1839 + // Keep default (true) on parse failure. 1834 1840 } 1835 1841 } 1836 1842 ··· 3340 3346 } 3341 3347 3342 3348 // === STATUS BAR === 3343 - // Bar is tall enough to fit a 25px QR code (21-module QR version 1 with 3344 - // a 2-module quiet-zone margin, scale=1) in the top-left corner. 3345 - const topBarH = 26; 3346 - const barY = 10; // vertical offset for status text — matrix font is ~7px tall 3349 + // Bar is tall enough to fit a 50×50 QR code (21-module version-1 QR + 3350 + // 2-module quiet-zone margin at scale=2) in the top-left corner, with 3351 + // the text row sitting vertically centered below the QR's midline so 3352 + // readability at arm's length works on a phone-camera scan too. 3353 + const topBarH = 54; 3354 + const barY = 22; // center of text row — leaves 2 px top/bottom padding for status text at size=1 3347 3355 3348 3356 ink(BAR_BG[0], BAR_BG[1], BAR_BG[2]); 3349 3357 box(0, 0, w, topBarH, true); ··· 3368 3376 if (reserveSysBrt >= 0) statusRightReserve += 4 + 16 + 2 + 3 * CH; 3369 3377 const statusRightLimit = Math.max(80, w - statusRightReserve - 8); 3370 3378 3371 - // Left: tiny QR code → notepat.com (25x25 at scale=1), then label. 3372 - // Clicking anywhere in the label zone still jumps to prompt piece. 3379 + // Left: QR code → notepat.com, scannable at arm's length from a phone 3380 + // camera. Scale-2, version-1 with 2-module quiet zone = 50×50 px. C 3381 + // side caches the Reed-Solomon encoding so only the module-grid blit 3382 + // is per-frame cost. 3373 3383 if (globalThis.qr) { 3374 - // qr() handles its own white background + margin. 3375 - globalThis.qr("https://notepat.com", 1, 1, 1); 3384 + globalThis.qr("https://notepat.com", 2, 2, 2); 3376 3385 } 3377 - const qrW = 26; // 25px QR + 1px padding before label 3386 + const qrW = 54; // 50px QR + 2px left inset + 2px right padding before label 3378 3387 const labelX = qrW + 4; 3379 3388 const labelW = 48; // "notepat.com" label width in matrix font at size=1 3380 3389 const npHovered = hoverX >= 0 && hoverX <= labelX + labelW && hoverY < topBarH;
+47 -15
fedac/native/src/js-bindings.c
··· 1645 1645 graph_ink(g, (ACColor){80, 80, 90, 120}); 1646 1646 graph_line(g, x, midY, x + w - 1, midY); 1647 1647 1648 - if (copy && want_len > 0) { 1648 + // Needle position within strip (offset 0..w from x). 1649 + int needle_off = (int)((double)w * needle_frac + 0.5); 1650 + if (needle_off < 0) needle_off = 0; 1651 + if (needle_off >= w) needle_off = w - 1; 1652 + int needle_x = x + needle_off; 1653 + 1654 + // Layout principle: the NEW sample (most recent in the ring) lands 1655 + // immediately adjacent to the needle, and older samples extend AWAY 1656 + // from the needle along the active half. The other half stays empty 1657 + // (background). This matches a record-player intuition: the needle 1658 + // is the playhead and "now" sits right under it. 1659 + // 1660 + // normal mode (reverse=0): active half = LEFT (newest just-left of needle) 1661 + // reverse mode (reverse=1): active half = RIGHT (newest just-right of needle, 1662 + // past tail extends further right as the held 1663 + // spacebar replays backward in time) 1664 + // 1665 + // If needle is at the strip edge, we degrade gracefully: active half 1666 + // covers the whole width. 1667 + int active_w; 1668 + int active_start_off; // pixel offset within strip where the active half begins 1669 + if (!reverse) { 1670 + active_w = needle_off > 0 ? needle_off : w; 1671 + active_start_off = needle_off > 0 ? 0 : 0; 1672 + } else { 1673 + active_w = (w - needle_off) > 0 ? (w - needle_off) : w; 1674 + active_start_off = (w - needle_off) > 0 ? needle_off : 0; 1675 + } 1676 + 1677 + if (copy && want_len > 0 && active_w > 0) { 1649 1678 int amp = (int)((double)h * 0.45); 1650 1679 if (amp < 1) amp = 1; 1651 - double samples_per_col = (double)want_len / (double)w; 1680 + double samples_per_col = (double)want_len / (double)active_w; 1652 1681 1653 - for (int col = 0; col < w; col++) { 1654 - int col_idx = reverse ? (w - 1 - col) : col; 1655 - int i0 = (int)((double)col * samples_per_col); 1656 - int i1 = (int)((double)(col + 1) * samples_per_col); 1682 + for (int col = 0; col < active_w; col++) { 1683 + // Distance from the needle (0 = adjacent to needle = newest sample). 1684 + int dist_from_needle = !reverse ? (active_w - 1 - col) : col; 1685 + // Map dist→sample range: dist 0 ⇢ samples near want_len-1 (newest), 1686 + // dist active_w-1 ⇢ samples near 0 (oldest). 1687 + int i1 = want_len - (int)((double)dist_from_needle * samples_per_col); 1688 + int i0 = want_len - (int)((double)(dist_from_needle + 1) * samples_per_col); 1689 + if (i0 < 0) i0 = 0; 1657 1690 if (i1 > want_len) i1 = want_len; 1658 - if (i0 < 0) i0 = 0; 1691 + if (i1 <= i0) continue; 1692 + 1659 1693 float peak = 0.0f; 1660 1694 for (int i = i0; i < i1; i++) { 1661 1695 float a = copy[i]; ··· 1665 1699 if (peak > 1.0f) peak = 1.0f; 1666 1700 int bar_h = (int)(peak * (float)amp + 0.5f); 1667 1701 if (bar_h < 1) bar_h = 1; 1668 - int r = 120 + (int)(peak * 140.0f + 0.5f); if (r > 255) r = 255; 1669 - int gc = 120 + (int)(peak * 80.0f + 0.5f); if (gc > 255) gc = 255; 1670 - int b = 90 + (int)((1.0f - peak) * 120.0f + 0.5f); if (b > 255) b = 255; 1702 + int r = 120 + (int)(peak * 140.0f + 0.5f); if (r > 255) r = 255; 1703 + int gc = 120 + (int)(peak * 80.0f + 0.5f); if (gc > 255) gc = 255; 1704 + int b = 90 + (int)((1.0f - peak) * 120.0f + 0.5f); if (b > 255) b = 255; 1671 1705 graph_ink(g, (ACColor){(uint8_t)r, (uint8_t)gc, (uint8_t)b, 220}); 1672 - graph_line(g, x + col_idx, midY - bar_h, x + col_idx, midY + bar_h); 1706 + int draw_x = x + active_start_off + col; 1707 + graph_line(g, draw_x, midY - bar_h, draw_x, midY + bar_h); 1673 1708 } 1674 1709 } 1675 1710 if (copy) free(copy); 1676 1711 1677 - // Playhead needle 1678 - int needle_x = x + (int)((double)w * needle_frac + 0.5); 1679 - if (needle_x < x) needle_x = x; 1680 - if (needle_x >= x + w) needle_x = x + w - 1; 1712 + // Playhead needle — draw last so it sits on top of the bars. 1681 1713 graph_ink(g, (ACColor){240, 80, 80, 220}); 1682 1714 graph_line(g, needle_x, y, needle_x, y + h - 1); 1683 1715