xgboost
This commit is contained in:
18
preprocess/factorize.ts
Normal file
18
preprocess/factorize.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Course } from "tja-parser";
|
||||
import { filterHitNotes } from "./util";
|
||||
|
||||
export type NoteFactor = [type: 0 | 1, bpm: number, scroll: number, delta: number]
|
||||
export function factorize(course: Course) {
|
||||
const hitNotes = filterHitNotes(course);
|
||||
const factors: NoteFactor[] = [];
|
||||
for (let i = 0; i < hitNotes.length; i++) {
|
||||
const note = hitNotes[i];
|
||||
factors.push([
|
||||
(note.type === 1 || note.type === 3) ? 0 : 1,
|
||||
note.getBPM() / 100,
|
||||
note.getScroll() / 5,
|
||||
i === 0 ? 0 : (note.getTimingMS() - hitNotes[i - 1].getTimingMS()) / 1000
|
||||
])
|
||||
}
|
||||
return factors;
|
||||
}
|
||||
121
preprocess/featurize.ts
Normal file
121
preprocess/featurize.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { Course, Bar, Note, HitNote } from 'tja-parser';
|
||||
|
||||
export namespace Feature {
|
||||
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[]) {
|
||||
if (notes.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
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[]) {
|
||||
if(notes.length === 0) return 0;
|
||||
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 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 getRhythmComplexity(notes: HitNote[]) {
|
||||
let complexity = 0;
|
||||
for (let i = 2; i < notes.length; i++) {
|
||||
const d1 = notes[i].getTimingMS() - notes[i - 1].getTimingMS()
|
||||
const d2 = notes[i - 1].getTimingMS() - notes[i - 2].getTimingMS()
|
||||
|
||||
const ratio = d1 / d2;
|
||||
const log = Math.log2(ratio);
|
||||
|
||||
if (Math.abs(log - Math.round(log)) < 1e-3) {
|
||||
complexity++;
|
||||
}
|
||||
}
|
||||
return complexity;
|
||||
}
|
||||
|
||||
export function getColorComplexity(notes: HitNote[]) {
|
||||
let complexity = 0;
|
||||
for (let i = 2; i < notes.length; i++) {
|
||||
if (notes[i].type % 2 != notes[i - 2].type % 2) {
|
||||
complexity += 1 / ((notes[i].getTimingMS() - notes[i - 2].getTimingMS()) ** 2)
|
||||
}
|
||||
}
|
||||
return complexity;
|
||||
}
|
||||
}
|
||||
|
||||
export function featurize(course: Course) {
|
||||
const notes = Feature.filterHitNotes(course)
|
||||
return {
|
||||
note_count: notes.length,
|
||||
density_avg: Feature.getAverageDensity(notes),
|
||||
density_peak: Feature.getPeakDensity(notes),
|
||||
bpm_avg: Feature.getAverageBPM(notes),
|
||||
bpm_change: Feature.getBpmChange(notes),
|
||||
scroll_change: Feature.getScrollChange(notes),
|
||||
rhythm_complexity: Feature.getRhythmComplexity(notes),
|
||||
color_complexity: Feature.getColorComplexity(notes)
|
||||
};
|
||||
}
|
||||
51
preprocess/parse.ts
Normal file
51
preprocess/parse.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import tjaParser, { Bar, Branch, Course } from 'tja-parser'
|
||||
|
||||
export function parseTja(tja: string): Partial<Record<'oni' | 'edit', Course>> | null {
|
||||
try {
|
||||
const song = tjaParser.Song.parse(tja);
|
||||
|
||||
let oni: Course | undefined = undefined;
|
||||
let edit: Course | undefined = undefined;
|
||||
|
||||
if (song.course?.oni) {
|
||||
const noteGroups = song.course.oni.noteGroups;
|
||||
oni = song.course.oni;
|
||||
oni.noteGroups = []
|
||||
|
||||
for (const noteGroup of noteGroups) {
|
||||
if (noteGroup instanceof Bar) {
|
||||
oni.pushNoteGroups(noteGroup)
|
||||
}
|
||||
else if (noteGroup instanceof Branch) {
|
||||
const bar = noteGroup.master || noteGroup.advanced || noteGroup.normal;
|
||||
if (bar) {
|
||||
oni.pushNoteGroups(...bar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (song.course?.edit) {
|
||||
const noteGroups = song.course.edit.noteGroups;
|
||||
edit = song.course.edit;
|
||||
edit.noteGroups = []
|
||||
|
||||
for (const noteGroup of noteGroups) {
|
||||
if (noteGroup instanceof Bar) {
|
||||
edit.pushNoteGroups(noteGroup)
|
||||
}
|
||||
else if (noteGroup instanceof Branch) {
|
||||
const bar = noteGroup.master || noteGroup.advanced || noteGroup.normal;
|
||||
if (bar) {
|
||||
edit.pushNoteGroups(...bar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { oni, edit }
|
||||
}
|
||||
catch (err){
|
||||
console.error(err)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
15
preprocess/util.ts
Normal file
15
preprocess/util.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Bar, Course, HitNote } from "tja-parser";
|
||||
|
||||
export function filterHitNotes(course: Course): HitNote[] {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user