bc8bcf78b7
**Most UI text drew with raylib's built-in font, not the loaded one.** Thirteen call sites used DrawText/MeasureText directly instead of the project's DrawTextScaled/MeasureTextScaled wrappers, so the cursor and marker readouts, spectrum panel labels and export toast rendered from raylib's default bitmap atlas. On desktop that merely looked slightly off; on web it renders as garbage. The only raw calls left are the two fallbacks inside the wrappers themselves. **The rail tooltip held a dangling pointer.** RailButton stored a TextFormat() result for the deferred draw pass, and raylib documents that string as expiring once TextFormat has been called a few more times — which it has by then. It copies into owned storage now. **Loading a large file in the browser looked like a crash.** The web build computes its whole STFT in one synchronous pass, with no frame presented in between, so nothing drawn on the canvas during that window ever reaches the screen. A notice now goes into the host DOM instead, which the browser paints independently, and yields once so that paint actually happens before the work starts. Because that yield unwinds the stack under ASYNCIFY, the load block is guarded by stftBusy — without it the re-entered main loop calls ComputeSTFTInit again and frees the STFT the suspended call is still building. **Removed EXPORTED_FUNCTIONS from the web link flags.** It replaces emscripten's default export list rather than extending it, so everything unnamed is dead-code-eliminated. The upload callback stays reachable via the EMSCRIPTEN_KEEPALIVE already on its definition. **Adds serve_web.py**, a dev server that sends no-store. Browsers cache .wasm hard enough that a plain reload runs a stale module while the page looks freshly loaded — which makes a rebuild appear to change nothing. That cost most of a debugging session today: three separate fixes were tested against a binary that never changed, each returning an identical fault at an identical address. README now points at it and says why. Documents the remaining web issue in known_bugs.md: large captures still corrupt text after loading. Leading theory is ALLOW_MEMORY_GROWTH reallocating the heap mid-load and invalidating pointers cached across that moment, which fits the symptom being text specifically. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8ZWfr5XZyyDttvkhJUgHN
113 lines
5.5 KiB
Markdown
113 lines
5.5 KiB
Markdown
# Known bugs & rough edges
|
|
|
|
Behaviour that is unspecified, awkward, or known-imperfect — as distinct from
|
|
outright breakage. Each entry says what happens, why, and what a real fix would
|
|
need to decide.
|
|
|
|
---
|
|
|
|
## Large WAVs in the browser: text corruption and a long hang
|
|
|
|
**Status:** open. Reproduces on the web build only; desktop is unaffected.
|
|
|
|
Loading a large capture through the web UI (drag-drop or the Open file button)
|
|
produces garbled glyphs in menu titles, tooltips and axis labels, and the page
|
|
stops responding for a long stretch. Small files load and render correctly, and
|
|
the corruption appears *after* the load rather than at startup — so it is
|
|
triggered by the size of the work, not by the build being broken.
|
|
|
|
The hang is understood and partly by design: the web build computes its entire
|
|
STFT in one synchronous pass (see the `__EMSCRIPTEN__` branch in the main loop),
|
|
because the desktop's incremental fill depends on idle main-loop frames the
|
|
browser doesn't hand back the same way. A DOM overlay now warns before it
|
|
starts, but the page genuinely is frozen until it finishes. A real fix means
|
|
chunking that work across frames or moving it to a Web Worker.
|
|
|
|
The **corruption** is the unexplained part. The leading theory is heap growth:
|
|
the build links with `ALLOW_MEMORY_GROWTH=1`, and a large file forces the wasm
|
|
heap to grow mid-load. Growth reallocates the backing `ArrayBuffer`, which
|
|
invalidates every cached view and raw pointer held across that moment — so
|
|
anything retaining a `char*` or a texture-side pointer from before the growth
|
|
would read garbage afterwards. Font glyph data reached through raylib's atlas is
|
|
a plausible casualty, which fits the symptom being *text* specifically.
|
|
|
|
Worth checking first:
|
|
- Whether `INITIAL_MEMORY` large enough to avoid growth entirely makes it go
|
|
away. That would confirm the theory cheaply, at the cost of a bigger initial
|
|
allocation.
|
|
- Whether the font atlas survives a deliberate `sbrk`-forced growth.
|
|
- SAFE_HEAP + ASSERTIONS=2 (`build_web.sh` debug path) to catch the first bad
|
|
access rather than the downstream symptom.
|
|
|
|
**Testing note:** always serve the web build with `serve_web.py`, never
|
|
`python3 -m http.server`. Browsers cache `.wasm` hard enough that a plain reload
|
|
runs a stale module, which makes rebuilds look like no-ops and has already
|
|
burned significant time chasing bugs that were fixed.
|
|
|
|
---
|
|
|
|
## Playhead vs. a selection edited mid-playback
|
|
|
|
**Status:** partially addressed; underlying semantics still undefined.
|
|
|
|
Playback hands a *snapshot* of the selected region to the audio device — the
|
|
samples are copied, bandpassed, and normalised up front, so the sound coming out
|
|
of the speakers is fixed the moment **Space** is pressed. The selection box,
|
|
however, stays live and editable while that audio plays.
|
|
|
|
Previously the playhead marker was drawn against the *live* `app.sel`, so moving
|
|
or resizing the selection during playback made the marker jump, run off the end,
|
|
or scale to a region that had nothing to do with what was audible. The playhead
|
|
is now measured against `playSelStart` / `playSelEnd` / `playDuration`, captured
|
|
at `PlaySelectedRegion()` time, so it tracks the audio that is actually playing.
|
|
|
|
What remains undefined is the *product* question, not the drawing math:
|
|
|
|
- If the user drags the selection somewhere else mid-playback, should the audio
|
|
follow (restart / re-seek against the new region), or should playback keep
|
|
going with the old buffer and the marker stay where it is (current behaviour)?
|
|
- Should editing the selection during playback simply stop playback?
|
|
- Should the playhead remain visible when the region it refers to is scrolled
|
|
off-screen, or has been replaced by a selection elsewhere in the file?
|
|
|
|
Current behaviour is the conservative reading: **the sound wins**. The marker
|
|
always describes real audio, and a mid-playback edit is treated as staging the
|
|
*next* thing to play rather than modifying the current one. That is defensible
|
|
but was never explicitly chosen, and the UI gives no feedback that the box on
|
|
screen and the audio in flight have diverged.
|
|
|
|
Related: a sub-threshold click *inside* an existing selection deliberately does
|
|
not clear it (`hoverInsideSelection` in `spectrogram.c`), because silently
|
|
clearing changes what **Space** would play. A click on empty space still resets
|
|
to full range.
|
|
|
|
**Touches:** `audio.c` (`PlaySelectedRegion`), `spectrogram.c` (playhead
|
|
advance, scope cursor), `render.c` (`DrawPlayhead`), `spectrogram_types.h`
|
|
(`playSelStart` / `playSelEnd` / `playDuration`).
|
|
|
|
---
|
|
|
|
## Long-file zoom sharpness lags the zoom gesture
|
|
|
|
**Status:** working as designed, but reads as a bug.
|
|
|
|
`ComputeSkipFactor()` (`stft.c`) strides the initial STFT pass for long files —
|
|
every 8th segment past 10 minutes — so the overview loads promptly. The missing
|
|
segments are filled at full resolution afterwards: the visible range first, then
|
|
a background sweep of the whole file.
|
|
|
|
The practical effect is that a hard zoom into a 30-minute file can look blocky
|
|
for a moment before the foreground fill catches up and it sharpens. The fill is
|
|
gated on `view.end - view.start <= 0.25f`, so it only runs once reasonably zoomed
|
|
in. If a view stays blocky indefinitely, the fill is not reaching that range and
|
|
that *is* a real bug worth chasing.
|
|
|
|
---
|
|
|
|
## Load time on long files is unbounded and unreported
|
|
|
|
A 30-minute 48 kHz file spends a long time in `Processing…` before the UI is
|
|
usable, and the percentage indicator advances non-linearly (the strided overview
|
|
completes fast, the high-res fill does not). There is no cancel. Headless/scripted
|
|
runs must wait this out; see `AGENTS.md`.
|