Files
rspektrum/build_web.sh
T
tyler 40623feadc feat: web file upload, axis-locked grid, averaged cursor readout
**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
2026-08-12 16:23:26 -07:00

122 lines
4.5 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.
# 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".
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 EXPORTED_FUNCTIONS=_main,_rspektrum_upload_done"
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"