# Performance Session — 2026-07-31 Android tablet remote: fader stutter + timecode feedback fluidity. Both root causes found and fixed. Companion docs: `FEEDBACK_STUTTER_BUG.md` (original symptom report, now resolved), `BISECT_LOG.md` (run-by-run test record). ## The two problems (they were separate) ### 1. Fader stutter under 60hz feedback → appendLog **Root cause:** `appendLog()` in `ArrangeFunctionsWebSocket.java` ran on every in/out WebSocket message (~110/sec during a drag with feedback streaming), even though the log panel is hidden by default: - `TextView.append()` on a selectable (editable/spannable) TextView — cost grows as the text grows - 2-3 posted main-thread runnables per message (~300/sec), queued ahead of touch events - Every ~0.5s, the line-trim: full 350-line copy/split/join/setText — a multi-millisecond spike that blew the 16ms frame budget. Periodic spikes = the rhythmic stutter. **Proof:** A/B tested — kill switch off = smooth, on = stutter returns immediately. Second time this exact code was convicted (an earlier session already replaced get+concat+setText with append; the remaining per-message work was still the culprit). **Fix (current):** `DEBUG_DISABLE_APPEND_LOG = true` — appendLog fully off. Side effect: log panel shows nothing. Shippable fix still TODO (see below). ### 2. Chunky timecode readout → REAPER buffer size **Root cause (found by Paul):** REAPER emits OSC feedback per audio block. A high audio buffer size makes feedback updates leave REAPER in uneven bursts — no client-side rendering can smooth a chunky source. Lowering the buffer restored fluid timecode. **Rule of thumb:** if timecode/dB readouts ever look chunky again on any client (Android, iOS, desktop log), check REAPER's audio buffer size FIRST, before suspecting client code. ## Changes made this session | Change | File | Status | |---|---|---| | Kill switch: appendLog off | `arrange/ArrangeFunctionsWebSocket.java` | **Active** (`DEBUG_DISABLE_APPEND_LOG = true`) | | Kill switch: per-message `Log.d` off | same | **Active** (`DEBUG_DISABLE_RECEIVE_LOGCAT = true`) — no felt difference, kept as freebie | | Kill switch: network activity-flash | same | Inactive (`false`) — exonerated; tablet's Activity button (default OFF) already gated it | | Kill switch: feedback setText | same | Inactive (`false`) — exonerated, 60hz setText was affordable | | Kill switch: fader glow / software layer | `widgets/FaderWidgetView.java`, `widgets/FocusFaderWidgetView.java` | Inactive (`false`) — exonerated by Paul, glow restored | | Kill switch: gradient fill alloc | same two files | Inactive (`false`) — exonerated, gradient restored | | **FocusFeedbackWidgetView rewritten: TextView → bare View + `canvas.drawText`** | `widgets/FocusFeedbackWidgetView.java` | **Permanent.** Update = string compare + field assign + invalidate; no text Layout object per update; same-frame updates collapse to one draw. All sizing/pinch/font-weight/color behavior preserved. | | Color picker: explicit branch for new view type | `arrange/ContextMenuViewLogic.java` | Permanent — was matching via `instanceof TextView`, which the rewrite would have silently broken | ## The lesson (codebase rule) Main thread = one queue for touch, draw, and posted runnables. Per-message work is fine if **small and constant** (setText, a flash, a field assign). Banned on the message stream: anything **unbounded** (grows with history) or **spiky** (periodic big jobs). appendLog was both, for an invisible panel. ## Follow-ups (not yet done) 1. **Shippable appendLog fix** — replace the kill switch: log only while the panel is visible; bounded ring buffer for history so opening the panel isn't empty; then delete `DEBUG_DISABLE_APPEND_LOG`. 2. Decide fate of the exonerated kill switches (probably delete for cleanliness; they're documented in BISECT_LOG.md if ever needed again). 3. Optional hygiene: create the fader fill gradient once (setLocalMatrix) instead of per-onDraw; gate `Log.d` behind `BuildConfig.DEBUG` instead of a custom flag. 4. Consider documenting the REAPER-buffer/feedback-cadence relationship in CLAUDE.md's DAW integration section.