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>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user