Files
fumen-measure-analyze/scripts/factorize.ts
2026-04-20 00:21:53 +09:00

58 lines
2.9 KiB
TypeScript

import { TJAParser, NoteSequence, BPMChangeCommand, MeasureCommand, DelayCommand, ScrollCommand, MasterBranchMarkerCommand, BranchMarkerCommand } from "tja";
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { join, basename } from "node:path";
async function factorizeTJA(filePath: string) {
try {
const content = await readFile(filePath, "utf-8");
const parsed = TJAParser.parse(content, false);
const results: any[] = [];
const targetCourses = parsed.courses.filter(c => ["Oni", "Edit", "Ura"].includes(c.difficulty.toString()));
for (const course of targetCourses) {
const commands = course.activeCourse.getCommands();
const timeline: any[] = [];
let currentTime = 0, currentBPM = parsed.bpm || 120, currentMeasure = { n: 4, d: 4 };
let inBranch = false, isMaster = false, scrollChanges = 0, bpmChanges = 0;
commands.forEach(cmd => {
if (cmd instanceof BPMChangeCommand) { currentBPM = cmd.bpm; bpmChanges++; }
else if (cmd instanceof MeasureCommand) currentMeasure = { n: cmd.value.numerator, d: cmd.value.denominator };
else if (cmd instanceof DelayCommand) currentTime += cmd.delay;
else if (cmd instanceof ScrollCommand) scrollChanges++;
else if (cmd instanceof MasterBranchMarkerCommand) { inBranch = true; isMaster = true; }
else if (cmd instanceof BranchMarkerCommand) { inBranch = true; isMaster = false; }
else if (cmd instanceof NoteSequence) {
if (inBranch && !isMaster) return;
const interval = ((60 / currentBPM) * 4 * (currentMeasure.n / currentMeasure.d)) / cmd.notes.length;
cmd.notes.forEach(note => {
if (!note.isBlank && !note.isMeasureEnd) timeline.push({ time: currentTime, isDon: note.isDon || note.isBigDon });
currentTime += interval;
});
}
});
if (timeline.length === 0) continue;
let peakNps = 0;
for (let i = 0; i < timeline.length; i++) {
let count = 0;
for (let j = i; j < timeline.length && timeline[j].time < timeline[i].time + 2; j++) count++;
peakNps = Math.max(peakNps, count / 2);
}
let transitions = 0;
for (let i = 1; i < timeline.length; i++) if (timeline[i].isDon !== timeline[i-1].isDon) transitions++;
results.push({
difficulty: course.difficulty.toString(),
factors: { physical: peakNps, stamina: 0, tech: transitions / timeline.length, accuracy: 0.1, reading: (bpmChanges * 0.5) + (scrollChanges * 0.2) }
});
}
if (results.length > 0) {
await mkdir("sample/output/factorize", { recursive: true });
await writeFile(join("sample/output/factorize", `${basename(filePath, ".tja")}.json`), JSON.stringify({ title: parsed.title, file: filePath, analysis: results }, null, 2));
}
} catch (e) { console.error(`Failed ${filePath}: ${e}`); }
}
for (const f of process.argv.slice(2)) await factorizeTJA(f);