A small React + TypeScript web app for loading and previewing tracker module files (MOD/IT/S3M/XM, etc.) using libopenmpt compiled to WebAssembly. The app also includes a visual pattern viewer (HTML and optional WebGPU/WGSL shaders) and a simple media overlay for images / GIFs / video.
Key features
Recent changes
-
v0.35: added "Donut & Night Mode" shader
shaders/patternv0.35_bloom.wgsl(channel invert toggle, white center island, studio dim + UV ring); also includes previous bloom helpers inutils/bloomPostProcessor.ts. -
Play tracker modules in the browser using libopenmpt (WASM).
-
Pattern view with three renderer backends: WebGPU (WGSL shaders), WebGL2 (simplified GLSL renderer — explicit opt-in only, never automatic), and HTML (DOM grid).
-
Simple media panel & overlay for images, GIFs, and videos synchronized with playback.
-
Built with Vite, React, and TypeScript. Tailwind CSS is used for styling.
Quick start
Prerequisites
- Node.js (16+ recommended) and npm or a compatible package manager.
- A modern browser. WebGPU is the default renderer (Chrome 113+, Edge 113+, Arc). The opt-in WebGL2 session (
?webgl2=1) and the HTML grid work in any modern browser; there is no automatic fallback.
Install dependencies
npm ciPrefer npm ci for a clean install that matches the committed package-lock.json. Use npm install only when adding/updating dependencies (and commit the updated lockfile).
# After editing package.json dependencies:
npm install
git add package.json package-lock.json
# Full regenerate (rare — e.g. lockfile corruption):
rm -rf node_modules package-lock.json
npm install
git add package-lock.jsonRun the development server
npm run devOpen your browser to the address printed by Vite (typically http://localhost:5173) and load a tracker module using the UI.
Build for production
npm run build
npm run previewAssets load from site root (/). For the live subpath deploy at test.1ink.us/xm-player/, use npm run build:xm-player (sets VITE_APP_BASE_PATH=/xm-player/).
Lint (ESLint, hard CI gate; max 27 warnings budget)
npm run lintProduction audio uses the tracked JS AudioWorklet at public/worklets/openmpt-worklet.js. Do not overwrite it.
The optional native engine is built with a single supported path:
# Prerequisites: emsdk 3.1.51 (matches CI)
git clone https://github.com/emscripten-core/emsdk.git && cd emsdk
./emsdk install 3.1.51 && ./emsdk activate 3.1.51
source ./emsdk_env.sh
cd /path/to/mod-player
npm run build:emcc # release (-O3)
# npm run build:emcc -- --debug # -O0 -g -sASSERTIONS=2Outputs (gitignored until built): public/worklets/openmpt-native.{js,wasm,aw.js}.
| Command | Notes |
|---|---|
npm run build:emcc |
Canonical — runs scripts/build-wasm.sh |
npm run build:worklet |
Deprecated alias → same script |
./build-wasm.sh |
Deprecated root wrapper → same script |
Verify exported symbols stay in sync with TypeScript/C++:
npm run verify:native-exportsSee also: docs/WASM_BUILD_SOLUTIONS.md, public/worklets/README.md, AGENTS.md.
npm run build && npm run preview -- --port 4173 &
npm run smoke:visual # full matrix (html/webgpu + lite modes; WebGL2 is opt-in only, not in the matrix)
npm run smoke:visual:ci # CI subset: html, v0.30b/46/50/57See docs/VISUAL_SMOKE.md for the manual WebGPU + mobile checklist.
Usage
- Click "Load" or drag-and-drop a tracker module file (.mod, .it, .s3m, .xm, etc.) to load it.
- Use the playback controls to play/stop and toggle looping.
- Switch pattern renderers via URL param, debug panel, or devtools:
?renderer=webgpu(default when available)?webgl2=1— opt-in WebGL2 (GLSL) visualizer for this page load, with a persistent "WebGL2 fallback — WebGPU not in use" badge (?renderer=webgl2is an alias). It is also offered as a Use WebGL2 visualizer button when WebGPU hard-fails; it never starts automatically and is never persisted?renderer=html— lightweight DOM grid (PatternSequencer)window.DEBUG_RENDERER = 'html'orlocalStorage.setItem('xasm1_pattern_renderer', 'html')(a stored/DEBUG_RENDERER'webgl2'is ignored)
- Add media files via the media panel to display them in the overlay while a module plays.
Project structure (important files)
index.html— application entry. Loads libopenmpt WASM/JS and sets up a readiness promise used by the React app.index.tsx,App.tsx— React entry and main application UI.hooks/useLibOpenMPT.ts— primary integration with the libopenmpt runtime and playback state.components/— UI components (Controls, Header, PatternDisplay, PatternSequencer, MediaPanel, etc.).src/renderers/— Pattern renderer abstraction (webgl2/,html/, selection + global API).shaders/— WGSL shader files used by the WebGPU pattern renderer.public/— static assets.package.json— scripts and dependency list. Uses Vite for dev and build.
Notes and configuration
- libopenmpt: Self-hosted real WebAssembly build of libopenmpt 0.8.4 —
public/worklets/libopenmpt-worklet.{js,wasm}— shared by the AudioWorklet, the main thread (index.html, BASE_URL-aware, SRI-pinned) and the parser worker. Rebuild withnpm run build:js-libopenmpt(emsdk 3.1.51). Seepublic/worklets/README.md. - Tailwind: Styles are compiled by PostCSS/Tailwind (
tailwind.config.js,postcss.config.js) in dev and production alike; there is no Tailwind CDN script. Thecontentglobs are deliberately explicit — seeCLAUDE.mdpitfall 5. - Pattern renderers: GPU viz requires WebGPU (hard-fail on probe/device failure — no auto WebGL2/HTML shader session). Explicit
?renderer=htmlselects the DOM pattern grid;?webgl2=1/ the failure-card button opt in to the WebGL2 GLSL session. Probe breadcrumb:window.__WEBGPU_PROBE__. - WebGPU → WebGL2 (opt-in viz): Shared packing lives in
utils/gpuPacking.ts; WebGL2 GLSL mirrorshooks/webGLShaders.ts. Chassis/night mode/bloom approximations are insrc/renderers/webgl2/shaders/.
Contributing
See CONTRIBUTING.md and docs/REPO_LAYOUT.md for first-party vs experimental paths.
Contributions and fixes are welcome. Open an issue or pull request; keep changes small and focused. If you add dependencies, update package.json and include a brief rationale in the PR.
Credits
- libopenmpt (WASM build) — playback engine for tracker modules.
- Project originally linked to: https://github.com/ford442/react-dom (see app footer for repository link).
License
This repository does not include an explicit license file. Add a LICENSE file if you want to permit reuse.