- .github/workflows/release.yml: every push to `release` builds a draft GitHub Release: macOS universal DMG (Developer ID + notarization), Windows x64/x86/arm64 MSI + zips (Azure Trusted Signing), Linux x86_64/aarch64 AppImage/deb/rpm/tar.gz + Flatpak manifest, FreeBSD, Web/WASM zip; SHA256SUMS; secrets only via the release environment - packaging/ for every platform; .scraft + audio file associations - cargo xtask version [set X.Y.Z] and xtask ico - apps/soundcraft-web (trunk, WebGPU/WebGL2, WebAudio) - placeholder app icon (original nightingale), window icon, Windows resources - ROADMAP.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hosting SoundCraft for the web
soundcraft-web-<version>.zip (from the GitHub release, or packaging/web/package.sh) holds a
static site in soundcraft-web-<version>/:
| File | What it is |
|---|---|
index.html |
The page. It loads everything through relative URLs. |
soundcraft-web-<hash>.js |
wasm-bindgen glue (generated, ES module) |
soundcraft-web-<hash>_bg.wasm |
The app (size: see the release; roughly a third of that with compression) |
_headers, .htaccess |
Sample header rules for Netlify/Cloudflare Pages and Apache |
There is no server-side code. Upload the folder's contents anywhere that serves static files.
Any path works
All URLs in index.html are relative (public_url = "./" in apps/soundcraft-web/Trunk.toml),
so the site works at a domain root (https://example.com/), under a prefix
(https://example.com/tools/soundcraft/) and from a CDN bucket. The asset names carry a content
hash, so they can be cached forever. Only index.html needs revalidation.
Required server settings
- MIME type: serve
.wasmasapplication/wasm. Browsers refuse to stream-compile it under any other type, and the app then loads slowly or not at all. Serve.jsastext/javascript. Most hosts already do both. For nginx, check thatmime.typeshasapplication/wasm wasm;. - Compression: turn on gzip or Brotli for
.wasm,.jsand.html. That takes the download to roughly a third. You can also precompress (brotli -k *.wasm) and let the server sendContent-Encoding: br. - Caching:
Cache-Control: public, max-age=31536000, immutableon the hashed.wasmand.jsfiles, andno-cacheonindex.html. - HTTPS: WebGPU (and the clipboard) only work in a secure context, which means
https://orhttp://localhost. Over plain HTTP elsewhere, the app falls back to WebGL2. - No special isolation headers: SoundCraft plays audio through WebAudio without
SharedArrayBuffer, so it doesn't needCross-Origin-Opener-PolicyorCross-Origin-Embedder-Policy. If your site already sends COEPrequire-corp, also sendCross-Origin-Resource-Policy: same-origin(orcross-originwhen the files live on a CDN) on the app's files.
nginx example:
location /soundcraft/ {
types { application/wasm wasm; text/javascript js; text/html html; }
gzip on;
gzip_types application/wasm text/javascript text/html;
location ~* \.(wasm|js)$ { add_header Cache-Control "public, max-age=31536000, immutable"; }
location ~* index\.html$ { add_header Cache-Control "no-cache"; }
}
Local test: python3 -m http.server 8765 inside the folder, then open http://localhost:8765/.
Embedding in a page (iframe)
<iframe
src="https://example.com/soundcraft/"
title="SoundCraft"
style="width: 100%; height: 720px; border: 0;"
allow="fullscreen; autoplay; clipboard-read; clipboard-write"
allowfullscreen>
</iframe>
- The app fills the iframe and follows its size, so size the iframe and not the app.
- Keyboard shortcuts go to the iframe after the user clicks into it, as with any embedded app.
- Sandboxed iframes need at least
sandbox="allow-scripts allow-same-origin". - Don't send
X-Frame-Options: DENYor aframe-ancestorsCSP that excludes the embedding page.
Renderer selection and fallback flags
SoundCraft renders with wgpu. It uses WebGPU when the browser has it and falls back to
WebGL2 on its own. URL query flags override this, and they work on the iframe src too:
| Flag | Effect |
|---|---|
| (none) | WebGPU if available, otherwise WebGL2 |
?webgl |
Force the WebGL2 backend (useful when a WebGPU driver misbehaves) |
?empty |
Start with an empty session instead of the synthesised demo session |
For example: <iframe src="https://example.com/soundcraft/?webgl" ...>.
A browser with neither WebGPU nor WebGL2 gets a message in place of the app.
Audio
Playback goes through WebAudio. Browsers only start an AudioContext after a user gesture, so
the first click or key press in the page may be needed before sound is heard. In an iframe,
allow="autoplay" lets the embedding page grant that up front. The web build has no recording,
no native file dialogs and no control channel; it opens the synthesised demo session.