Files
rspektrum/build_web.sh
T
tyler bc8bcf78b7 fix: web text corruption, blocking-load notice, no-cache dev server
**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
2026-08-12 18:38:59 -07:00

133 lines
5.2 KiB
Bash
Executable File

#!/bin/bash
# Build spectrogram viewer for the web using Emscripten
# Usage: ./build_web.sh [debug|release]
set -e
# Get script directory (project root)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
BUILD_TYPE=${1:-release}
RAYLIB_DIR="$SCRIPT_DIR/build/external/raylib-master"
SRC_DIR="$SCRIPT_DIR/src"
BUILD_DIR="$SCRIPT_DIR/bin/web"
RAYLIB_BUILD="$BUILD_DIR/raylib_build"
OUTPUT="$BUILD_DIR/rspektrum.html"
# Create build directories
mkdir -p "$BUILD_DIR" "$RAYLIB_BUILD"
echo "=== Building Spectrogram Viewer for Web ($BUILD_TYPE) ==="
echo "Raylib dir: $RAYLIB_DIR"
# Common compile flags for raylib
RAYLIB_CFLAGS="-Os -Wall -DPLATFORM_WEB -DGRAPHICS_API_OPENGL_ES2"
if [ "$BUILD_TYPE" = "debug" ]; then
RAYLIB_CFLAGS="-g -O0 -Wall -DPLATFORM_WEB -DGRAPHICS_API_OPENGL_ES2"
fi
echo "=== Step 1: Compiling raylib for web ==="
# raylib rarely changes; skip the (slow) rebuild if the archive already exists.
# Delete "$RAYLIB_BUILD/libraylib.a" to force a clean raylib rebuild.
if [ ! -f "$RAYLIB_BUILD/libraylib.a" ]; then
# Compile raylib source files
emcc -c "$RAYLIB_DIR/src/rcore.c" $RAYLIB_CFLAGS -o "$RAYLIB_BUILD/rcore.o"
emcc -c "$RAYLIB_DIR/src/rshapes.c" $RAYLIB_CFLAGS -o "$RAYLIB_BUILD/rshapes.o"
emcc -c "$RAYLIB_DIR/src/rtextures.c" $RAYLIB_CFLAGS -o "$RAYLIB_BUILD/rtextures.o"
emcc -c "$RAYLIB_DIR/src/rtext.c" $RAYLIB_CFLAGS -o "$RAYLIB_BUILD/rtext.o"
emcc -c "$RAYLIB_DIR/src/rmodels.c" $RAYLIB_CFLAGS -o "$RAYLIB_BUILD/rmodels.o"
emcc -c "$RAYLIB_DIR/src/raudio.c" $RAYLIB_CFLAGS -o "$RAYLIB_BUILD/raudio.o"
# Create static library
cd "$RAYLIB_BUILD"
emar rcs libraylib.a rcore.o rshapes.o rtextures.o rtext.o rmodels.o raudio.o
cd "$SCRIPT_DIR"
else
echo "libraylib.a found, skipping raylib rebuild"
fi
# Compile the application modules (web version — no subprocess support).
# platform_web.c provides stub implementations for spawn/error functions.
#
# Auto-discovered from src/*.c (same as the desktop Makefile) so adding a module
# needs no edit here. The web target keeps platform_web.c and drops the desktop
# backends (platform_linux.c / platform_win32.c).
echo "=== Step 2: Compiling spectrogram modules for web ==="
cd "$SCRIPT_DIR"
APP_MODULES=""
for f in "$SRC_DIR"/*.c; do
m=$(basename "$f" .c)
case "$m" in
platform_linux|platform_win32) continue ;; # desktop-only backends
esac
APP_MODULES="$APP_MODULES $m"
done
APP_OPT="-Os"
if [ "$BUILD_TYPE" = "debug" ]; then
APP_OPT="-g -O0"
fi
APP_INCLUDES="-I$RAYLIB_DIR/src -I$RAYLIB_DIR/src/external -I$RAYLIB_DIR/src/external/glfw/include -Iinclude"
# NOTE: do NOT pass -DPLATFORM_WEB to the app modules. Our code selects web
# behavior by linking platform_web.c (not via the macro), and PLATFORM_WEB is
# also an enumerator in platform.h — defining it as a macro breaks that enum.
# (raylib itself is built with -DPLATFORM_WEB above; it needs the macro.)
OBJECTS=""
for m in $APP_MODULES; do
echo " CC $m.c"
emcc -c $APP_OPT -Wall $APP_INCLUDES \
"$SRC_DIR/$m.c" -o "$RAYLIB_BUILD/$m.o"
OBJECTS="$OBJECTS $RAYLIB_BUILD/$m.o"
done
# Linker flags
# The font lives at resources/fonts/DejaVuSansMono.ttf and is loaded relative to
# the resources dir at runtime, so the resources@resources preload covers it.
# INITIAL_MEMORY + ALLOW_MEMORY_GROWTH: the web build now computes the full STFT
# up front, so the heap must be able to grow for longer recordings.
# ASYNCIFY_STACK_SIZE: the whole main loop runs under ASYNCIFY, so every yield
# copies the live C stack into this buffer. The 4 KB default is far too small
# for a stack that runs through the render/STFT call chain — overflowing it
# corrupts the heap and surfaces as "memory access out of bounds" at doRewind.
# EXPORTED_RUNTIME_METHODS: the file-upload path calls back into C from a
# browser event (ccall), and writes the chosen file into MEMFS (FS). Neither is
# exported by default in recent emscripten, and omitting them fails only at
# runtime, when the user clicks "Open file".
#
# Do NOT add EXPORTED_FUNCTIONS here. It *replaces* the default export list
# rather than extending it, so everything unnamed gets dead-code-eliminated —
# including the ASYNCIFY rewind machinery this build depends on for its blocking
# main loop. The symptom is a blank canvas after load with "memory access out of
# bounds" at doRewind. The upload callback stays reachable via
# EMSCRIPTEN_KEEPALIVE on its definition instead.
LDFLAGS="-s USE_GLFW=3 -s ASYNCIFY -s INITIAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1 -s FORCE_FILESYSTEM=1 --preload-file resources@resources --shell-file $SCRIPT_DIR/web_shell.html -s NO_EXIT_RUNTIME=1 -s EXPORTED_RUNTIME_METHODS=ccall,cwrap,FS -s ASYNCIFY_STACK_SIZE=1048576"
if [ "$BUILD_TYPE" = "debug" ]; then
LDFLAGS="$LDFLAGS -g -O0 -s ASSERTIONS=1"
else
LDFLAGS="$LDFLAGS -O2"
fi
# Compile and link
emcc -o "$OUTPUT" \
$OBJECTS \
-I"$RAYLIB_DIR/src" \
-I"$RAYLIB_DIR/src/external" \
-I"$RAYLIB_DIR/src/external/glfw/include" \
-Iinclude \
"$RAYLIB_BUILD/libraylib.a" \
$LDFLAGS
echo ""
echo "=== Build complete ==="
echo "Output: $OUTPUT"
echo ""
echo "To test locally, run:"
echo " cd $BUILD_DIR && python3 -m http.server 8080"
echo "Then open http://localhost:8080/rspektrum.html in your browser"