Bisect session with per-suspect kill switches (see BISECT_LOG.md): - appendLog was the confirmed stutter culprit — ran per-message (~110/sec) on the main thread even with the log panel hidden; A/B verified. Disabled via DEBUG_DISABLE_APPEND_LOG; shippable visible-only fix still TODO. - Per-message Log.d also disabled (freebie, no felt difference). - Glow, gradient fill, network flash, feedback setText all exonerated and restored; switches left in place at false. - FocusFeedbackWidgetView rewritten: TextView -> bare View + canvas.drawText. 60hz updates are now field-assign + invalidate, no per-update text Layout. - ContextMenuViewLogic: explicit color-picker branch for the new view type. - Timecode chunkiness root cause was REAPER audio buffer size (source-side burst cadence), documented in PERF_SESSION_2026-07-31.md; stutter bug report stamped RESOLVED. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.1 KiB
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)
- 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. - Decide fate of the exonerated kill switches (probably delete for cleanliness; they're documented in BISECT_LOG.md if ever needed again).
- Optional hygiene: create the fader fill gradient once (setLocalMatrix)
instead of per-onDraw; gate
Log.dbehindBuildConfig.DEBUGinstead of a custom flag. - Consider documenting the REAPER-buffer/feedback-cadence relationship in CLAUDE.md's DAW integration section.