**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
**File loading on the web.** The browser build's filesystem is an in-memory
sandbox, so the file browser could only ever list what the page itself had
written — useless for opening a capture off the user's disk. Adds
Platform_NeedsFileUpload / RequestFileUpload / TakeUploadedFile: no-ops on
desktop, and on web they drive a real <input type="file">, copy the chosen
bytes into MEMFS, and hand the path back through a callback the main loop
polls. The ordinary load path takes it from there, so mLnL parsing and
collision detection work identically.
All three entry points (the O key, File -> Open, and the empty-state
button) route through the same place, so they can't disagree about what
"open" means. ccall/cwrap/FS and the callback symbol needed explicit
export — recent emscripten omits them by default and the failure is
silent until someone clicks the button.
The empty-state banner was centred with the old 320px sidebar's width
hardcoded and never measured its text, so it sat well off-centre once the
rail shrank to 46px. It now measures and centres against the area right of
the rail, and says something useful on web.
**Grid was not locked to the axes.** It drew a fixed 10x8 even divisions of
the viewport rectangle, with no idea what time or frequency those lines
fell on — so they marked arbitrary values and slid continuously while
panning, never coinciding with the labels. Both axes now draw at real
values from a 1-2-5 ladder, and the time labels moved onto that same
ladder (they were at 11 fixed fractions of the view, printing values like
"3.47s" that matched no gridline). Grid and labels share one set of
spacing helpers so they cannot drift apart again.
**Cursor dB readout was a single bin.** On an OFDM burst that swings ~30 dB
between adjacent subcarriers and symbols, so the number reported where the
cursor landed rather than the level of the signal under it. Now averaged
over +/-3 segments x +/-6 bins (~76 Hz x 300 ms at 12 kHz / 2048, under a
third of the narrowest mLink channel, so it stays inside one signal).
Simulated against Rayleigh-distributed subcarriers this cuts the standard
deviation from 5.6 dB to 0.44 dB.
The mean is taken over power and converted afterwards: averaging dB values
is a geometric mean of power and read ~2 dB low. The window is sized in
STFT cells rather than screen pixels, which would otherwise mean something
different at every zoom — one pixel spans hundreds of segments zoomed out
and a fraction of a bin zoomed in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8ZWfr5XZyyDttvkhJUgHN
- README: rewrite Build section for the hand-written Makefile (make /
make DEBUG=1 / run / test / bench). Add a "System dependencies" table
with the X11+GL dev packages per distro (apt/dnf/pacman/zypper/apk),
since a clone won't build without them and the error is otherwise
cryptic. Fix stale ./bin/Debug paths to ./bin/Release.
- Makefile: add `make check-deps` — probes for the required X11/GL dev
headers and prints the install command for the detected distro on
failure (via /etc/os-release).
- build_web.sh: auto-discover app modules from src/*.c (drop the
hardcoded APP_MODULES list that had to be hand-synced), matching the
desktop Makefile. Excludes the desktop platform backends. Remove the
stale rspektrum.make reference.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Commit the accumulated working-tree changes as one snapshot.
- Headless render: `--render OUT.png INPUT.wav` draws the spectrogram
(full window, or `--pane` for the spectrogram pane only) to a PNG
with no visible window. Options: `--annotations`/`--no-annotations`,
`--annotation-opacity`, `--width`/`--height`.
- mLnL annotations: parse the optional `mLnL` RIFF chunk (schema v2)
and render tx_frame/assertion/control overlays, a timeline lane, and
a waveform-scope echo, with hover tooltips on the spectrogram,
timeline, and scope.
- sched_offset_ms: parse the per-frame intent->air latency and surface
it in the hover tooltips (boxes stay air-anchored upstream).
- Supporting: build wiring (rspektrum.make), shared types/headers,
web-build and capture-script tweaks, and removal of the old
synchrosqueezing LaTeX doc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the Emscripten build behave like the desktop app for loading, window
scaling, and exporting. All changes are #ifdef __EMSCRIPTEN__-gated or no-op on
desktop, so native behavior is unchanged.
Scaling:
- Drop FLAG_WINDOW_HIGHDPI on web. The emscripten-GLFW shim forces a fixed
pixel canvas style (!important) when HiDPI-aware, overriding the shell's
100vw/100vh CSS so the canvas can't fill the page; raylib's own resize/window
callbacks also disagree about dividing by devicePixelRatio, desyncing the
framebuffer from the reported screen size.
- Sync raylib's window size to window.innerWidth/innerHeight each frame via
SetWindowSize (guarded against no-op churn). This keeps screen size, GL
viewport, and projection consistent, so the UI fills the viewport and reflows
on resize like the desktop window.
Loading:
- Compute the full-resolution STFT synchronously when a file loads instead of
the desktop overview-then-deferred-high-res path, which relied on many
main-loop iterations yielding to the browser and appeared to stall partway.
- Allow the wasm heap to grow (INITIAL_MEMORY + ALLOW_MEMORY_GROWTH) so longer
recordings fit now that everything is computed up front.
Exports:
- Add Platform_OfferFileToUser(): no-op on desktop (file is already on disk);
on web it reads the just-written file from MEMFS and triggers a browser
download, then unlinks the temp copy. Wired into PNG and WAV export, which
now show just the filename in the status message.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The web build had bit-rotted since the last successful build (Apr 12):
- build_web.sh only compiled spectrogram.c + platform_web.c, but commit
3a8f20b split the code into 9 modules — link failed with undefined
symbols. Now compiles all app modules (kept in sync with rspektrum.make,
swapping platform_linux -> platform_web).
- Dropped -DPLATFORM_WEB from the app-module compile/link: the platform
refactor (b6942d8) added a `Platform` enum whose PLATFORM_WEB enumerator
collides with the macro. Web behavior is selected by linking
platform_web.c, not the macro, so the define was never needed by our code
(raylib still gets it in its own build).
- Removed the stale `fonts/DejaVuSansMono.ttf` preload (font moved to
resources/fonts/); resources@resources already bundles it and the runtime
loads it relative to the resources dir.
- platform_web.c: include <errno.h> for ENOSYS (this stub had never compiled).
- build_web.sh: skip the slow raylib rebuild when libraylib.a already exists.
Verified: `./build_web.sh release` exits 0 and produces html/js/wasm/data;
font is packaged; all artifacts serve 200 via python http.server and the
wasm has valid magic bytes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move all Unix-specific syscalls (fork, execvp, waitpid, strerror, /tmp)
into a platform layer so the same spectrogram.c can target Windows
(CreateProcess) and WebAssembly (stubs). Key changes:
- src/platform.h: public API with spawn handle, error codes, path helpers
- src/platform_linux.c: fork/execvp implementation, /tmp, strerror
- src/platform_win32.c: CreateProcess implementation, temp dir lookup
- src/platform_web.c: stub implementations (no subprocess support on web)
- src/spectrogram.c: consume only platform.h; replace pid_t/fork/execvp
with Platform_SpawnChild/WaitForChild; use Platform_GetTempDir() for
/tmp path; replace strdup with malloc/memcpy for portability
- Build: add platform_*.c to gmake, Premake, and build_web.sh
- Add incremental STFT processing with loading overlay and progress bar
- Add auto-scaling amplitude (max dB, max-40dB floor) for low-signal files
- Replace fixed bitmap text with TTF font scaled by window size for crisp rendering at any resolution
- Fix command-line file path resolution relative to original working directory
- Scale file browser dialog dimensions by GetUIScale() to prevent text overlap
- Disable ESC key closing the window
- Fix FFT size changes to restart STFT computation