From 13e45ab0be6452a577103c840057f8106e12e6e2 Mon Sep 17 00:00:00 2001 From: nichinichisou Date: Fri, 16 Oct 2026 12:31:15 +0800 Subject: [PATCH 1/2] fix(story): play a cue missing from its sheet as silence An episode whose sound table lists a cue that the cue sheet does not hold stopped at preload with "cue ... not decoded". The cue now plays as an empty sound, so the line runs on and the rest of the episode plays. Co-Authored-By: Claude Sonnet 5.5 --- src/engine/audio.js | 12 +++++++++++- src/story/silent-audio.js | 5 ++--- tests/unit/engine-audio.test.mjs | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/engine/audio.js b/src/engine/audio.js index e29f66d..b1a3680 100644 --- a/src/engine/audio.js +++ b/src/engine/audio.js @@ -64,6 +64,9 @@ export class SoundCategoryVolumes { } }; +// the meta of a cue the sheet lacks: no file, no samples +export const MISSING_CUE = Object.freeze({ file: null, sampleRate: 48000, channels: 1, samples: 0, missing: true }); + export class Audio { constructor(resolve, loop, { context = null, assets = null } = {}) { this.ownsContext = !context; @@ -219,7 +222,14 @@ export class Audio { const c = this.cueOf(id), key = `${c.sheet}/${c.cue}`; if (this.buffers.has(key)) continue; const meta = this.assets.json(`audio/${c.sheet}/cues.json`)[c.cue]; - if (!meta) throw new Error(`cue ${c.cue} not decoded in ${c.sheet}`); + if (!meta) { + // a sound row whose cue the sheet does not hold (a line listed with a voice that was never shipped): it + // plays as an empty sound, so the line runs on and the rest of the episode is not held back + console.warn(`cue ${c.cue} is not in cue sheet ${c.sheet}; it plays as silence`); + const rate = this.ctx.sampleRate; + this.buffers.set(key, { buf: this.ctx.createBuffer(1, 1, rate), meta: { ...MISSING_CUE, sampleRate: rate } }); + continue; + } // decoded at the context's rate; the loop points below are converted to seconds with the cue's own rate const buf = await this.ctx.decodeAudioData(this.assets.arrayBuffer(`audio/${c.sheet}/${meta.file}`)); this.buffers.set(key, { buf, meta }); diff --git a/src/story/silent-audio.js b/src/story/silent-audio.js index c79eaac..773333c 100644 --- a/src/story/silent-audio.js +++ b/src/story/silent-audio.js @@ -1,4 +1,4 @@ -import { SOUND_CATEGORY, SoundCategoryVolumes } from "../engine/audio.js"; +import { MISSING_CUE, SOUND_CATEGORY, SoundCategoryVolumes } from "../engine/audio.js"; // SilentAudio: the SoundManager of a story session without Web Audio (Node, or a page that plays the story muted). // It keeps the sounds' timing on the player loop's clock and plays nothing: a cue lasts its length in cues.json @@ -24,8 +24,7 @@ export class SilentAudio { cueOf(soundId) { const c = this.resolve(soundId); - const meta = this.assets.json(`audio/${c.sheet}/cues.json`)[c.cue]; - if (!meta) throw new Error(`cue ${c.cue} not in ${c.sheet}`); + const meta = this.assets.json(`audio/${c.sheet}/cues.json`)[c.cue] || MISSING_CUE; // a cue the sheet lacks is silence return { ...c, meta }; } diff --git a/tests/unit/engine-audio.test.mjs b/tests/unit/engine-audio.test.mjs index 835604f..2671127 100644 --- a/tests/unit/engine-audio.test.mjs +++ b/tests/unit/engine-audio.test.mjs @@ -178,3 +178,19 @@ test("timeStretch keeps the length ratio and the pitch of a sine", () => { assert.ok(Math.abs(rate - 2 * f) < 0.05 * 2 * f, `speed ${speed}: ${rate} crossings/s`); } }); + +test("a cue the sheet lacks plays as an empty sound and ends at once", async () => { + const ctx = fakeContext(); + const row = { _isRandomPitch: false }; + const cue = { sheet: "S", cue: "gone", category: 2, row }; + const assets = { json: () => ({}), arrayBuffer: () => { throw new Error("no file to read"); } }; + const a = new Audio(() => cue, { time: 0 }, { context: ctx, assets }); + const warn = console.warn; + console.warn = () => {}; + try { await a.preload([1]); } finally { console.warn = warn; } + const started = []; + const id = a.play(1, { onPlayStart: (info) => started.push(info) }); + assert.ok(id > 0); + assert.equal(started.length, 1); + assert.equal(ctx.sources.length, 1); +}); From 3af474b9f80048150a673c9fcc55e93d853722f1 Mon Sep 17 00:00:00 2001 From: nichinichisou Date: Fri, 16 Oct 2026 12:33:19 +0800 Subject: [PATCH 2/2] chore(release): prepare ournotes-player v0.1.8 Sets the version to 0.1.8 in package.json and package-lock.json, regenerates CHANGELOG.md with git-cliff (the 0.1.8 section) and adds release-notes/v0.1.8.txt, the body of the GitHub release. Verified with `npm test` (670 pass, 2 skipped), `npm run typecheck` and `node scripts/build.mjs --check`. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 16 ++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- release-notes/v0.1.8.txt | 8 ++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 release-notes/v0.1.8.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index c660e5b..c8a4c0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to this package. Versions follow [Semantic Versioning](https://semver.org/). +## [0.1.8](https://github.com/empty-sekai/ournotes-player/compare/v0.1.7..v0.1.8) — 2026-10-16 + +### Features +- report fixes for script problems, and pace each line _(story-author)_ + + +### Bug Fixes +- cull per-object lights against the layer of each draw _(story)_ +- refuse nested DOTween Sequences _(story)_ +- play a cue missing from its sheet as silence _(story)_ + + +### Documentation +- list every export of the story and UI entry points + + ## [0.1.7](https://github.com/empty-sekai/ournotes-player/compare/v0.1.6..v0.1.7) — 2026-10-14 ### Features diff --git a/package-lock.json b/package-lock.json index 9a35155..33a900e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ournotes-player", - "version": "0.1.7", + "version": "0.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ournotes-player", - "version": "0.1.7", + "version": "0.1.8", "license": "AGPL-3.0-only", "devDependencies": { "@eslint/js": "10.0.1", diff --git a/package.json b/package.json index 4aab9e9..078bb57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ournotes-player", - "version": "0.1.7", + "version": "0.1.8", "description": "Browser player for BanG Dream! Our Notes: live charts, story episodes and Live2D models with the game's own shaders (WebGL2 + WebAudio, custom elements)", "keywords": [ "bang-dream", diff --git a/release-notes/v0.1.8.txt b/release-notes/v0.1.8.txt new file mode 100644 index 0000000..48f4ed0 --- /dev/null +++ b/release-notes/v0.1.8.txt @@ -0,0 +1,8 @@ +ournotes-player 0.1.8 plays a story line whose cue is missing from its cue sheet as silence, and fixes story lighting and DOTween handling. + +- A story sound whose cue the cue sheet does not hold no longer stops the episode at preload; the line runs on without that sound. +- Per-object lights are culled against the layer of each draw. +- Nested DOTween Sequences are refused. +- The experimental story author reports fixes for script problems and paces each line. + +Validation: type checks, reproducible builds and package checks pass; the unit tests pass.