Score_To_MML / core /models.py
Coconuttttt's picture
Initial deployment: Score to MML converter
daa0bdd
"""
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<BPM>]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 = "" # ์ž๋™ ์ฐธ๊ณ  ์ถ”์ฒœ (์ตœ์ข… ํŒ์ • ์•„๋‹˜)