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);