Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
97036b2
chore(deps): bump node-web-audio-api from 1.0.9 to 2.0.0
dependabot[bot] May 24, 2026
1e0bbea
chore(deps-dev): bump oxlint from 1.67.0 to 1.69.0
dependabot[bot] Jun 11, 2026
7738d5f
chore(deps-dev): bump @typescript/native-preview
dependabot[bot] Jun 11, 2026
87ef71f
chore(deps-dev): bump @cloudflare/workers-types
dependabot[bot] Jun 11, 2026
dbdf05a
refactor(parser): unify BMS text decoding into a shared module
nulltask Jun 11, 2026
8e3db8d
refactor(parser): share object data line parsing with control-flow ca…
nulltask Jun 11, 2026
f0912cb
refactor(chart): add shared monophonic WAV playback predicate
nulltask Jun 11, 2026
1b6440e
refactor(player): centralize EXRANK judge-window conversion
nulltask Jun 11, 2026
ba3b9eb
refactor(player): share POOR BGA display duration and gauge clear check
nulltask Jun 11, 2026
a966391
fix(sea): inline .js chunks emitted alongside the SEA entry bundle
nulltask Jun 11, 2026
3f9c19f
fix(sea): bundle SEA entries with node resolve conditions
nulltask Jun 11, 2026
7802f98
fix(player): correct gauge clear, EXRANK unit, bmson mine damage, and…
nulltask Jun 11, 2026
2475823
fix(player): spawn SEA workers from the embedded bundle
nulltask Jun 11, 2026
6ce9173
fix(audio-renderer): default missing keysound references to silence
nulltask Jun 11, 2026
b2c4f9b
fix(parser): detect UTF-16 BOMs and score BOM-less encodings in decod…
nulltask Jun 11, 2026
cdc42a1
fix(parser): truncate object data at the first whitespace
nulltask Jun 11, 2026
4256abe
fix(player): load optional native modules beside the SEA executable
nulltask Jun 11, 2026
ae891d8
fix(sea): warn when ad-hoc code signing fails on macOS
nulltask Jun 11, 2026
99324e8
fix(player-web): scope dynamic volume changes to subsequently trigger…
nulltask Jun 11, 2026
ca1012c
docs(changeset): record SEA runtime fixes for utils, player, and play…
nulltask Jun 11, 2026
4d5a89e
fix(parser): accept #END IF, #END, and #ELSE IF control-flow variants
nulltask Jun 11, 2026
a7cbecf
feat(utils): probe an env-configured directory in loadOptionalNodeModule
nulltask Jun 11, 2026
5b517bd
feat(sea): embed node-web-audio-api into the player SEA binary
nulltask Jun 11, 2026
e4c7be1
docs(changeset): cover the embedded native module loading for SEA builds
nulltask Jun 11, 2026
cabc213
feat(sea): embed libav.js for self-contained video BGA playback
nulltask Jun 11, 2026
9780434
docs(changeset): note the embedded libav.js video BGA support
nulltask Jun 11, 2026
4663c22
fix(sea): embed chunk and entry sources verbatim when inlining
nulltask Jun 11, 2026
2a0f582
fix(player): accept plain CJS exports from the SEA libav fallback
nulltask Jun 11, 2026
b9922cf
feat(parser): support bmson ln_type / note t long-note modes
nulltask Jun 11, 2026
811cdfc
feat(player): adopt the measured LR2 judge windows
nulltask Jun 11, 2026
7bbf052
feat(player): align HARD / EASY / DEATH gauges with the LR2 tables
nulltask Jun 11, 2026
254e213
fix(player): gate empty POOR on the LR2 1-second early window
nulltask Jun 11, 2026
d0bb321
feat(player): adopt the LR2 mine detonation model
nulltask Jun 11, 2026
fc480eb
Merge pull request #126 from nulltask/dependabot/npm_and_yarn/oxlint-…
nulltask Jun 11, 2026
bf158da
Merge pull request #130 from nulltask/dependabot/npm_and_yarn/cloudfl…
nulltask Jun 11, 2026
7f66838
Merge pull request #127 from nulltask/dependabot/npm_and_yarn/typescr…
nulltask Jun 11, 2026
c5ace1d
fix(player-web-demo): alias the utils optional-node-module subpath
nulltask Jun 11, 2026
39f2233
Merge branch 'devel' of https://github.com/nulltask/be-music into devel
nulltask Jun 11, 2026
f07ff77
fix(player-web): match LR2 groove gauge bar rendering
nulltask Jun 11, 2026
d4c47e7
test(player): make landmine detonation tests deterministic
nulltask Jun 12, 2026
a00bbdc
fix(player-web-demo): resolve workspace root aliases exactly
nulltask Jun 12, 2026
b526f3f
chore(deps-dev): bump wrangler from 4.98.0 to 4.100.0
dependabot[bot] Jun 12, 2026
69c7727
chore(deps-dev): bump oxfmt from 0.52.0 to 0.54.0
dependabot[bot] Jun 12, 2026
995bfe2
Merge pull request #128 from nulltask/dependabot/npm_and_yarn/wrangle…
nulltask Jun 12, 2026
75ad266
Merge pull request #129 from nulltask/dependabot/npm_and_yarn/oxfmt-0…
nulltask Jun 12, 2026
104c1c4
Merge pull request #103 from nulltask/dependabot/npm_and_yarn/node-we…
nulltask Jun 12, 2026
2ac681c
Merge branch 'devel' of https://github.com/nulltask/be-music into devel
nulltask Jun 12, 2026
ebfdba7
docs(changeset): note the node-web-audio-api 2.0.0 bump for player
nulltask Jun 14, 2026
d9df3f6
chore: version packages
nulltask Jun 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions docs/bms-spec.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,19 @@
- [x] `#PLAYER=4` (BATTLE) はメタ情報として保持し、現状は専用の 2 人対戦プレイを実装しない
- [x] チャンネル `D1-D9` (地雷) を解釈
- [x] チャンネル `E1-E9` (地雷) を解釈
- [x] MANUAL モードで地雷タイミング入力を `BAD` 判定に反映
- [x] MANUAL モードの地雷ダメージに譜面の ID base で解釈したオブジェクト値 (`object value / 2`) を適用しつつ、判定表示は `BAD` のままにする
- [x] MANUAL モードで「キー ON かつ `GOOD` 窓以内」の地雷を爆発させる(押下時・押しっぱなし通過の両方。LR2 準拠)
- [x] 地雷ダメージはオブジェクト値(大文字 base36)をそのままパーセントとして適用し、判定・コンボには影響させない(LR2 / beatoraja 準拠)
- [x] `#WAV00` が定義されている場合、MANUAL モードの地雷ヒットで爆発音として使用
- [x] 地雷を `TOTAL` / `EX-SCORE` の対象ノート数から除外

