.
This commit is contained in:
@@ -1,55 +1,114 @@
|
||||
import { Course, Bar } from 'tja-parser';
|
||||
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) => {
|
||||
return {
|
||||
//@ts-expect-error
|
||||
type: note.type,
|
||||
time: note.getTimingMS()
|
||||
}
|
||||
}));
|
||||
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: { time: number }[]): number {
|
||||
if (notes.length < 2) return 0;
|
||||
const duration = (notes[notes.length - 1].time - notes[0].time) / 1000;
|
||||
return duration > 0 ? notes.length / duration : 0;
|
||||
// 밀도 관련
|
||||
export function getAverageDensity(notes: HitNote[]) {
|
||||
return notes.length / (notes[notes.length - 1].getTimingMS() - notes[0].getTimingMS()) * 1000
|
||||
}
|
||||
|
||||
export function getPeakDensity(notes: { time: number }[]): number {
|
||||
if (notes.length === 0) return 0;
|
||||
let maxPeak = 0;
|
||||
const windowSize = 1000;
|
||||
export function getPeakDensity(notes: HitNote[]) {
|
||||
let peakDensity = 0;
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
let count = 0;
|
||||
const startTime = notes[i].time;
|
||||
for (let j = i; j < notes.length && notes[j].time < startTime + windowSize; j++) {
|
||||
count++;
|
||||
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;
|
||||
}
|
||||
maxPeak = Math.max(maxPeak, count);
|
||||
}
|
||||
return maxPeak;
|
||||
return peakDensity;
|
||||
}
|
||||
|
||||
export function getMaxCombo(notes: any[]): number {
|
||||
return notes.length;
|
||||
// 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.getAllNotes(course);
|
||||
const notes = Factor.filterHitNotes(course)
|
||||
return {
|
||||
average_density: Factor.getAverageDensity(notes),
|
||||
peak_density: Factor.getPeakDensity(notes),
|
||||
max_combo: Factor.getMaxCombo(notes)
|
||||
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)
|
||||
};
|
||||
}
|
||||
@@ -52,7 +52,7 @@ def predict():
|
||||
|
||||
with torch.no_grad():
|
||||
input_tensor = torch.from_numpy(X_scaled).float()
|
||||
pred = model(input_tensor) * 11 + 1
|
||||
pred = model(input_tensor) * 12 + 0.5
|
||||
print(float(pred.item()))
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}", file=sys.stderr)
|
||||
@@ -89,7 +89,7 @@ function main() {
|
||||
console.error(`Error: Difficulty '${difficulty}' not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
const score = runInference(workingDir, factorize(chart));
|
||||
console.log(`\n🎯 Predicted Difficulty: ${score.toFixed(2)}`);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import torch.nn as nn
|
||||
import torch.optim as optim
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
|
||||
MAX_MARGIN_LIMIT = 3
|
||||
MAX_MARGIN_LIMIT = 10
|
||||
|
||||
class DifficultyNet(nn.Module):
|
||||
def __init__(self, input_dim):
|
||||
@@ -83,7 +83,7 @@ def train(script_dir, working_dir, data_dir, train_count, val_count, margin, val
|
||||
|
||||
# 현재 학습 데이터에 대한 오차 분석
|
||||
with torch.no_grad():
|
||||
diff = torch.abs((preds_train * 11 + 1) - (y_train * 11 + 1))
|
||||
diff = torch.abs((preds_train * 12 + 0.5) - (y_train * 12 + 0.5))
|
||||
train_mae = torch.mean(diff).item()
|
||||
train_max = torch.max(diff).item()
|
||||
|
||||
@@ -114,7 +114,7 @@ def train(script_dir, working_dir, data_dir, train_count, val_count, margin, val
|
||||
val_df = df.sample(n=min(int(val_count), len(df)))
|
||||
X_val = torch.FloatTensor(val_df[X_cols].values).to(device)
|
||||
y_val_raw = val_df['상수'].values
|
||||
preds = model(X_val) * 11 + 1
|
||||
preds = model(X_val) * 12 + 0.5
|
||||
y_val_tensor = torch.FloatTensor(y_val_raw).unsqueeze(1).to(device)
|
||||
diff_tensor = torch.abs(preds - y_val_tensor)
|
||||
mae = torch.mean(diff_tensor).item()
|
||||
@@ -135,7 +135,7 @@ def train(script_dir, working_dir, data_dir, train_count, val_count, margin, val
|
||||
if mae <= margin and max_error <= (margin * MAX_MARGIN_LIMIT):
|
||||
print(f" [Iter {i}] ✅ PASS (MAE: {mae:.4f}, MAX: {max_error:.4f})")
|
||||
else:
|
||||
print(f" [Iter {i}] ❌ FAIL (MAE: {mae:.4f} > {margin}, MAX: {max_error:.4f} > {margin * MAX_MARGIN_LIMIT})")
|
||||
print(f" [Iter {i}] ❌ FAIL (MAE: {mae:.4f} > {margin} || MAX: {max_error:.4f} > {margin * MAX_MARGIN_LIMIT})")
|
||||
all_passed = False
|
||||
break
|
||||
|
||||
|
||||
Reference in New Issue
Block a user