Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Example pages. Open them with any static server at the repository root:
| [examples/basic.html](examples/basic.html) | one `<ournotes-player>` |
| [examples/iframe.html](examples/iframe.html) | a page holding one player, ready to embed with an `<iframe>` (`?src=`) |
| [examples/chart-list](examples/chart-list) | lists a site's `charts.json` and plays the chosen chart; a site of several regions or languages switches with `?region=&lang=` |
| [examples/songs](examples/songs) | chart data: reads a site's `music-data.json` (nnnotes music-data, with ournotes-deck's whole-live statistics, Gekisou on) and `jackets/` (optional); rankings by efficiency (expectation and range over the random skill order), event rank (power needed, chance), speed, level, notes, length and skip score, a scatter plot and level distribution, chart details (BPM, density, fevers with their Gekisou missions, skill timeline, notes by kind, skill position weights, score ranks) and a guide to the model; styled after moenotes' Sirius theme (light / dark), song pages link to moenotes |
| [examples/songs](examples/songs) | chart data: reads a site's `music-data.json` (nnnotes music-data, with ournotes-deck's whole-live statistics, Gekisou on) and `jackets/` (optional); rankings by efficiency (expectation and range over the random skill order), event rank (power needed, chance), speed, level, notes, length and skip score, a scatter plot and level distribution, chart details (BPM, density, fevers with their Gekisou missions, skill timeline, notes by kind, skill position weights, score ranks, the rank measures of the Gekisou ranges, single-skill aptitude and chart factors; aptitude gains cannot be added) and a guide to the model; styled after moenotes' Sirius theme (light / dark), song pages link to moenotes |
| [examples/story-list](examples/story-list) | lists a site's `stories.json` and plays the chosen episode |
| [examples/story](examples/story) | plays one episode (`?story=` or `?src=`). Live2D's two files come from `?core=` and `?motionsync=`, the Spine runtime for home talks from `?spine=` |
| [examples/live2d](examples/live2d) | lists a site's `models.json`, shows the chosen model and plays its motions and expressions |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ button.onclick = () => player.play(); // 浏览器要求在用户操作中开
| [examples/basic.html](examples/basic.html) | 一个 `<ournotes-player>` |
| [examples/iframe.html](examples/iframe.html) | 只含一个播放器的页面,可用 `<iframe>` 嵌入(`?src=`) |
| [examples/chart-list](examples/chart-list) | 列出站点的 `charts.json` 并播放所选谱面;多区服或多语言的站点用 `?region=&lang=` 切换 |
| [examples/songs](examples/songs) | 谱面数据:读站点的 `music-data.json`(nnnotes music-data,含 ournotes-deck 的激走整局统计)和 `jackets/`(可选),按效率(随机技能顺序下的期望与区间)、活动评级(所需综合力、达成率)、速度、等级、Notes、时长、跳过得分排行,散点图和等级分布,谱面详情(BPM、密度、Fever 与激走任务、技能时间轴、音符构成、技能位权重、评级门槛)和模型说明;外观沿用 moenotes 的 Sirius 主题(浅色 / 深色),歌曲资料页跳转 moenotes |
| [examples/songs](examples/songs) | 谱面数据:读站点的 `music-data.json`(nnnotes music-data,含 ournotes-deck 的激走整局统计)和 `jackets/`(可选),按效率(随机技能顺序下的期望与区间)、活动评级(所需综合力、达成率)、速度、等级、Notes、时长、跳过得分排行,散点图和等级分布,谱面详情(BPM、密度、Fever 与激走任务、技能时间轴、音符构成、技能位权重、评级门槛、各激走区间的名次指标、单个激走技能的适性与谱面因子;适性不可相加)和模型说明;外观沿用 moenotes 的 Sirius 主题(浅色 / 深色),歌曲资料页跳转 moenotes |
| [examples/story-list](examples/story-list) | 列出站点的 `stories.json` 并播放所选剧情 |
| [examples/story](examples/story) | 播放一集剧情(`?story=` 或 `?src=`;Live2D 的两个文件用 `?core=`、`?motionsync=`,据点对话的 Spine 运行时用 `?spine=`) |
| [examples/live2d](examples/live2d) | 列出站点的 `models.json`,显示所选模型并播放其动作与表情 |
Expand Down
55 changes: 55 additions & 0 deletions examples/songs/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,55 @@ export const refigure = (rows, data, scenario) => {
return rows;
};

