Skip to content

Developer Documentation

RMT Compose is a plain ES-module web app. No framework, no TypeScript in src/, one runtime dependency. Every musical value in a composition is a text expression that is compiled to bytecode and run on a stack VM with exact rational arithmetic; the results are drawn in a single WebGL2 canvas and scheduled through the Web Audio API.

The project is licensed MIT (LICENSE.md). Contributions are welcome.

Tech stack

TechnologyRole
ES ModulesThe whole of src/. No framework.
Vite 7Dev server (port 3000) and production bundler.
WebGL2The workspace. A hard requirement — player.js probes for a context and, without one, never constructs the Workspace. There is no DOM fallback renderer.
Web Audio APISynthesis, sample playback, the reverb/limiter signal graph.
fraction.js 5.3.4Exact rational arithmetic. BigInt-backed — arbitrary precision, at any magnitude and depth.
Rust → WASMAn alternative evaluator core that ships in the bundle but is off by default.
VitePressThis documentation site (docs/, deployed separately).

The WASM evaluator is not the default path

The rmt-core WASM binary is not even fetched on a normal page load — initWasm() returns early without the URL flag ?evaluator=wasm (src/wasm/evaluator-adapter.js:36-40), and that flag currently hangs the tab on a full re-evaluation. Everything you experience in the app runs on the JavaScript evaluator. See WASM Overview.

The layers

┌──────────────────────────────────────────────────────────────────────┐
│ UI                                                                   │
│  player.js (orchestrator) · modals/ (note + group widgets)           │
│  menu/ (module library) · settings/ (panel + store) · theme/          │
├──────────────────────────────────────────────────────────────────────┤
│ Expression front ends                                                │
│  dsl/          lexer → parser → compiler → decompiler   (primary)    │
│  expression-compiler.js   legacy method-chain parser + routing + LRU  │
├──────────────────────────────────────────────────────────────────────┤
│ Core                                                                 │
│  module.js  ·  note.js                                               │
│  binary-note.js      bytecode format (OP / VAR / CORRUPT)            │
│  binary-evaluator.js stack VM + IncrementalEvaluator (Kahn topo-sort)│
│  dependency-graph.js forward + inverse indexes, corruption flags     │
├──────────────────────────────────────────────────────────────────────┤
│ Output                                                               │
│  renderer/webgl2/   renderer · workspace · camera · config           │
│  player/            audio-engine → audio-graph → reverb              │
│  instruments/       synths + multisampled piano/violin               │
├──────────────────────────────────────────────────────────────────────┤
│ State                                                                │
│  store/app-state.js · store/history.js · utils/event-bus.js          │
├──────────────────────────────────────────────────────────────────────┤
│ wasm/  (alternative evaluator core — opt-in, currently blocked)      │
└──────────────────────────────────────────────────────────────────────┘

Documentation sections

Architecture

Core systems

Rendering

Audio

Deep dives

  • Performance — the benchmark harness and the measured numbers
  • Theming — presets, tokens, and how they reach the GL canvas

WASM

API reference

Contributing

Key files

FilePurpose
src/main.jsEntry point (index.html loads this). Registers instruments, boots WASM, modals, menu, settings.
src/player.jsThe orchestrator: transport, selection, drag/resize commits, undo wiring, scale controls. ~6.6k lines.
src/module.jsModule — notes, dependency registration, evaluation, JSON load/save, reindexing.
src/note.jsNote — six BinaryExpressions plus color / instrument.
src/dsl/The primary expression language: lexerparsercompilerdecompiler, plus simplify.
src/expression-compiler.jsThe legacy method-chain parser, the format router (isDSLSyntax), and the 4000-entry LRU compile cache.
src/binary-note.jsOP, VAR, CORRUPT, BinaryExpression.
src/binary-evaluator.jsBinaryEvaluator (stack VM), IncrementalEvaluator, FractionPool, SymbolicPower.
src/dependency-graph.jsForward and inverse dependency indexes, per-property indexes, corruption flags.
src/renderer/webgl2/renderer.jsRendererAdapter — 22 shader programs, all instance buffers, sync(), CPU picking.
src/renderer/webgl2/workspace.jsWorkspace — pointer interaction, drag/marquee/multi-select arbitration.
src/renderer/webgl2/renderer-config.jsdefaultRendererConfig — every geometry and text constant.
src/player/audio-engine.jsVoice construction and the lookahead scheduler.
src/player/audio-graph.jsInstrument buses, reverb send/return, pitch pan, master limiter.
src/player/reverb.jsAlgorithmic impulse-response generator.
src/settings/settings-store.jsThe settingsStore singleton (rmt:settings:v1).
src/theme/theme-manager.jsappearance.* → CSS custom properties + renderer.setConfig / setThemeColors.
src/store/history.jsUndo/redo. String snapshots, 50 entries, 12 MB cap.
src/utils/event-bus.jsThe pub/sub bus every subsystem talks over.
src/utils/simplify.jsExpression simplification and the arrow-interval coefficient fold.

The save/load path is Module.loadFromJSON() and Module.createModuleJSON() in src/module.js. (The dead src/module-serializer.js that used to sit beside it has been deleted.)

Getting started

  1. Set up your environmentnpm ci, then npm run dev on port 3000.
  2. Read System Architecture.
  3. Follow one edit end to end in Data Flow.
  4. Then go deep wherever you are working: Expression Compiler, Rendering, or Audio Graph.

Released under the MIT License