Skip to content
nulltaskPublic

About

TypeScript monorepo for the be-music BMS/BMSON toolchain

Resources

Stars

15 stars

Watchers

0 watching

Forks

Latest commit

 

History

2,147 Commits

Folders and files

Repository files navigation

Japanese version

be-music

BMS/BMSON toolchain composed of TypeScript + pnpm workspaces.

Packages

  • @be-music/json: pure IR of BMS/BMSON intermediate representation (JSON compatible) dedicated to Be-Music internal processing
  • @be-music/chart: chart semantics helper such as beat resolution, event order, long note resolution, etc.
  • @be-music/utils: Generic utilities reused across all packages, with narrow subpath exports for browser-safe core helpers and Node-facing helpers
  • @be-music/parser: .bms / .bme / .bml / .pms / .bmson / JSON parser
  • @be-music/stringifier: Stringization from JSON to .bms / .bmson
  • @be-music/audio-renderer: Render the music score and output .wav / .aiff
  • @be-music/player: Shared playback engine, timing, judgment, scoring, gauge, BGA timeline, and UI/audio adapter contracts
  • @be-music/player-tui: Terminal UI and bms-player CLI frontend for autoplay, keyboard play, Music Select, BGA, and SEA builds, plus the bms-playlog play-log re-simulator
  • @be-music/lr2-skin: Renderer-independent Lunatic Rave 2 skin parser, asset resolver, and theme loader
  • @be-music/beatoraja-skin: Renderer-independent beatoraja JSON/Lua skin parser, normalizer, and theme loader
  • @be-music/skin-sdk: Framework-free SDK for writing be-music skins for the browser player
  • @be-music/player-web: Browser PixiJS player core for song selection, built-in default / LR2 / beatoraja skin rendering, gameplay, result scenes, and recording
  • @be-music/player-web-demo: Private Vite demo that wires folder/ZIP drops, LR2/beatoraja themes, debug controls, and browser playback together
  • @be-music/editor: CLI editor (import/edit/export)

Required environment

  • Node.js >= 26
  • pnpm workspaces

Setup

pnpm install

Build and Verification

pnpm run clean
pnpm run build
pnpm run check
pnpm run test

The toolchain is Vite+ (vite-plus), configured from the root vite.config.ts. pnpm run build runs each workspace's build script through the Vite+ task runner (vp run -r --cache build) in dependency order, replaying unchanged packages from the task cache; library packages build with vp pack (tsdown) from the pack block in their vite.config.ts and emit the bundle and type definitions (.d.ts) together. pnpm run check runs formatting, linting, and type checking (Oxfmt, Oxlint, and tsgolint) across the whole workspace in one pass; pnpm run format / pnpm run lint / pnpm run typecheck run the individual steps. Each package's tsconfig.json is its type-check project, while tsconfig.build.json drives declaration output. Installing dependencies also registers a Vite+ pre-commit hook that runs vp check --fix on staged files.

Package Releases

In this repository, Changesets manages the version and changelog for each package.

pnpm run changeset
pnpm run release:status
pnpm run release:version
  • Normal feature PR adds .changeset/*.md for the changed package
  • When you want to release, run pnpm run release:version on devel and commit the generated packages/*/package.json and packages/*/CHANGELOG.md updates together.
  • In that state, merging the release PR of devel -> main creates a separate GitHub Release for each public package whose version has increased (private packages such as the player demo and the Leaderboard get none).
  • The player SEA executable is built from @be-music/player-tui and attached to the @be-music/player-tui release; @be-music/audio-renderer also ships a SEA zip
  • The private repository root package.json stays at 0.0.0; releasable versions are tracked in packages/*/package.json.

The tag is created in the format @be-music/package-name@x.y.z.

Specifications

@be-music/json is Be-Music's internal data model. It is not designed as a distribution format or a reusable exchange format with other tools. The semantics helper of the score is separated into @be-music/chart, and @be-music/json itself is responsible for pure IR and round-trip preservation.

