Files
Paul Lipscomb 60c8255c2d Android perf: fix fader stutter (appendLog), drawText feedback widget, bisect docs
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>
2026-07-31 17:46:07 -05:00

74 lines
4.1 KiB
Markdown

# 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.