#### 地雷ダメージの根拠

地雷ダメージは BM98 時代の基礎 BMS 仕様には含まれていないため、現実装では後年公開された地雷拡張系の資料を根拠にしています
地雷ダメージは BM98 時代の基礎 BMS 仕様には含まれていないため、現実装では後年公開された地雷拡張系の資料と LR2 互換実装を根拠にしています

- 地雷ダメージを `value / 2` とする根拠は Hitkey の command memo です。`[01-ZZ]` をダメージ量とし、ゲージが `value / 2` だけ減る整理に従います。
- `#WAV00` を地雷リアクション専用とする扱いと、`ZZ` を即死級の値とみなす根拠は Obj Tech Lovers chapter3-2 / chapter4-7 で補強しています。
- `be-music` では groove gauge を LR2 互換の `2-100%` で実装しているため、`ZZ` の実際の効果はゲージ下限 `2%` への clamp です。
- 発動条件「キー ON かつ判定線との間隔が `GOOD` 判定以内(押しっぱなし通過を含む)」と「ダメージ = 指定値そのまま(10進換算%)」は losak「地雷オブジェに関するアレコレ (LR2)」の LR2 実機検証に従います。beatoraja(jbms-parser `Section.java` / `JudgeManager`)も生値をダメージとして直接適用しており一致します。
- Hitkey command memo の `value / 2` は nanasi 系統の仕様であり、LR2 の実挙動とは異なるため採用していません。
- `#WAV00` を地雷リアクション専用とする扱いは Hitkey memo / Obj Tech Lovers chapter3-2 / chapter4-7 で補強しています。
- `ZZ`(= 1295%)は survival 系ゲージ(HARD / DEATH)では即 FAILED、`2-100%` の GROOVE / EASY ではゲージ下限 `2%` への clamp になります(losak の「EASY / GROOVE なら 2% になるだけ」と一致)。
- [x] チャンネル `SC` を `#SCROLLxx` 参照イベントとして保持
- [x] チャンネル `SC` を音声トリガー対象から除外
- [x] チャンネル `SC` のスクロール速度を player 描画へ反映
Expand Down Expand Up @@ -299,7 +300,7 @@ runtime と round-trip の扱い:
- [ ] 未定義 `#BPMxx` / `#STOPxx` 参照時の互換挙動(無視・既定値・エラー)
- [ ] `#STOPxx` 空定義参照(例: `#05209:` の未定義トークン)時の互換挙動
- [ ] 行頭インデント付きコマンド(先頭空白 + `#COMMAND`)の受理方針
- [ ] 制御構文の別表記 `#ELSE IF` / `#END IF` / `#END` の受理方針
- [x] 制御構文の別表記 `#ELSE IF n` / `#END IF` / 値なし `#END` を `#ELSEIF n` / `#ENDIF` として受理(保存・出力時は正規形へ正規化、`#END <他の値>` は未知ヘッダのまま)
- [ ] `#IF` / `#SWITCH` ブロック未終端(`#ENDIF` / `#ENDSW` 欠落)時の EOF 補完規則
- [x] Bemuse 拡張ヘッダ `#SPEEDxx` の受理と実行時反映
- [x] Bemuse 拡張チャンネル `#xxxSP`(spacing factor)の受理と描画反映
Expand Down Expand Up @@ -354,6 +355,7 @@ runtime と round-trip の扱い:
- `AUTO` / `AUTO SCRATCH` / `MANUAL` のいずれでも、再生音声はリアルタイムトリガ方式を使用する
- `--play-volume` は演奏レーン系、`--bgm-volume` は非演奏レーン系へ適用する
- `SC` / 地雷 / `LNOBJ` 終端抑止対象イベントは音声トリガ対象から除外する
- 未定義・ファイル欠落・デコード失敗の `#WAVxx` 参照は無音として扱う(デバッグ用に `missingSampleToneSeconds` オプションで代替トーンを有効化できる)

