58 lines
2.9 KiB
TypeScript
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);
|