Compatibility status summary

parser (@be-music/parser)

  • Preserves BMS header/object lines/control syntax
  • Interpret #WAVxx / #BMPxx / #BPMxx / #STOPxx / #TEXTxx
  • Contains BMS extension headers (#PREVIEW, #LNTYPE, #LNMODE, #LNOBJ, #VOLWAV, #SCROLLxx, #VIDEOFILE, etc.)
  • Interpret BMSON's info / lines / sound_channels / bpm_events / stop_events / bga, and beatoraja's scroll_events / key_channels / mine_channels extensions
  • BMS text character code guessing (Shift_JIS, UTF-8, EUC-JP, latin1, etc.)
  • BMSON info.init_bpm is required by the parser; missing or invalid values fail early instead of falling back to 130
  • The beatoraja BMSON extensions are mapped through the mode_hint lane resolver as beatoraja reads them: mine_channels become mines that preserve per-mine damage, and key_channels become hidden (invisible) notes

stringifier (@be-music/stringifier)

  • Output BMS/BMSON from intermediate representation (JSON)
  • Stable reproduction of bar resolution using position: [numerator, denominator]
  • Output BMSON extension information (info extension, bga, notes.l/c)
  • Preserve BMS source structure when the stored preservation layer still matches the normalized events

audio-renderer (@be-music/audio-renderer)

  • Supports BMS / BMSON / JSON input
  • Output format .wav / .aiff
  • Sample loading: WAV / MP3 / OGG (Vorbis/Opus) / OPUS
  • Reflects bar length / BPM / STOP
  • 100001 times the BPM gimmick value of LR2 series is processed by time resolution
  • Applies #VOLWAV, #xxx97 / #xxx98, BMSON notes.c, #WAVCMD 01 xx vv, and #EXWAVxx v volume scaling

player core (@be-music/player)

  • Shared autoPlay() / manualPlay() engine for terminal and browser runtimes
  • UI and input signal buses for lane flashes, POOR BGA commands, frame snapshots, pause, restart, and high-speed changes
  • Shared note extraction for playable notes, legacy long notes, #LNOBJ, FREE ZONE, mines, and invisible notes
  • Shared judgment windows, dynamic #EXRANKxx changes, score, groove gauge, scroll-distance, BGA timelines, and result summaries
  • Browser-safe subpath exports for reusable helpers such as core/engine, core/bga-timeline, core/scroll-distance, core/groove-gauge, playable-notes, and playlog

terminal player (@be-music/player-tui)

  • 3 modes: MANUAL / AUTO SCRATCH / AUTO
  • TUI play screen and song selection screen
  • Metadata / preview / banner display on song selection screen
  • Local cache of song list metadata (~/.be-music/chart-selection-cache.json)
  • HIGH-SPEED (0.5 ~ 10.0, 0.5 increments)
  • TUI refresh rate setting (--tui-fps, default 60)
  • Judgment: PERFECT / GREAT / GOOD / BAD / POOR (with FAST / SLOW tally)
  • 200,000 points SCORE and IIDX compliant EX-SCORE
  • Show invisible notes (--show-invisible-notes)
  • Exclusive handling of FREE ZONE (17 / 27)
  • BGA image drawing (BMP / PNG / JPEG) and video drawing (mpeg1video / h264 / mjpeg)
  • Video BGA progressive decoding (switch to old method with --no-video-bga-streaming)
  • Kitty graphics protocol drawing switching using --kitty-graphics / --no-kitty-graphics (default: on)
  • Play with node-web-audio-api fixed backend
  • Optional BMS #LNTYPE auto-detection when omitted (--ln-type-auto / --no-ln-type-auto)
  • Optional pre-playback audio rendering (--render-audio) and per-bus volume tuning (--volume, --bgm-volume, --key-volume)
  • Output dynamics tuning with compressor/limiter switches and threshold/release controls
  • Structured log output (~/.be-music/logs/player.ndjson, overwritten with --log-file)
  • Node worker split for gameplay, TUI rendering, and video BGA decoding
  • bms-playlog CLI that re-derives LR2 / beatoraja / IIDX results from a recorded play log (see Play log specification)

LR2 skin (@be-music/lr2-skin)

  • Parses LR2 select / decide / play / result skin CSV files independently from PixiJS
  • Resolves #INCLUDE, #CUSTOMOPTION, #CUSTOMFILE, #LR2FONT, system font declarations, skin timers, op conditions, and play-skin variants
  • Parses image, number, text, slider, bargraph, button, BGA, judge-line, measure-line, gauge, score-chart, and result graph elements
  • Loads theme assets with case-insensitive and wildcard lookup, TGA decoding, and DXA archive extraction

beatoraja skin (@be-music/beatoraja-skin)

  • Parses beatoraja JSON skins and Lua .luaskin entries independently from PixiJS
  • Evaluates the beatoraja two-phase Lua contract on a restricted Fengari sandbox
  • Discovers play / select / decide / result / course-result entries, with play skins grouped by 5 / 7 / 9 / 10 / 14 / 24 / 24d — every one of them mountable for gameplay
  • Resolves property[], filepath[], category groups, custom offsets, wildcard source paths, and case-insensitive assets
  • Normalizes image, imageset, value, float-value, text, slider, note, judge, gauge, graph, BPM graph, timing graph, song-list, custom event, destination, and PM character elements

be-music skin SDK (@be-music/skin-sdk)

  • Skin contract for the browser player's built-in family: the skin draws each screen onto a canvas from plain per-frame data
  • Imports no rendering framework; a skin brings its own (Canvas 2D, WebGL / WebGPU, PixiJS, three.js, …)
  • Stage, lane layout, touch / portrait layout, moment, judgement word, loading, audio drive, song fact, result track, skin settings, and motion helpers
  • See Writing a be-music skin

browser player (@be-music/player-web / @be-music/player-web-demo)

  • Browser song library for dropped folders, ZIPs, and mixed song/LR2/beatoraja theme drops
  • Lazy browser asset loading for large audio/video files, with case-insensitive path lookup
  • Built-in be-music skins (Synesthesia by default, Phantom, and Plain, plus the opt-in Lattice) for select, gameplay, and result, and for a decide screen when the skin declares one
  • Touch play and a touch gameplay layout on touch-first devices, portrait stages for skins that provide one, and per-skin settings
  • LR2 select / decide / gameplay / result skin parsing and rendering on PixiJS
  • beatoraja select / decide / gameplay / result skin parsing and rendering on PixiJS
  • Separated default gameplay chrome injection so default-skin rendering does not depend on LR2 renderer modules
  • Shared LR2 Pixi helpers for destination interpolation, sprite transforms, numbers, text, sliders, and bargraphs
  • Shared beatoraja Pixi helpers for destination sampling, source-cell selection, text, values, sliders, gauges, graphs, timing visualizers, notes, markers, BGA, and runtime op/timer wiring
  • Shared playback semantics with the terminal player for notes, timing, scroll distance, BGA cues, score, and results
  • WebAudio preview and gameplay buses with split key/BGM/master compressor controls
  • BGA still/video rendering, browser-side video transcode fallback, and WebM gameplay recording
  • Single PixiJS scene host that owns one renderer context and disposes scene resources on transitions
  • In-scene LR2 and beatoraja PLAY OPTION controls for hi-speed, BGA mode/size, filters, sort, HS-FIX, lane cover, lift, auto-scratch, DP flip, random/mirror modes, gauge variants, and skin options

editor (@be-music/editor)

  • init, import, export, set-meta, add-note, delete-note, list-notes

Main commands

1. Convert BMS/BMSON to JSON

pnpm run parse chart.bms chart.json

2. Convert JSON to BMS/BMSON

pnpm run stringify chart.json chart.bms --format bms
pnpm run stringify chart.json chart.bmson --format bmson

3. Audio rendering

pnpm run audio-render chart.bms out.wav
pnpm run audio-render chart.bms out.aiff --sample-rate 48000

4. Player

# Autoplay
pnpm run player chart.bms --auto

# Autoplay scratch only (16ch/26ch)
pnpm run player chart.bms --auto-scratch

# Manual play
pnpm run player chart.bms

# Disable TUI
pnpm run player chart.bms --no-tui

# Initial HIGH-SPEED value
pnpm run player chart.bms --high-speed 3.5

# TUI refresh rate
pnpm run player chart.bms --tui-fps 120

# Disable progressive decode for video BGA
pnpm run player chart.bms --no-video-bga-streaming

# Show invisible channels (31-39/41-49) as green notes
pnpm run player chart.bms --show-invisible-notes

# Render BGA / STAGEFILE / BANNER images with the Kitty graphics protocol
pnpm run player chart.bms --kitty-graphics

# Override the structured log output path
pnpm run player chart.bms --log-file /tmp/be-music.ndjson

# Disable audio
pnpm run player chart.bms --no-audio

# Disable the output limiter
pnpm run player chart.bms --no-limiter

# Enable the compressor
pnpm run player chart.bms --compressor --compressor-threshold-db -10 --compressor-ratio 3

# Render audio before playback without in-game audio
pnpm run player chart.bms --render-audio preview.wav --no-audio

# Tune visible-note cap for dense charts
pnpm run player chart.bms --tui-visible-notes-limit 4096

# Disable automatic #LNTYPE inference when missing
pnpm run player chart.bms --no-ln-type-auto

5. Browser player demo

pnpm run player:web

The demo starts a Vite dev server. Drop a BMS/BMSON song folder, an LR2 theme folder, a beatoraja theme folder, a ZIP, or a song folder and LR2/beatoraja theme folder together into the browser.

The demo can also load archives from a URL: ?music=<zip URL>&skin=<zip URL> (either parameter may be omitted) feeds the archives into the same pipeline as a drop. Cross-origin https URLs are fetched through the same-origin proxy route /__url-load-proxy, served by the Vite dev server and by the Cloudflare Worker.

To deploy the demo to Cloudflare Workers (Static Assets plus a Worker, see packages/player-web-demo/wrangler.jsonc):

pnpm --filter @be-music/player-web-demo run cf:r2:create # once: create the R2 bucket for ffmpeg-core
pnpm --filter @be-music/player-web-demo run deploy:cf

deploy:cf builds the demo, uploads ffmpeg-core.wasm / ffmpeg-core.js to R2 only when they changed (they exceed the Static Assets per-file limit and are served from R2 through the Worker), and runs wrangler deploy. cf:r2:push force-uploads the ffmpeg core.

6. Editor

pnpm run editor import chart.bms chart.json
pnpm run editor add-note chart.json 0 11 1 2 01
pnpm run editor export chart.json chart.bms

7. Play log re-simulation

pnpm run playlog -- /path/to/Song.bmplay.json
pnpm run playlog -- /path/to/Song.bmplay.json --ruleset=lr2,beatoraja --json

See Play log specification for the options.

Player Operations

Song selection screen

  • ↑/↓ or k/j: Move
  • ←/→ or h/l: Move page
  • Ctrl+b / Ctrl+f: Move page
  • g or Home / G or End: Jump to the first / last entry
  • 1-5: DIFFICULTY filter
  • 0: DIFFICULTY filter cancellation
  • a: MANUAL -> AUTO SCRATCH -> AUTO switching
  • s: HIGH-SPEED increase (+0.5)
  • S: HIGH-SPEED decrease (-0.5)
  • Enter: Start
  • Esc or Ctrl+C: Exit

Playing

  • Space: Pause/Resume
  • Alt/Option + Odd lane input: HIGH-SPEED decrease (-0.5)
  • Alt/Option + Even lane input: HIGH-SPEED increase (+0.5)
  • Esc: Finish playing and go to results
  • Ctrl+C: Exit

Results screen

  • Enter or Esc: Return to song selection screen
  • r: Play the same chart again (single-chart mode)

Automatic lane mode determination and input assignment

Automatic judgment

Automatically determines the next mode based on the channel used.

  • 5 KEY SP
  • 5 KEY DP
  • 7 KEY SP
  • 14 KEY DP
  • 9 KEY
  • 24 KEY SP
  • 48 KEY DP

If the automatic judgment is ambiguous, it will be supplemented with an extension.

  • .bms -> 5 KEY SP/DP
  • .bme -> 7 KEY SP/14 KEY DP
  • .pms -> 9 KEY
  • A full 11..19 one-player keyboard or PMS-STD 22..25 without traditional IIDX 2P channels also resolves to 9 KEY.
  • Any extended lane channel (1A..1O / 2A..2O, or the wheel 1P / 1Q / 2P / 2Q) resolves to 24 KEY SP / 48 KEY DP ahead of every other rule, and so does a bmson keyboard-24k / keyboard-24k-double mode_hint.

Representative mode channels and inputs

Mode Channel -> Input
5 KEY SP 16 -> LShift, 11 -> z, 12 -> s, 13 -> x, 14 -> d, 15 -> c
5 KEY DP 16 -> LShift, 11 -> z, 12 -> s, 13 -> x, 14 -> d, 15 -> c, 21 -> b, 22 -> h, 23 -> n, 24 -> j, 25 -> m, 26 -> RShift
7 KEY SP 5 KEY SP + 18 -> f, 19 -> v
14 KEY DP 7 KEY SP + 21 -> b, 22 -> h, 23 -> n, 24 -> j, 25 -> m, 28 -> k, 29 -> ,, 26 -> RShift
9 KEY (BME-compatible) 11 -> z, 12 -> s, 13 -> x, 14 -> d, 15 -> c, 16 -> f, 17 -> v, 18 -> g, 19 -> b
9 KEY (PMS-STD) 11 -> z, 12 -> s, 13 -> x, 14 -> d, 15 -> c, 22 -> f, 23 -> v, 24 -> g, 25 -> b
24 KEY SP 11..19 + 1A..1O (24 keys, no scratch) -> a s d f g h j k l ; q w e r u i o p z x c v b n; wheel 1P -> Tab (up), 1Q -> LShift (down)
48 KEY DP 24 KEY SP + 21..29 + 2A..2O, the 2P keys running past the printable-key pool onto function keys; 2P wheel 2P -> Enter, 2Q -> RShift

FREE ZONE (17 / 27)

  • Other than 9KEY and the 24KEY / 48KEY keyboard modes, it is treated as FREE ZONE.
  • Do not create an independent lane, but draw on top of the scratch lane (16 / 26).
  • The note length is fixed at a quarter note.
  • Since it is not subject to judgment, it is not included in TOTAL / EX-SCORE / SCORE.
  • When determining 9KEY or 24KEY / 48KEY, 17 / 27 is treated as the normal lane note.

Keyboard input (kitty keyboard protocol)

  • Automatic opt-in to kitty keyboard protocol when starting play.
  • On compatible terminals, pressing and lifting of Left Shift and Right Shift will be handled individually.
  • Incompatible terminals will fall back to conventional input.
  • Even during fallback, scratch input can be replaced with a (1P) / ] (2P).

