60c8255c2d
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>
74 lines
4.1 KiB
Markdown
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.
|