양손 악보를 피아노 독주로 통일하고 난이도 정렬과 곡 목록 통신 복구를 보완했다. 재생 위치에 맞춰 악보 커서와 화면이 이동하도록 고쳤으며 높은음과 낮은음에는 8va와 8vb 표기를 적용했다. 상단 브랜드와 저장소 링크를 ITOSM 피아노 학습실 기준으로 바꿨다. 검사: 프런트엔드 테스트 67건 통과, 백엔드 테스트 4건 통과, 타입 검사와 빌드 통과, Docker 재빌드와 운영 화면 확인 운영 반영: 완료 백업: /home/syskiller/piano/backups/bach-to-basics/20261003-1635-classification-scroll
This commit is contained in:
@@ -177,6 +177,7 @@ def _apply_piano_split(score): # noqa: C901
|
||||
import copy
|
||||
from music21 import clef as m21clef, instrument as m21inst, layout as m21layout
|
||||
from music21 import note as m21note, chord as m21chord
|
||||
from music21 import spanner as m21spanner
|
||||
from music21 import stream as m21s
|
||||
|
||||
parts = list(score.parts)
|
||||
@@ -313,6 +314,58 @@ def _apply_piano_split(score): # noqa: C901
|
||||
first_m.remove(c)
|
||||
first_m.insert(0, clef_obj)
|
||||
|
||||
def _add_readable_ottavas(staff, *, upper: bool) -> None:
|
||||
"""긴 덧줄 대신 연속된 고음과 저음 구간에 8va 또는 8vb를 붙인다.
|
||||
|
||||
한 음에만 기호를 붙이면 악보가 더 복잡해진다. 한 마디 안에 기준을
|
||||
넘는 음이 두 개 이상 있거나 극단적인 음이 하나라도 있을 때만 그
|
||||
마디를 대상으로 삼는다. 이어진 대상 마디는 하나의 옥타브 선으로
|
||||
묶는다. non-transposing으로 내보내 원래 MIDI 음높이와 재생 결과는
|
||||
바뀌지 않고 MusicXML 표기만 읽기 쉬운 위치로 이동한다.
|
||||
"""
|
||||
|
||||
measures = list(staff.getElementsByClass("Measure"))
|
||||
marked: list[int] = []
|
||||
for index, measure in enumerate(measures):
|
||||
pitched = list(measure.recurse().notes)
|
||||
qualifying = 0
|
||||
extreme = False
|
||||
for element in pitched:
|
||||
pitches = list(element.pitches) if hasattr(element, "pitches") else [element.pitch]
|
||||
midi_values = [pitch.midi for pitch in pitches]
|
||||
if upper:
|
||||
qualifying += sum(value >= 84 for value in midi_values) # C6 이상
|
||||
extreme = extreme or any(value >= 96 for value in midi_values) # C7 이상
|
||||
else:
|
||||
qualifying += sum(value <= 36 for value in midi_values) # C2 이하
|
||||
extreme = extreme or any(value <= 24 for value in midi_values) # C1 이하
|
||||
if qualifying >= 2 or extreme:
|
||||
marked.append(index)
|
||||
|
||||
groups: list[list[int]] = []
|
||||
for index in marked:
|
||||
if groups and index == groups[-1][-1] + 1:
|
||||
groups[-1].append(index)
|
||||
else:
|
||||
groups.append([index])
|
||||
|
||||
for group in groups:
|
||||
elements = []
|
||||
for index in group:
|
||||
elements.extend(measures[index].recurse().notes)
|
||||
if not elements:
|
||||
continue
|
||||
ottava = m21spanner.Ottava(
|
||||
*elements,
|
||||
type="8va" if upper else "8vb",
|
||||
transposing=False,
|
||||
placement="above" if upper else "below",
|
||||
)
|
||||
staff.insert(0, ottava)
|
||||
|
||||
_add_readable_ottavas(treble_staff, upper=True)
|
||||
_add_readable_ottavas(bass_staff, upper=False)
|
||||
|
||||
# ── Assemble score with brace StaffGroup (= grand staff) ─────────────────
|
||||
new_score = m21s.Score()
|
||||
if score.metadata:
|
||||
|
||||
Reference in New Issue
Block a user