전체 화면의 악보 영역을 넓히고 렌더링된 악보 높이에 따라 배율을 자동으로 맞춘다. 모바일 가로 화면에서는 최소 높이를 줄여 다른 연습 영역을 지나치게 가리지 않도록 했다. 검사: 프런트 타입 검사, 전체 70개 테스트, Docker 운영 빌드, 데스크톱·태블릿·모바일 가로 화면 확인 운영 반영: 완료 백업: /home/syskiller/piano/backups/bach-to-basics/20261003-1730-sheet-fit
This commit is contained in:
+3
-1
@@ -28,13 +28,15 @@
|
||||
- 단계별 공개 샘플은 모두 오른손과 왼손을 포함한 피아노 독주다. 손별 연습은 연습 화면에서 선택한다.
|
||||
- 곡 목록 조회는 일시적인 연결 실패 때 한 번 재시도한다. 정상 응답을 받으면 남아 있던 연결 오류를 지운다.
|
||||
- 악보 커서는 변환된 악보의 시간 위치와 원본 템포 지도를 환산한 AlphaTab tick을 함께 사용한다. 음소거된 내부 플레이어가 위치 이벤트를 보내지 않는 경우에도 악보 렌더가 끝나면 커서를 직접 갱신한다. 확인한 AlphaTab 1.8.3을 고정해 커서 경로 변경으로 인한 재발을 막는다.
|
||||
- 전체 화면의 악보 영역은 높이의 38%를 사용한다. 최소 높이는 화면에 따라 150px에서 220px 사이로 조정한다. 악보 렌더링이 끝난 뒤 실제 높이를 측정하며 양손 보표가 영역을 넘으면 배율을 자동으로 낮춰 위아래가 잘리지 않게 한다.
|
||||
- 운영 브라우저에서 미뉴에트와 엘리제를 위하여 입문 악보의 오선과 음표 표시를 확인했다.
|
||||
- 운영 백엔드에서 MIDI와 MusicXML과 MXL 및 PDF 검증기를 모두 통과했다.
|
||||
- 프런트 타입 검사와 67개 테스트와 운영 빌드를 통과했다.
|
||||
- 프런트 타입 검사와 70개 테스트와 운영 빌드를 통과했다.
|
||||
|
||||
## 백업
|
||||
|
||||
- 작업 전 DB와 수정 대상 소스: `/home/syskiller/piano/backups/bach-to-basics/20261003-1545-sheet-upload`
|
||||
- 악보 영역 자동 맞춤 적용 전 소스: `/home/syskiller/piano/backups/bach-to-basics/20261003-1730-sheet-fit`
|
||||
|
||||
## 남은 확인
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { fitHorizontalSheetScale } from "../components/SheetMusic/SheetMusicView";
|
||||
|
||||
describe("fitHorizontalSheetScale", () => {
|
||||
it("악보 높이가 영역 안에 들어오면 현재 배율을 유지한다", () => {
|
||||
expect(fitHorizontalSheetScale(0.62, 220, 240)).toBe(0.62);
|
||||
});
|
||||
|
||||
it("악보가 넘치면 아래 보표가 잘리지 않도록 배율을 낮춘다", () => {
|
||||
expect(fitHorizontalSheetScale(0.62, 300, 240)).toBeCloseTo(0.4795, 3);
|
||||
});
|
||||
|
||||
it("작은 화면에서도 최저 가독성 배율을 보장한다", () => {
|
||||
expect(fitHorizontalSheetScale(0.62, 600, 180)).toBe(0.42);
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,30 @@ function sanitizeMusicXml(xml: string): string {
|
||||
return xml.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/\bon\w+\s*=/gi, "data-removed=");
|
||||
}
|
||||
|
||||
const HORIZONTAL_SHEET_BASE_SCALE = 0.62;
|
||||
const HORIZONTAL_SHEET_MIN_SCALE = 0.42;
|
||||
|
||||
/**
|
||||
* 전체 화면의 악보는 곡마다 보표와 옥타브 표기의 높이가 달라진다.
|
||||
* 고정 배율만 사용하면 복잡한 양손 악보의 아래 보표가 영역 밖으로 잘릴 수 있다.
|
||||
* 실제 렌더링 높이가 가용 높이를 넘을 때만 배율을 낮추고 작은 화면에서도
|
||||
* 음표를 알아볼 수 있도록 최저 배율을 보장한다.
|
||||
*/
|
||||
export function fitHorizontalSheetScale(
|
||||
currentScale: number,
|
||||
renderedHeight: number,
|
||||
availableHeight: number,
|
||||
): number {
|
||||
if (renderedHeight <= 0 || availableHeight <= 0 || renderedHeight <= availableHeight) {
|
||||
return currentScale;
|
||||
}
|
||||
|
||||
const safetyMargin = 8;
|
||||
const usableHeight = Math.max(1, availableHeight - safetyMargin);
|
||||
const fitted = currentScale * (usableHeight / renderedHeight);
|
||||
return Math.max(HORIZONTAL_SHEET_MIN_SCALE, Math.min(currentScale, fitted));
|
||||
}
|
||||
|
||||
export function SheetMusicView() {
|
||||
const scrollRef = useRef<HTMLDivElement>(null); // outer overflow-auto container
|
||||
const containerRef = useRef<HTMLDivElement>(null); // inner AlphaTab host
|
||||
@@ -49,6 +73,7 @@ export function SheetMusicView() {
|
||||
// SheetMusicView mounts fresh per layout mode (not a singleton), so reading
|
||||
// the mode once at init is safe for this instance's lifetime.
|
||||
const horizontal = useAppStore.getState().settings.layoutMode === "all";
|
||||
let horizontalScale = HORIZONTAL_SHEET_BASE_SCALE;
|
||||
|
||||
const api = new AlphaTabApi(containerRef.current, {
|
||||
core: {
|
||||
@@ -70,7 +95,7 @@ export function SheetMusicView() {
|
||||
// Smaller scale in the horizontal "all" band so the full grand staff
|
||||
// (both clefs) fits the short band height without the bass clef being
|
||||
// clipped. Page mode keeps the larger, more readable 0.9.
|
||||
scale: horizontal ? 0.62 : 0.9,
|
||||
scale: horizontal ? horizontalScale : 0.9,
|
||||
// stretchForce 0 = notes keep their natural rhythmic proportions (like
|
||||
// a printed score). A very small value (0.1) avoids a ragged right
|
||||
// edge on the last system without visibly compressing the spacing.
|
||||
@@ -97,6 +122,34 @@ export function SheetMusicView() {
|
||||
api.masterVolume = 0;
|
||||
setApiReady(true);
|
||||
|
||||
// 곡마다 보표와 8va·8vb 표기의 세로 높이가 달라진다. 렌더링이 끝난
|
||||
// 뒤 실제 높이를 재서 악보 띠 안에 양손 보표가 모두 들어오도록 배율을
|
||||
// 한 번 더 맞춘다. 계산 결과가 같으면 다시 렌더링하지 않아 반복을 막는다.
|
||||
try {
|
||||
api.postRenderFinished.on(() => {
|
||||
if (!horizontal || disposed) return;
|
||||
requestAnimationFrame(() => {
|
||||
const scrollEl = scrollRef.current;
|
||||
const scoreEl = containerRef.current;
|
||||
if (!scrollEl || !scoreEl || disposed) return;
|
||||
|
||||
const nextScale = fitHorizontalSheetScale(
|
||||
horizontalScale,
|
||||
scoreEl.scrollHeight,
|
||||
scrollEl.clientHeight - 16,
|
||||
);
|
||||
if (horizontalScale - nextScale < 0.01) return;
|
||||
|
||||
horizontalScale = nextScale;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(api.settings as any).display.scale = horizontalScale;
|
||||
api.render();
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
/* 이전 AlphaTab 버전에서는 렌더 완료 이벤트가 없을 수 있다. */
|
||||
}
|
||||
|
||||
// AlphaTab의 파일 해석과 렌더링 오류는 비동기로 전달된다. 빈 화면만
|
||||
// 남기지 않고 사용자가 원인을 알 수 있도록 화면에 오류를 표시한다.
|
||||
try {
|
||||
|
||||
@@ -356,7 +356,7 @@ export function PracticeView() {
|
||||
<div
|
||||
key="sheet-band"
|
||||
className="flex flex-col min-h-0 overflow-hidden"
|
||||
style={{ flex: "0 0 32%", minHeight: 130 }}
|
||||
style={{ flex: "0 0 38%", minHeight: "clamp(150px, 26vh, 220px)" }}
|
||||
>
|
||||
<SheetMusicView />
|
||||
</div>
|
||||
|
||||
@@ -21,3 +21,8 @@
|
||||
- 운영 백엔드에서 MIDI와 MusicXML과 MXL 및 PDF 검증을 통과했다.
|
||||
- 운영 브라우저에서 난이도별 묶음과 미뉴에트 및 엘리제를 위하여 악보 표시를 확인했다.
|
||||
- 작업 전 백업은 /home/syskiller/piano/backups/bach-to-basics/20261003-1545-sheet-upload에 저장했다.
|
||||
- 전체 화면에서 악보 영역을 높이의 38%로 넓혔다. 최소 높이는 모바일 가로 화면에서 다른 연습 영역을 가리지 않도록 화면 높이에 따라 150px에서 220px 사이로 조정한다.
|
||||
- 곡마다 다른 양손 보표와 8va 및 8vb 높이를 렌더링 뒤 측정한다. 영역을 넘으면 악보 배율을 자동으로 낮춰 아래 보표가 잘리지 않게 했다.
|
||||
- 악보 자동 맞춤 계산의 정상 범위와 넘침 보정과 최저 배율을 검사하는 회귀 테스트 3개를 추가했다.
|
||||
- 프런트 타입 검사와 전체 70개 테스트와 운영 빌드를 통과했다.
|
||||
- 악보 영역 수정 전 백업은 /home/syskiller/piano/backups/bach-to-basics/20261003-1730-sheet-fit에 저장했다.
|
||||
|
||||
Reference in New Issue
Block a user