BGA implementation

  • Combine and draw 04 (base) and 07 (layer).
  • Black (#000000) in layer is treated as a transparent color.
  • #BANNER / bmson banner_image is displayed in the song introduction block on the song selection screen.
  • On compatible terminals, the kitty graphics protocol is used to display gameplay BGA, #STAGEFILE loading screen, and song selection screen banner as images by default.
  • Add --no-kitty-graphics to return to ANSI drawing.
  • BGA recalculates and updates the display size when resizing the window.
  • Video BGA is decoded with @uwx/libav.js-fat.
    • Supported codecs: mpeg1video, h264, mjpeg
    • Does not decode audio tracks.
    • By default, playback starts after the first frame is acquired, and the remaining frames are decoded gradually in the background.
    • Add --no-video-bga-streaming to revert to the old method of decoding all frames before playback.

Score and judgment

  • Judgment type: PERFECT, GREAT, GOOD, BAD, POOR
  • FAST / SLOW is added only when pressing GREAT / GOOD quickly or slowly.
  • A blank keystroke without a corresponding unjudged note fires LR2-style empty POOR: it does not change judge counters, score, EX-SCORE, or combo, but it applies the empty-POOR gauge delta and triggers POOR BGA.
  • EX-SCORE:
    • PERFECT = +2
    • GREAT = +1
  • SCORE (200000 points):
    • Judgment base points 150000 + Combo additional points 50000
    • BAD / POOR does not add points, disconnects the combo

Settings/Cache/Log

player saves the following locally.

  • Play Mode (manual / auto-scratch / auto)
  • HIGH-SPEED
  • Per-directory last selected chart file and music-select focus key

Storage location and usage:

  • ~/.be-music/player.json
    • Play Mode, HIGH-SPEED, last selected chart file by directory, and music-select focus key by directory
  • ~/.be-music/chart-selection-cache.json
    • Song list metadata cache
    • Reuse is determined by contentHash in the chart body, and saved entries are verified by cacheHash
  • ~/.be-music/logs/player.ndjson
    • Structured log when running player
    • If --log-file <path> is specified, use that path

SEA (Single Executable Applications)

# Build the player SEA binary
pnpm run player:sea

# Build the audio-renderer SEA binary
pnpm run audio-renderer:sea

# Build outputs
./packages/player-tui/dist-sea/be-music-player chart.bms
./packages/audio-renderer/dist-sea/be-music-audio-render chart.bms output.wav

# When specifying the Node executable explicitly
pnpm run player:sea --node-binary /path/to/node
pnpm run audio-renderer:sea --node-binary /path/to/node

supplement:

  • Requires Node.js 26+.
  • SEA generation uses built-in --build-sea.

Exports Benchmark

# All packages
pnpm run bench

# Single package (example: parser)
pnpm --filter @be-music/parser run bench

# Aggregate multiple runs
pnpm run bench:aggregate -- --output tmp/bench/head.json tmp/bench/head-runs/*.json

# Compare two revisions and generate Markdown
pnpm run bench:compare -- --head tmp/bench/head.json --base tmp/bench/base.json --output tmp/bench/benchmark.md
  • snapshot output: tmp/bench/exports*.json
  • compare output: arbitrary Markdown and summary JSON
  • In GitHub Actions, post base/head comparison as PR comment in PR for devel / main
  • GitHub Actions also performs the previous revision comparison when pushing to devel / main and posts a commit comment to the target commit.
  • CI measures base and head on the same runner, taking turns (head, base, head, base, …) for 5 runs each, so host-to-host noise and slow stretches hit both sides alike.
  • Fast functions are called many times per timed sample (about 50 µs a sample), so cases that take nanoseconds are measured rather than lost in the timer's resolution. Times are reported per call.
  • A case counts as a regression or an improvement only when its median changes past the threshold (8% in CI) and the runs of the two sides do not overlap; a change past the threshold with overlapping runs is listed as "within noise".
  • Cases marked ioBound (they touch the filesystem) are listed apart and never judged, since their time follows the OS cache and the disk.
  • The comment leads with the verdict, links the base and head commits, lists the changed packages' cases and new or removed exports, and folds noise, exclusions and run details into collapsible sections.

About

TypeScript monorepo for the be-music BMS/BMSON toolchain

Resources

Stars

15 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages