""" core/models.py 내부 데이터 구조 정의. 모든 core 모듈 간에 공유되는 데이터 클래스. """ from __future__ import annotations from dataclasses import dataclass, field from typing import Optional @dataclass class NoteEvent: """ 악보에서 추출한 단일 음표/쉼표 이벤트. pitch=0 이면 쉼표(rest)로 취급. duration은 4분음표 기준의 비율 (예: 1.0 = 4분음표, 0.5 = 8분음표, 2.0 = 2분음표). """ pitch: int # MIDI 번호 (0 = rest, 60 = C4) start: float # 시작 시각 (4분음표 기준 beat) duration: float # 지속 시간 (4분음표 기준 beat) staff: int = 1 # 보표 번호 (1 = 상단, 2 = 하단) voice: int = 1 # 성부 번호 part_hint: Optional[int] = None # 파트 배분 힌트 (None = 미지정) @dataclass class ConvertOptions: """ 변환 파이프라인 옵션. """ part_count: int = 0 # 출력 파트 수 (0 = 동시 발음 수 기준 자동 감지) strict_mode: bool = False # True이면 경고를 에러로 처리 prefer_sharps: bool = True # True = 올림표(#), False = 내림표(b) mock_mode: bool = True # True이면 MockOMRAdapter 사용 default_tempo: int = 120 # 기본 템포 BPM pdf_dpi: int = 150 # PDF 렌더링 해상도 # 150dpi: 빠른 처리, 기본값 # 300dpi: Audiveris 실제 사용 시 권장 (인식률 향상) preprocess_enabled: bool = True # OpenCV 전처리 사용 여부 (audiveris 모드만 적용) blur_enabled: bool = True # GaussianBlur 노이즈 제거 사용 여부 binarize_enabled: bool = False # 이진화 사용 여부 (기본 off: Audiveris 자체 이진화 신뢰) binarize_method: str = "otsu" # 이진화 방식: "otsu" | "adaptive" (binarize_enabled=True 시) deskew_enabled: bool = False # 기울기 보정 (실험적, 기본 off) debug_dir: str = "" # 중간 결과물 저장 디렉토리 (빈 문자열 = 저장 안 함) engine: str = "" # OMR 엔진 지정: "" | "audiveris" | "homr" | "oemer" | "clarity" # 빈 문자열이면 mock_mode에 따라 자동 선택 pdf_pages: list = field(default_factory=list) # 처리할 PDF 페이지 번호 목록 (1-based). 빈 리스트 = 전체 처리 # 예) [1, 3] → 1, 3페이지만 처리 @dataclass class ConvertResult: """ 변환 결과. mml: 마비노기 MML 완성 문자열 "MML@p1,p2,p3;" part1/2/3: 파트별 본문 문자열 (MML@, ; 제외, 내부 검사용) """ success: bool mml: str = "" # 최종 마비노기 MML "MML@[t]p1,p2,p3;" part1: str = "" # Part 1 본문 (내부 검사/디버그용) part2: str = "" part3: str = "" warnings: list[str] = field(default_factory=list) debug_info: dict = field(default_factory=dict) def parts(self) -> list[str]: """파트 본문 목록을 리스트로 반환.""" return [self.part1, self.part2, self.part3] def format_output(self) -> str: """콘솔/파일 출력용 포맷. 파트별 개별 MML@...;로 출력.""" lines = [] for i, mml in enumerate([self.part1, self.part2, self.part3], start=1): lines.append(f"Part {i}") lines.append(mml if mml else "MML@r1;") lines.append("") if self.warnings: lines.append("--- Warnings ---") for w in self.warnings: lines.append(f" [WARN] {w}") return "\n".join(lines).strip() @dataclass class WarningMessage: """경고 메시지 (필요시 구조화된 경고로 사용).""" code: str message: str context: Optional[str] = None # --------------------------------------------------------------------------- # 멀티 엔진 비교용 데이터 구조 # --------------------------------------------------------------------------- @dataclass class EngineRunResult: """ 단일 OMR 엔진 실행 결과. 자동 점수만으로 최종 판정을 내리지 않는다. heuristic_summary는 참고 지표일 뿐, 최종 품질 판단은 사용자가 직접 확인해야 한다. """ engine_name: str success: bool stage: str = "" # 실패 단계: "init" | "preprocess" | "omr" | "parse" | "convert" warnings: list[str] = field(default_factory=list) error_message: str = "" # 출력 파일 경로 (저장된 경우) output_xml_path: str = "" # 엔진 출력 MusicXML 경로 output_mml_path: str = "" # 저장된 MML 텍스트 파일 경로 output_notes_json_path: str = "" # 저장된 notes.json 경로 output_notes_txt_path: str = "" # 저장된 notes.txt 경로 output_debug_path: str = "" # 저장된 debug.json 경로 # 정량 지표 (참고용) note_count: int = 0 chord_count: int = 0 # 동시 발음 노트 그룹 수 part_note_counts: list[int] = field(default_factory=list) warning_count: int = 0 # 상세 데이터 debug_info: dict = field(default_factory=dict) heuristic_summary: dict = field(default_factory=dict) mml_parts: list[str] = field(default_factory=list) notes_dump: list[dict] = field(default_factory=list) # 사람이 검토하기 좋은 노트 목록 @dataclass class ComparisonReport: """ 여러 OMR 엔진 비교 실행 결과 집계. 중요: user_review_priority=True 는 항상 True여야 한다. 최종 엔진 선택은 사용자가 직접 결과를 들어보고 판단한다. suggested_engine은 단순 참고용이며 자동 결정이 아니다. """ input_file: str timestamp: str = "" runs: list[EngineRunResult] = field(default_factory=list) user_review_priority: bool = True # 항상 True — 최종 판정은 사용자 직접 확인 comparison_summary: str = "" notes_for_manual_review: list[str] = field(default_factory=list) suggested_engine: str = "" # 자동 참고 추천 (최종 판정 아님)