# 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. One thing gets built: `mac-app/`, a native macOS app (Tauri v2 + Rust) whose frontend is a single self-contained page, wrapping real file access, a Rust audio engine and a Rust WAV writer. There used to be a second, browser-only page built from the same sources, and much of the shape of this repo still shows it — a vendored WordPress plugin patched on the way in, a `shim`/`bridge` split, tests that load a page into jsdom. It is gone. Don't build it, don't write for it, and don't reintroduce a `--tauri` flag or a second output: there is one page and one build. ## Build ```bash python3 build/build.py # writes ./mac-app/dist/index.html ``` 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. `cargo build` on its own is not that. It produces the bare binary and leaves the bundle alone, so the `.app` sitting there is still the old one — which looks exactly like a change that didn't work. **Restart the app after compiling it, every time, without being asked.** Quit the running copy and open the newly built one, so there is something on screen to try rather than a description of what should happen. A compiled-but-not- restarted app goes on showing the previous page and nothing in the window says so. Then **say in the reply that the app was rebuilt and relaunched**, and give the build stamp it should be showing — `window.BWFA_BUILD`, also on `data-bwfa-build`, and printed by the build script. Stating it is the point: it lets a mismatch be spotted from the reply instead of being mistaken for a broken feature. ## Test ```bash node build/test-tauri.js # the 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 420 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` (`BASE_APP_JS_PATCHES`, `APP_JS_PATCHES`, `PLAYER_PATCHES`, `SPECTRO_PATCHES`, `MIXER_PATCHES`, `TRANSPORT_KEY_PATCHES`). A patch whose anchor has gone stops the build 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. - 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. **"Is a modal on screen" needs both markers.** `.modal` is `display: none` without `.open`, and closing does not always take `.open` back off — so a modal that has been opened once and shut again still carries it. Neither the class nor the `hidden` attribute answers the question alone: `.modal.open:not([hidden])` does. Modals also stack (the mixer opens over the file sheet), so "something else is open" says nothing about what is in front. **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 the page, 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 ```