114 lines
3.8 KiB
TypeScript
114 lines
3.8 KiB
TypeScript
import { Course, Bar, Note, HitNote } from 'tja-parser';
|
|
|
|
export namespace Factor {
|
|
export function filterHitNotes(course: Course) {
|
|
const notes: HitNote[] = [];
|
|
course.noteGroups.forEach((g) => {
|
|
if (g instanceof Bar) {
|
|
for (const note of g.getNotes()) {
|
|
if (note instanceof HitNote) {
|
|
notes.push(note);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
return notes;
|
|
}
|
|
|
|
export function getAllNotes(course: Course) {
|
|
const notes: { type: number, time: number }[] = [];
|
|
for (const group of course.noteGroups) {
|
|
if (group instanceof Bar) {
|
|
notes.push(...group.getNotes().map((note) => ({
|
|
//@ts-expect-error
|
|
type: note.type,
|
|
time: note.getTimingMS()
|
|
})));
|
|
}
|
|
}
|
|
return notes.sort((a, b) => a.time - b.time).filter(n => [1, 2, 3, 4].includes(n.type));
|
|
}
|
|
|
|
// 밀도 관련
|
|
export function getAverageDensity(notes: HitNote[]) {
|
|
return notes.length / (notes[notes.length - 1].getTimingMS() - notes[0].getTimingMS()) * 1000
|
|
}
|
|
|
|
export function getPeakDensity(notes: HitNote[]) {
|
|
let peakDensity = 0;
|
|
for (let i = 0; i < notes.length; i++) {
|
|
let noteCount = 1;
|
|
for (let j = i + 1; j < notes.length && (notes[j].getTimingMS() - notes[i].getTimingMS() <= 1000); j++) {
|
|
noteCount++;
|
|
}
|
|
if (peakDensity < noteCount) {
|
|
peakDensity = noteCount;
|
|
}
|
|
}
|
|
return peakDensity;
|
|
}
|
|
|
|
// BPM 관련
|
|
export function getAverageBPM(notes: HitNote[]) {
|
|
|
|
const averageBPM = notes.reduce((p, note) => p + note.getBPM().valueOf(), 0) / notes.length;
|
|
return averageBPM;
|
|
}
|
|
|
|
export function getBpmChange(notes: HitNote[]) {
|
|
let bpmChange = 0;
|
|
for (let i = 1; i < notes.length; i++) {
|
|
if (Math.abs(notes[i].getBPM() - notes[i - 1].getBPM()) > 1.5) {
|
|
bpmChange++;
|
|
}
|
|
};
|
|
return bpmChange;
|
|
}
|
|
|
|
// 복잡성
|
|
export function getComplexity(notes: HitNote[]) {
|
|
let complexity = 0;
|
|
for (let i = 2; i < notes.length; i++) {
|
|
let localComplexity = 0;
|
|
|
|
// ddk 또는 dkk류면 1, 아니면 0.5
|
|
if (
|
|
(notes[i].type % 2 === notes[i - 1].type % 2 && notes[i - 1].type % 2 !== notes[i - 2].type % 2) ||
|
|
(notes[i].type % 2 !== notes[i - 1].type % 2 && notes[i - 1].type % 2 === notes[i - 2].type % 2)
|
|
) {
|
|
localComplexity = 1;
|
|
} else {
|
|
localComplexity = 0.5
|
|
}
|
|
|
|
// 시간 차가 짧을 수록 complexity 증가
|
|
localComplexity *= (1 / (notes[i].getTimingMS() - notes[i - 1].getTimingMS()) + 1 / (notes[i - 1].getTimingMS() - notes[i - 2].getTimingMS()))
|
|
complexity += localComplexity;
|
|
}
|
|
return complexity / notes.length;
|
|
}
|
|
|
|
// 스크롤 변화
|
|
export function getScrollChange(notes: HitNote[]) {
|
|
let bpmChange = 0;
|
|
for (let i = 1; i < notes.length; i++) {
|
|
if (Math.abs(notes[i].getBPM() * notes[i].getScroll() - notes[i - 1].getBPM() * notes[i - 1].getScroll()) > 1.5) {
|
|
bpmChange++;
|
|
}
|
|
};
|
|
return bpmChange;
|
|
}
|
|
}
|
|
|
|
export function factorize(course: Course) {
|
|
const notes = Factor.filterHitNotes(course)
|
|
return {
|
|
note_count: notes.length,
|
|
density_avg: Factor.getAverageDensity(notes),
|
|
density_peak: Factor.getPeakDensity(notes),
|
|
bpm_avg: Factor.getAverageBPM(notes),
|
|
bpm_change: Factor.getBpmChange(notes),
|
|
complexity: Factor.getComplexity(notes),
|
|
scroll_change: Factor.getScrollChange(notes)
|
|
};
|
|
} |