### SCROLL/BPM/STOP 互換ポリシー

Expand All @@ -367,14 +369,19 @@ runtime と round-trip の扱い:
## イベント位置の扱い

- `data` は 2文字単位で分割し、`00` は空イベント
- `data` は最初の空白文字で打ち切り、それ以降の文字列は無視する
- 位置は `position: [numerator, denominator]` として保持
- `denominator = トークン数`
- `numerator = トークンの0始まりインデックス`

## 文字コード

- BOM 付き UTF-8 / UTF-16LE / UTF-16BE を優先
- BOM がない場合は `shift_jis`, `utf8`, `euc-jp`, `latin1` をスコアリングして推測
- BOM 付き UTF-8 / UTF-16LE / UTF-16BE を最優先で採用
- BOM がない場合は `#CHARSET` 宣言を解決(対応エンコーディングに正規化できた場合のみ)
- ASCII のみのファイルは UTF-8 として扱う
- 厳密 UTF-8 検証(マルチバイト列を含み fatal デコードに成功)を通れば UTF-8 とみなす
- 上記以外は `shift_jis`, `utf8`, `euc-jp`, `latin1` をスコアリングして推測
- この判定は `@be-music/parser` の `decodeBmsText` に一本化されており、CLI / TUI / Web のすべてが同じ結果になる

## stringifier ルール