// music-data.json's `gekisouCatalog` tables by id, built once per file.
const CATALOGS = new WeakMap();
const catalogOf = (data) => {
if (data && CATALOGS.has(data)) return CATALOGS.get(data);
const cat = (data && data.gekisouCatalog) || {};
const out = {};
for (const [k, list] of Object.entries(cat)) {
if (Array.isArray(list)) out[k] = new Map(list.filter((x) => x && x.id !== undefined).map((x) => [x.id, x]));
}
if (data && typeof data === "object") CATALOGS.set(data, out);
return out;
};

// A Gekisou skill (`table` "skills") or Gekisou support skill ("supportSkills") of `gekisouCatalog` by id: its name in
// a language (pickText: else Japanese, else any language; "#id" without one), mission and maxLevel (null when the
// catalog lacks them, as in older data).
export const gekisouSkill = (data, table, id, lang) => {
const e = (catalogOf(data)[table] || new Map()).get(id);
return {
id,
name: pickText(e && e.name, lang) || `#${id}`,
mission: e ? e.mission ?? null : null,
maxLevel: e ? e.maxLevel ?? null : null,
};
};

// The skills of an aptitude shape (music-data.json deck.gekisouAptitude.shapes[]: member Gekisou skills or snap
// Gekisou support skills with the same score effects) with their names (gekisouSkill, "#id" without one) and levels,
// each skill once.
export const shapeSkills = (data, shape, lang) => {
const table = shape && shape.source === "support" ? "supportSkills" : "skills";
const seen = new Set();
const out = [];
for (const s of (shape && Array.isArray(shape.skills) ? shape.skills : [])) {
const key = `${s.id}:${s.level}`;
if (seen.has(key)) continue;
seen.add(key);
out.push({ ...gekisouSkill(data, table, s.id, lang), level: s.level ?? null });
}
return out;
};

// The bands a shape's band condition names (skills[].bandIds), by music-data.json's `bands` ("#id" without one).
export const shapeBands = (data, shape, lang) => {
const bands = new Map(((data && data.bands) || []).map((b) => [b.id, b]));
const ids = [...new Set((shape && Array.isArray(shape.skills) ? shape.skills : []).flatMap((s) => s.bandIds || []))].sort((a, b) => a - b);
return ids.map((id) => pickText(bands.get(id) && bands.get(id).name, lang) || `#${id}`);
};

// Whether a row matches a search text: any language of the title, reading, credits, or the music id.
export const matches = (row, text) => {
const t = String(text || "").trim().toLowerCase();
Expand Down Expand Up @@ -141,6 +190,12 @@ export const histogram = (rows, get, step = 1) => {

// moenotes (bdon.moe) has the song pages (credits, vocals, audio, jacket, chart previews); this page links to them.
export const MOENOTES = "https://bdon.moe/";
/** Same published path as MoeNotes' getMusicJacketUrl; independent of a local --jackets export. */
export function moenotesJacketUrl(assetName, assetBase = "https://assets.bdon.moe") {
if (!assetName) return null;
const key = `Image/Jacket/${assetName}`;
return `${assetBase.replace(/\/+$/, "")}/${["zh-Hans", ...key.split("/"), `${key.split("/").at(-1)}.webp`].map(encodeURIComponent).join("/")}`;
}
const MOENOTES_PREFIX = { "zh-Hans": "", "zh-Hant": "zh-tw", ja: "ja", en: "en", ko: "ko" };

// The moenotes page of a song in the language nearest to `lang` (default: Simplified Chinese, its root).
Expand Down
Loading
Loading