# BWF Analyser Reads and edits BWF (Broadcast Wave) metadata for production sound: scene, take, timecode, track names, plus export, split, combine and a sound report. Two things get built from one source tree: - `index.html` at the root: a single self-contained page, no server, no network, opens in a browser. - `mac-app/`: a native macOS app (Tauri v2 + Rust) that wraps the same page and adds real file access, a Rust audio engine and a Rust WAV writer. ## Build ```bash python3 build/build.py # writes ./index.html python3 build/build.py --tauri # writes ./mac-app/dist/index.html ``` Both, every time: a change to shared code affects both pages, and a feature wired into only one looks exactly like a broken feature (a visible button that does nothing). There is a test for that specific trap. The Mac app is built by double-clicking `mac-app/Build BWF Analyser.command`. That script regenerates the page, touches `main.rs` so the page is re-embedded (cargo does not reliably notice a changed `dist/`), compiles, assembles and signs the bundle. ## Test ```bash node build/test.js # the browser page, in jsdom node build/test-tauri.js # the Mac page against a stubbed engine node build/test-export.js # every export/split/combine/format combination node build/test-play.js # playback maths, peaks, spectrogram, meters node build/test-restore.js build/test-framerate.js build/test-pdf.js BWF_TINY_CHUNKS=1 node build/test-tauri.js # tiny-read paths ``` All of them should pass before anything ships. Roughly 370 checks. ## How the build works, and its traps `build/build.py` inlines CSS and JS into one HTML file. The app's JavaScript (`build/src/bwf-analyser-app.js`) is vendored from a WordPress plugin and is kept pristine: changes to it are made as **exact-string patches** in lists in `build.py` (`SHARED_APP_JS_PATCHES`, `APP_JS_PATCHES`, `PLAYER_PATCHES`, `SPECTRO_PATCHES`, `MIXER_PATCHES`). Each patch asserts its anchor is found exactly once, so a stale anchor fails the build loudly rather than silently doing nothing. - Patch strings are Python **non-raw** triple-quoted strings, so tabs in the JavaScript are written `\t`. Get this wrong and the anchor never matches. - `SHARED_*` patches apply to both builds; the others only to `--tauri`. - When inserting into the page, use the **last** ``, not the first: the page contains inlined JavaScript that writes HTML, and the first match is inside a string literal. ## The Rust side `mac-app/src-tauri/src/`: - `main.rs` — the commands the page calls (`bwf_scan`, `bwf_export`, `bwf_combine`, `bwf_peaks`, `bwf_spectrogram`, `bwf_play`, `bwf_gains`, …). - `convert.rs` — RIFF/BWF parsing and writing, peaks, spectrogram, FFT. - `play.rs` — the audio engine. cpal, one engine thread owning the stream (`cpal::Stream` is `!Send` on macOS), a reader thread streaming blocks over bounded channels, per-channel gains and meters as atomics read in the callback. Playback deliberately does **not** use Web Audio: the webview's audio would die after a while and no JavaScript could recover it. `build/wav-convert.js` is a line-for-line **Node mirror** of the Rust converter, and the tests drive both. Anything added to the Rust side that has maths in it should be mirrored and tested, including the rounding: Rust's `f64::round` and JavaScript's `Math.round` disagree on negative ties. ## Conventions worth keeping **Mutation-test every new test.** Break the thing on purpose, confirm the test fails with a message that names the problem, put it back. Several tests in this suite passed for the wrong reason until this caught them. **jsdom has no layout engine.** Nothing here can see that a button has no hit area, that a grid is scrambled, or that an element collapsed to zero width. Every one of those has shipped at least once. Layout changes need a screenshot from the user; keep them small and ask. **Two pages, one behaviour.** If a feature only makes sense on the Mac build, it still needs to not look broken on the other one. **The page stamps itself.** `window.BWFA_BUILD` holds the build time, also on `data-bwfa-build` on the app root. Use it to settle "is the app running the page I just built" rather than guessing. **There is an in-app log.** Cmd-Shift-L opens it (`build/diagnostics.js`): it captures console output and errors from before the app starts, traces clicks with what was actually hit, and measures the controls' boxes against `elementFromPoint`. This is the fastest route to a diagnosis when a control looks fine and does nothing. Devtools are compiled in too (right-click → Inspect Element). **Text lives in `build/l10n.json`**, including the hover hints, keyed by data attribute. ## Where things are ``` build/ build.py assembles both pages, holds all the patches body.html markup overrides.css the app's own styling on top of the plugin's l10n.json every string, plus hints diagnostics.js the in-app log src/ the vendored plugin (JS + CSS), kept pristine wav-convert.js Node mirror of the Rust converter, for tests test*.js the suites mac-app/ src-tauri/src/ the Rust dist/index.html generated Build BWF Analyser.command ```