Expand Down
25 changes: 16 additions & 9 deletions docs/bms-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,19 @@ However, the exact specification of control syntax compatibility for files conta
- [x] `#PLAYER=4` (BATTLE) will be retained as meta information, and dedicated two-player competitive play will not be implemented at this time.
- [x] Interpret channel `D1-D9` (mine)
- [x] Interpret channel `E1-E9` (mine)
- [x] Reflect mine timing input in `BAD` judgment in MANUAL mode
- [x] Apply mine object value as MANUAL mode groove gauge damage (`object value / 2` under the chart's ID base) while keeping the judgment display as `BAD`
- [x] Detonate mines in MANUAL mode while "the key is ON and the mine is within the `GOOD` window" (both press and hold-through, LR2 behavior)
- [x] Apply the mine object value (upper-case base36) directly as the gauge-damage percentage, with no effect on judgments or combo (LR2 / beatoraja behavior)
- [x] When `#WAV00` is defined, use it as the landmine explosion sound on manual mine hit
- [x] Exclude landmines from the number of target notes for `TOTAL` / `EX-SCORE`

#### Landmine damage basis

The original BM98-era core BMS specifications do not define landmine damage as part of the base format, so this implementation follows later public extension references.
The original BM98-era core BMS specifications do not define landmine damage as part of the base format, so this implementation follows later public extension references and LR2-compatible implementations.

- `value / 2` for landmine damage is based on Hitkey's command memo (`[01-ZZ]` damage amount, gauge decreases by `value / 2`).
- `#WAV00` as the dedicated landmine reaction sound and the `ZZ` instant-death convention are corroborated by Obj Tech Lovers chapter3-2 and chapter4-7.
- In `be-music`, the practical effect of `ZZ` is a clamp to the implemented groove gauge minimum `2%`, because the player keeps the LR2-compatible `2-100%` gauge range.
- The detonation condition ("key ON and the mine within the `GOOD` window of the judge line, including hold-through") and "damage = the value itself (as a decimal percentage)" follow losak's LR2 mine writeup ("地雷オブジェに関するアレコレ"), verified against the real LR2. beatoraja (jbms-parser `Section.java` / `JudgeManager`) applies the raw value directly as damage, matching this.
- Hitkey command memo's `value / 2` is the nanasi-lineage rule and differs from LR2's actual behavior, so it is not adopted.
- `#WAV00` as the dedicated landmine reaction sound is corroborated by Hitkey's memo and Obj Tech Lovers chapter3-2 / chapter4-7.
- `ZZ` (= 1295 %) instantly FAILs survival gauges (HARD / DEATH); on the `2-100%` GROOVE / EASY gauges it clamps to the `2%` floor (matching losak's "EASY / GROOVE just drop to 2%").
- [x] Keep channel `SC` as `#SCROLLxx` reference event
- [x] Exclude channel `SC` from audio triggering
- [x] Reflect scroll speed of channel `SC` to player drawing
Expand Down Expand Up @@ -299,7 +300,7 @@ Also, volume changes will only be reflected in the initial gain of new sounds th
- [ ] Compatible behavior when referencing undefined `#BPMxx` / `#STOPxx` (ignored, default value, error)
- [ ] `#STOPxx` Compatibility behavior when empty definition reference (e.g. undefined token of `#05209:`)
- [ ] Acceptance policy for commands with leading indentation (leading blank + `#COMMAND`)
- [ ] Acceptance policy for alternative notation of control syntax `#ELSE IF` / `#END IF` / `#END`
- [x] Accept the alternative spellings `#ELSE IF n` / `#END IF` / bare `#END` as `#ELSEIF n` / `#ENDIF` (normalized to the canonical form on save / output; `#END <other value>` remains an unknown header)
- [ ] EOF completion rules when `#IF` / `#SWITCH` block is unterminated (`#ENDIF` / `#ENDSW` is missing)
- [x] Acceptance of Bemuse extension header `#SPEEDxx` and reflection in note drawing distance
- [x] Bemuse expansion channel `#xxxSP` (spacing factor) acceptance and drawing reflection
Expand Down Expand Up @@ -354,6 +355,7 @@ Also, volume changes will only be reflected in the initial gain of new sounds th
- Playback audio uses real-time trigger method for any of `AUTO` / `AUTO SCRATCH` / `MANUAL`
- `--play-volume` applies to performance lanes, `--bgm-volume` applies to non-performance lanes.
- `SC` / Landmine / `LNOBJ` Exclude terminal suppression target events from audio trigger targets
- Undefined, missing, or undecodable `#WAVxx` references are treated as silence (a substitute debug tone can be enabled with the `missingSampleToneSeconds` option)

### SCROLL/BPM/STOP compatibility policy

Expand All @@ -367,14 +369,19 @@ Also, volume changes will only be reflected in the initial gain of new sounds th
## Handling of event location

- `data` splits by two characters, `00` is empty event
- `data` is truncated at the first whitespace character; everything after it is ignored
- Position is kept as `position: [numerator, denominator]`
- `denominator = number of tokens`
- `numerator = zero-based token index`

## Character Encoding

- Prefer UTF-8 / UTF-16LE / UTF-16BE with BOM
- If BOM is missing, infer by scoring `shift_jis`, `utf8`, `euc-jp`, `latin1`
- Adopt UTF-8 / UTF-16LE / UTF-16BE with BOM first
- If there is no BOM, resolve the `#CHARSET` declaration (only when it canonicalizes to a supported encoding)
- ASCII-only files are treated as UTF-8
- A buffer that passes strict UTF-8 validation (fatal decode succeeds with multibyte sequences present) is UTF-8
- Otherwise infer by scoring `shift_jis`, `utf8`, `euc-jp`, `latin1`
- This detection is unified in `@be-music/parser`'s `decodeBmsText`, so CLI / TUI / Web all produce the same result

## stringifier rules

Expand Down
5 changes: 3 additions & 2 deletions docs/bmson-spec.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- [x] `info` 拡張項目: `mode_hint`
- [x] `info` 拡張項目: `judge_rank`
- [x] `info` 拡張項目: `total`
- [x] beatoraja 拡張 `info.ln_type` と note 単位 `t`(1: LN / 2: CN / 3: HCN)を IR に保持し、player の LN モード解決と stringifier 出力に使用
- [x] `info` 拡張項目: `back_image`
- [x] `info` 拡張項目: `eyecatch_image`
- [x] `info` 拡張項目: `banner_image`
Expand All @@ -47,7 +48,7 @@
- [x] `info.back_image`, `info.banner_image`, `info.eyecatch_image` を unified `metadata.backBmp`, `metadata.banner`, `metadata.stageFile` へ mirror
- [x] 明示的な `lines: []` を barline suppression として扱い、`bmson.barlinesSuppressed` に保持
- [x] `lines` 欠落時は bmson 既定の 4/4 model として扱う
- [x] `info.total` 欠落または非正値は bmson 既定値 `100` として扱う
- [x] `info.total` 欠落・非数は bmson 既定値 `100`、負値は仕様どおり絶対値、`0` は保持(ライフバーが増えない)として扱う
- [x] `info.init_bpm` 欠落、0、負値、非数値は parse error として扱う
- [ ] `version` の妥当性検証(`null` のエラー化、未指定時の legacy 扱い方針)
- [ ] `version` の互換判定を SemVer で行う方針の明文化
Expand Down Expand Up @@ -132,7 +133,7 @@
- `info.resolution` を `bmson.info.resolution` に保持
- 互換としてルート `resolution` も読み取り、`info.resolution` がなければ採用
- `info.init_bpm` が欠落、または正の有限数ではない場合は document を reject
- `info.total` が有限数ならそのまま使い、それ以外は bmson 既定値 `100` を適用
- `info.total` が有限数なら使い(負値は仕様どおり絶対値へ正規化)、それ以外は bmson 既定値 `100` を適用
- `info.back_image`, `info.banner_image`, `info.eyecatch_image` を normalized metadata image field に mirror
- `sound_channels[i].name` を `resources.wav[key]` に登録
- `key = base36(i + 1)` を2桁化
Expand Down
5 changes: 3 additions & 2 deletions docs/bmson-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ This document defines how `packages/parser` / `packages/stringifier` handles BMS
- [x] `info` extended item: `mode_hint`
- [x] `info` extended item: `judge_rank`
- [x] `info` extended item: `total`
- [x] Preserve the beatoraja extensions `info.ln_type` and per-note `t` (1: LN / 2: CN / 3: HCN) in the IR, used for the player's LN mode resolution and stringifier output
- [x] `info` extended item: `back_image`
- [x] `info` extended item: `eyecatch_image`
- [x] `info` extended item: `banner_image`
Expand All @@ -47,7 +48,7 @@ This document defines how `packages/parser` / `packages/stringifier` handles BMS
- [x] Mirror `info.back_image`, `info.banner_image`, and `info.eyecatch_image` into unified `metadata.backBmp`, `metadata.banner`, and `metadata.stageFile`
- [x] Treat an explicit `lines: []` as barline suppression and keep it in `bmson.barlinesSuppressed`
- [x] Treat missing `lines` as the bmson 4/4 default model
- [x] Treat missing or non-positive `info.total` as the bmson default `100`
- [x] Treat missing / non-numeric `info.total` as the bmson default `100`, take the absolute value of a negative one, and keep `0` (the lifebar does not increase)
- [x] Treat missing, zero, negative, or non-numeric `info.init_bpm` as a parse error
- [ ] Validation of `version` (turning `null` into an error, treating legacy as unspecified)
- [ ] Clarification of policy for determining compatibility of `version` with SemVer
Expand Down Expand Up @@ -132,7 +133,7 @@ This document defines how `packages/parser` / `packages/stringifier` handles BMS
- Keep `info.resolution` in `bmson.info.resolution`
- Also read the root `resolution` for compatibility, and adopt it if `info.resolution` is not present.
- Reject the document when `info.init_bpm` is missing or not a positive finite number.
- Use `info.total` as-is when finite, otherwise apply the bmson default `100`.
- Use `info.total` when finite (normalizing a negative value to its absolute value per the spec), otherwise apply the bmson default `100`.
- Mirror `info.back_image`, `info.banner_image`, and `info.eyecatch_image` into normalized metadata image fields.
- Register `sound_channels[i].name` in `resources.wav[key]`
- Convert `key = base36(i + 1)` to 2 digits
Expand Down
Loading
Loading