Đây là kho phát hành, không phải kho mã nguồn. Kho này chỉ chứa bản dựng và tài liệu người dùng của Keyflow; mã nguồn nằm ở một kho riêng. Mọi tệp ở đây do quy trình phát hành của kho nguồn sinh ra tại đúng tag
v2.2.0— xem Tải bản phát hành bên dưới, hoặc Releases cho bản mới nhất, và Issues để báo lỗi.Đừng sửa tệp trong kho này bằng tay: mỗi lần phát hành, nhánh này được dựng lại từ kho nguồn nên mọi thay đổi viết tay sẽ bị ghi đè.
English version: README.en.md · Bản dưới đây là bản gốc tiếng Việt. Hai tệp là cùng một tài liệu và cùng ảnh (do CI render); cả hai được sinh từ một bản gốc nên không bản nào lệch khỏi bản kia.
Nhật ký thay đổi: CHANGELOG.md (bản tiếng Anh: CHANGELOG.en.md) — mọi thay đổi người dùng nhìn thấy, ghi theo từng phiên bản phát hành.
Tải bản dựng: kho phát hành công khai — ZIP portable và bộ cài
.exe. Kho bạn đang đọc là kho phát hành: gói cài và trang sản phẩm nằm ngay ở đây, còn mã nguồn nằm ở một kho riêng.
Keyflow là ứng dụng desktop Windows (C# · WPF · .NET 10) để chơi đàn, luyện tập và làm video piano theo MIDI với chất lượng trình diễn hoà nhạc. Giao diện có hai ngôn ngữ — English và Tiếng Việt — đổi ngay trong ứng dụng, không cần khởi động lại (xem Đa ngôn ngữ). Sân khấu mặc định là một hội trường tối: nốt rơi theo thời gian, bàn phím 88 phím đổ bóng bằng shader mô phỏng mô hình Unreal (GGX + softbox + ACES), tia lửa nóng sáng nguội dần theo bức xạ nhiệt, sóng cộng hưởng âm học, lửa tại điểm phím gõ và các lớp không khí (bụi acoustic, cánh hoa, đèn sân khấu) có thể bật riêng. Sân khấu ấy do một engine GPU Direct3D 11 vẽ trên luồng riêng — HDR 16-bit, bloom nhiều tầng, tới 240 FPS và không bao giờ làm chậm đầu vào MIDI; bộ dựng hình WPF chỉ còn là đường dự phòng khi Direct3D không khởi động được (xem Sân khấu & hiệu ứng hình ảnh).
Hiệu ứng đã duyệt (mã nguồn hiện tại): vào Tổng quan → Hiệu ứng hạt nâng cấp, hoặc chọn preset Prism Flow - Wisps / Prism Flow - Flames để dùng vật liệu held/release chung cho GPU Live Stage và AR cùng cách chia cap AR 192. Preset cũ không tự đổi; renderer phần mềm giữ classic.
Ảnh dưới đây do chính ứng dụng render trong CI (--snapshot) với --lang=vi, nằm trong docs/previews/vi/ và được cập nhật tự động — không phải ảnh dàn dựng (bản tiếng Anh có bộ ảnh riêng ở docs/previews/en/):
Sân khấu live do engine GPU Direct3D 11 vẽ (ảnh CI dựng bằng WARP, bộ dựng hình phần mềm của Windows): nốt rơi, đường chạm phát sáng, tia lửa, bàn phím 3D có ánh sáng và transport dưới cùng.
Cùng sân khấu với một ảnh nền do người dùng chọn: ảnh phủ kín khung (crop giữa, không méo tỉ lệ) và bị
làm tối ở mức mặc định 30/100 nên nốt vẫn đọc được. Ảnh nền ở đây là docs/samples/stage-backdrop.png — repo tự sinh bằng
tools/make_stage_background.py để ảnh minh hoạ vừa tất định vừa không dính bản quyền của người khác.
| Bàn phím & phím tắt | Hộp thoại Play |
|---|---|
![]() |
![]() |
| Menu khởi động (thẻ giao diện có cả chọn ngôn ngữ) | Dock thiết kế (Style) | Dock thiết kế (Theme) |
|---|---|---|
![]() |
![]() |
![]() |
Ảnh trong README do chính ứng dụng render trong CI (
--snapshot). Ảnh được render lại mỗi khi giao diện đổi, rồi chép sang kho này ở mỗi lần phát hành.
Mỗi mốc phát hành (v2.2.0 và các bản sau) là một mục trong Releases, kèm
SHA256SUMS.txt của mọi tệp đính kèm. Bản mới nhất: https://github.com/lxmtuu/KeyFlow/releases/latest.
| Tệp | Là gì | Máy đích cần gì |
|---|---|---|
Keyflow-2.2.0-win-x64.zip |
Bản portable self-contained: giải nén là chạy PianoPath.exe |
Không cần cài gì thêm |
Keyflow-Setup-2.2.0.exe |
Bộ cài cho Windows x64: shortcut Start Menu/Desktop, mục gỡ cài đặt, wizard Anh/Việt | Không cần cài gì thêm |
Keyflow-2.2.0-win-x64-fd.zip |
Bản framework-dependent, nhẹ hơn nhiều | .NET 10 Desktop Runtime (x64) |
Keyflow-2.2.0-win-arm64.zip |
Windows on ARM, self-contained (CI publish; chưa máy ARM nào chạy thử) | Không cần cài gì thêm |
Cài bằng bộ cài thì xong; dùng bản ZIP thì giải nén cả thư mục rồi chạy PianoPath.exe — đừng
tách .exe khỏi LICENSE.txt:
Keyflow-2.2.0-win-x64\
├── PianoPath.exe ← file chạy duy nhất
└── LICENSE.txt ← giấy phép MIT, phải đi kèm bản sao
Salamander Grand Piano V3 không nằm trong gói. Khi muốn có tiếng đàn, mở SETTINGS → Audio và chủ động tải bộ mẫu khoảng 748 MB; các mẫu được xác minh trước khi sử dụng, có thể huỷ/tiếp tục, và thông tin ghi công CC BY 3.0 được lưu cạnh bộ mẫu trong thư mục cài piano của người dùng.
- Kiểm tệp tải về: mỗi release kèm
SHA256SUMS.txt; đối chiếu bằngGet-FileHash .\Keyflow-2.2.0-win-x64.zip -Algorithm SHA256. Các gói chưa được ký mã số; bảng hash vẫn là cách nhanh để chắc tệp nhận được đúng là tệp quy trình build ra. - SmartScreen: các gói chưa được ký Authenticode, nên Windows có thể hiện bảng "Windows protected your PC" khi cài đặt hay chạy lần đầu; bấm More info → Run anyway.
- Giữ giấy phép:
LICENSE.txt(MIT) phải đi cùng bản sao. Thông báo CC BY 3.0 của Salamander nằm trong thư mục dữ liệu piano sau khi người dùng tải bộ mẫu. - Nhật ký thay đổi: CHANGELOG.md (bản tiếng Anh: CHANGELOG.en.md) — mỗi bản một mục, ghi những gì người dùng nhìn thấy.
- Bộ slide thuyết trình sản phẩm: docs/presentation — 20 slide 16:9, bản PDF để chiếu và bản DOCX để sửa lại.
- Bộ kiểm chứng:
PianoPath.exe --verify --verify-log=%TEMP%\keyflow-verify.logchạy ngay trên máy bạn vừa tải về và in từng mục PASS/FAIL (mục cần phần cứng không có sẽ ghi SKIP, không phải lỗi).
- Tải bản phát hành
- Yêu cầu hệ thống
- Thiết lập lần đầu
- Bắt đầu sử dụng
- Bàn phím & thao tác nhanh
- Chức năng
- Đa ngôn ngữ
- Bản đồ giao diện
- Giới hạn hiện tại
- Kiểm thử
- Giấy phép
| Thành phần | Yêu cầu | Ghi chú |
|---|---|---|
| Hệ điều hành | Windows 10 (khuyến nghị 22H2) hoặc Windows 11, 64-bit | Ứng dụng dùng WPF và WinMM nên chỉ chạy trên Windows. |
| Để chạy bản đã đóng gói | Không cần cài gì thêm với bản self-contained; bản framework-dependent cần .NET 10 Desktop Runtime (x64) | Xem Tải bản phát hành. |
| Để biên dịch từ mã nguồn | .NET 10 SDK (10.0.100 trở lên) và Git | Chỉ cần .NET SDK và Git; ứng dụng tải mẫu Salamander khi người dùng yêu cầu âm thanh. |
| IDE (tuỳ chọn) | Visual Studio 2026 với workload .NET desktop development, hoặc VS Code + extension C# Dev Kit | .NET 10 cần Visual Studio 2026 (18.0) trở lên; VS 2022 chỉ mở được dự án khi đã cài .NET 10 SDK và không hỗ trợ đầy đủ. |
| Ổ đĩa | ~1,5 GB cho SDK + ~500 MB cho mã nguồn và bản build | Cần thêm khoảng 748 MB để cài Salamander Grand nếu muốn dùng tiếng piano. |
| Âm thanh | Card âm thanh bất kỳ được Windows nhận (WASAPI; tuỳ chọn ASIO cho sound card thu âm, waveOut làm đường dự phòng) |
|
| Đồ hoạ | GPU hỗ trợ Direct3D 11 (feature level 11_0 trở lên, shader model 5.0) | Sân khấu mặc định là engine GPU: Keyflow thử card đồ hoạ trước rồi mới tới WARP (bộ dựng hình phần mềm của Windows — cùng shader, chỉ chậm hơn card thật); shader HLSL được biên dịch lúc khởi động bằng d3dcompiler_47.dll có sẵn trong Windows. Direct3D không khởi động được thì dùng bộ dựng hình WPF và nói lý do ở Ứng dụng → GRAPHICS ENGINE. |
| MIDI (tuỳ chọn) | Đàn/thiết bị MIDI USB được Windows nhận diện | Không có đàn vẫn chơi được bằng bàn phím máy tính hoặc piano ảo. |
| Python (tuỳ chọn) | Python 3.9+ | Chỉ dùng cho tools/check_sources.py (kiểm tra tĩnh, không cần .NET SDK). |
- Âm thanh: Keyflow phát qua thiết bị đầu ra mặc định của Windows bằng WASAPI (tự lùi về
waveOutkhi cần); mục OUTPUT & LATENCY của trang Audio chọn WASAPI shared/exclusive, một driver ASIO hoặcwaveOut, kèm độ trễ mong muốn. Đổi loa/tai nghe trong Settings → System → Sound của Windows trước khi mở ứng dụng. - Piano: Salamander Grand Piano V3 là cây đàn duy nhất. Bản cài không tải mẫu ngầm; trong SETTINGS → Audio, bấm nút Tải đàn piano · 748 MB để tải một lần. Từng tệp FLAC được xác minh, tiến độ có thể huỷ/tiếp tục và đàn tự nạp sau khi hoàn tất. Dùng HALL REVERB để bật/tắt tiếng vang, chọn phòng (hall, concert hall, soundboard hoặc impulse response WAV/FLAC) và bật/tắt string resonance.
- Đàn MIDI: cắm đàn trước khi mở ứng dụng thì Keyflow tự kết nối ngõ vào đầu tiên. Cắm sau thì mở SETTINGS → MIDI rồi bấm nút icon làm mới thiết bị. Nhãn thiết bị phía trên bàn phím chuyển sang
MIDI IN · C4khi nhận Note On. Cùng trang có ngõ ra MIDI (nếu muốn phát qua synth ngoài), metronome và danh sách track. - Đồ hoạ: sân khấu chạy bằng GPU ngay từ lần mở đầu, không phải bật gì. Muốn đổi tốc độ khung hình (60 / 120 / 144 / 240 FPS hoặc không giới hạn), tắt VSync của cửa sổ sân khấu GPU, mở cửa sổ đó cho màn hình thứ hai hoặc xem engine nào đang vẽ thì vào SETTINGS → General → GRAPHICS ENGINE. Đây là thiết lập của máy: áp preset không đổi chúng.
- Giao diện: chọn preset ở SETTINGS → Style (mặc định Concert Gold), chọn giao diện hoà nhạc ở SETTINGS → Theme (Concert Grand / Concert Noir / Velvet Gold; Concert Grand là mặc định riêng của bộ chọn theme, còn preset Concert Gold ghép với Velvet Gold), rồi tinh chỉnh ở các trang Notes / Particles / Keyboard / Background / Camera & FX. Mọi thay đổi áp dụng ngay và tự lưu.
- Vị trí lưu cấu hình:
%LOCALAPPDATA%\Keyflow\visual-settings.json(cài đặt hiện tại),%LOCALAPPDATA%\Keyflow\library.json(danh sách bài mở gần đây kèm giá trị đã dùng cho từng bài),%LOCALAPPDATA%\Keyflow\history\practice.jsonl(lịch sử luyện tập) và%LOCALAPPDATA%\Keyflow\presets\*.json(preset người dùng) và%LOCALAPPDATA%\Keyflow\themes\*.json(theme tự tạo, xem mục Theme tự tạo bên dưới); đổi chỗ bằng--settings-dir=<thư mục>. Xoá filevisual-settings.jsonhoặc bấm RESET TO DEFAULT trong dock để về mặc định; sao chép thư mụcpresetsđể mang preset sang máy khác (hoặc dùng IMPORT/EXPORT). Id giao diện cũ (sakura,noir,velvet) được tự động chuyển sang id mới khi nạp file cũ. - Ghi hình: trang Recording chọn Format — AVI video, PNG sequence (32-bit alpha) (chuỗi khung PNG có kênh trong suốt để ghép vào Premiere/Resolve/OBS) hoặc MP4 (H.264 + AAC) (một tệp đã có luôn tiếng, không phải ghép gì thêm), Resolution và Frame rate; bấm REC ở góc sân khấu, chọn nơi lưu rồi bấm lại để dừng. Với AVI, cài một codec MJPEG (ví dụ gói K-Lite) nếu muốn tệp nhỏ hơn, không bắt buộc.
- Ứng dụng khởi động ở menu hoà nhạc (Perform & Play / Quick Adjustments / Piano & Audio Setup / MIDI Devices / Keyboard & Shortcuts / About / Exit). Các lối tắt Piano & Audio Setup và MIDI Devices mở đúng trang cài đặt; Quick Adjustments mở điều khiển sân khấu thường dùng, còn Stage Design Studio mở dock đầy đủ. Nút HOME trên header quay lại menu bất cứ lúc nào.
- Live Play: nhấn một phím trên piano, bàn phím máy tính hoặc đàn MIDI để chỉ hiện nốt vừa chơi. Khi chưa cài Salamander, phần hình và MIDI vẫn hoạt động nhưng chưa có tiếng; mở Piano & Audio Setup để tải đàn. Sau khi cài, các phím máy tính, phím ảo và MIDI cùng phát qua Salamander. Nút HALL bật/tắt tiếng vang.
- Bấm Play trên header (hoặc Perform & Play ở menu chính) để mở hộp thoại phiên. Chọn MIDI File để mở bài
.mid/.midihoặc bản nhạc MusicXML (.musicxml,.xml,.mxl— tệp nén đọc quaMETA-INF/container.xml), hoặc chọn Live Play để biểu diễn ngay. Với MusicXML, điểm chia tay đọc từ khuông nhạc (khuông 1 = tay phải, khuông 2 = tay trái, hoặc mỗi tay mộtpart) nên không phải đoán, và giá trị đó được nhớ cho bài đó tronglibrary.json. Khi chưa có bài, nút phiên cỡ nhỏ ghi Choose a MIDI file; khi đã nạp bài, nút ghi Start playback (nút MIDI bên cạnh dùng để thay bài); khi bài đang phát, nút ghi Return to playback. Nút vàng lớn Live Play vẫn bắt đầu chơi trực tiếp như trước. Thẻ Left Hand và Right Hand mở bộ chọn preset riêng cho từng tay, kèm lựa chọn màu tuỳ chỉnh. QUICK ADJUST mở nhanh kiểu nốt, chế độ màu, tốc độ, độ sáng, màu từng tay và các lớp sân khấu; các thay đổi đồng bộ với dock Design. Advanced settings… mở dock đầy đủ. Danh sách RECENT giữ các bài đã mở cùng số nốt, số track, tempo và preset lần trước; bấm một dòng để mở lại đúng thiết lập đã lưu, dấu × để quên. Phần LIBRARY có thể lập chỉ mục một thư mục, tìm theo tên/nhãn và mở bài ngay trong hộp thoại. - Khi bài chạy, nốt đi xuống và chạm đường sáng ngay trên phím đàn. Nhấn phím đàn để chơi cùng và nhận phản hồi đúng/sai.
- Nếu đã cắm đàn MIDI khi mở ứng dụng, Keyflow tự kết nối ngõ vào đầu tiên tìm thấy. Khi cắm sau, mở SETTINGS → MIDI rồi bấm nút icon làm mới thiết bị; ứng dụng tự chọn và kết nối đàn mới. Nhãn xanh phía trên bàn phím đổi thành
MIDI IN · C4khi nhận Note On. Trong dock cài đặt (nút bánh răng trên header hoặc phímEsc) chọn thiết bị vào/ra khác, chế độ tập, tempo, track, metronome và điểm đầu/cuối vòng lặp.Escquay lại theo đúng thứ tự bề mặt đang mở (Quick Adjust, Play, cài đặt, menu); từ sân khấu nó mở dock.
Keyflow dùng duy nhất Salamander Grand Piano V3: grand Yamaha C5 thu ở 48 kHz/24-bit bởi Alexander Holm, giấy phép CC BY 3.0. 641 tệp FLAC (tổng 748 397 030 byte, xấp xỉ 748 MB) được tải theo yêu cầu vào %LocalAppData%\Keyflow\Instruments\SalamanderGrandPiano; tải ghim vào một commit, xác minh Git blob SHA-1 từng tệp, bỏ qua tệp hợp lệ đã có và tiếp tục an toàn sau khi huỷ. Mapping SFZ được tạo riêng cho bộ mẫu này; không có nhập SF2 hoặc chọn nhạc cụ khác. Engine phát stereo 44.1 kHz, hỗ trợ Note On/Off, ba pedal MIDI, reverb và cộng hưởng dây. MIDI output là đường âm thanh bổ sung nếu chọn thiết bị MIDI ngoài.
Thẻ F1 trong ứng dụng liệt kê đúng bảng này (ảnh ở đầu README), không cần mở tài liệu:
| Nhóm | Phím / thao tác | Tác dụng |
|---|---|---|
| Chơi nhạc | A W S E D F T G Y H U J K |
Một quãng tám từ nốt giữa (C4); nhấn giữ để duy trì nốt, thả ra để gửi Note Off. |
| Chơi nhạc | Space |
Phát/tạm dừng bài MIDI đang mở. |
| Chơi nhạc | Click chuột lên phím đàn | Chơi phím ảo với đầy đủ phản hồi màu, tia lửa, lửa. |
| Chơi nhạc | Đàn MIDI | Velocity, Note On/Off và ba pedal (CC 64/66/67) đi vào cùng luồng phản hồi. |
| Điều hướng | F11 |
Bật/tắt toàn màn hình. |
| Điều hướng | F1 |
Mở/đóng thẻ phím tắt. |
| Điều hướng | Tab |
Mở hộp thoại Play từ sân khấu; trong menu và hộp thoại, Tab vẫn chuyển tiêu điểm như bình thường. |
| Điều hướng | Esc |
Quay lại theo thứ tự giao diện đang mở (Quick Adjust → Play → cài đặt → menu → sân khấu); ở màn hình sân khấu mở dock, ô tìm kiếm được xoá trước và thẻ F1 đóng trước. |
| Điều hướng | Ctrl+Z / Ctrl+Shift+Z |
Hoàn tác / làm lại thay đổi trong bàn thiết kế. Dock giữ 32 trạng thái gần nhất; một lần kéo slider (hoặc một đợt chỉnh liên tục) tính là một bước, và Ctrl+Z không giành quyền hoàn tác của ô đang gõ. |
| Điều hướng | Để chuột yên ~2,8 giây | Toàn bộ giao diện (thanh trên/dưới, Menu, REC và cả dock nếu đang mở) tự ẩn, chỉ còn đàn, nền và nốt đang chạy. Di chuyển chuột để hiện lại đúng những gì vừa ẩn. Giao diện không tự ẩn khi đang kéo slider, mở danh sách chọn, mở bảng chọn màu, đang mở thẻ phím tắt, hoặc khi gõ phím trong bảng cài đặt. |
| Phiên làm việc | A, B, × (thanh thời gian) | Đặt/xoá vòng lặp A–B tại playhead. |
| Phiên làm việc | Kéo thanh thời gian | Tua bài; nốt bị bỏ qua không tính là miss. |
| Phiên làm việc | Trang Practice | Follow along, Wait for my note, Right hand only, Left hand only; tempo 50–150%. |
| Phiên làm việc | REC | Ghi sân khấu ra AVI, ra thư mục khung PNG 32-bit (có alpha), hoặc ra MP4 có luôn âm thanh trong tệp, theo định dạng/độ phân giải/fps đã chọn ở trang Recording. |
| Nhóm | Chi tiết |
|---|---|
| Nốt | 6 vật liệu (Solid / Neon outline / Glass / Fire có vân cháy animation / Gem mặt cắt / Crystal Outline trong mờ góc cạnh), 5 chế độ màu (gradient theo cao độ với 6 palette hoặc màu đầu–cuối tuỳ chỉnh, theo tay với điểm chia đổi được, theo track MIDI với bảng 8 màu, cầu vồng theo cao độ, cầu vồng theo thời gian), độ rộng, bo góc, độ dài tối thiểu, khe hở, đổ bóng 3D, tên nốt in trên thanh, tint, bloom, độ sáng/dày viền, glow cạnh trước, khúc xạ, shimmer (dải bóng chạy dọc nốt), landing glow (quầng sáng đón nốt — sân khấu GPU), tốc độ rơi và hướng di chuyển (Down: rơi xuống chạm phím rồi chìm dưới đường chạm; Up: sinh ra tại phím theo tiếng nốt và bốc lên khỏi đỉnh sân khấu — tia lửa, lửa, vòng sóng vẫn bung tại phím), falling FX (7 kiểu vệt + pulse + ghost), hold FX (bar/breath/rung/arc điện), release FX (6 kiểu) và smart modulators (velocity/octave/zone/pedal/tempo/audio). |
| Hạt & lửa | Tia lửa incandescent có physics đầy đủ (gravity, drag, vector field…), wisps plasma bốc lên từ phím đang giữ (mật độ, tốc độ, chiều cao, độ rộng, nhiễu loạn, glow), lửa theo nốt (cường độ, chiều cao, màu ấm hoặc theo nốt, cháy tiếp khi giữ phím rồi tắt dần), vòng sóng va chạm, sóng impact (Ring/Shockwave/Ripple + chớp Flash/Lightning/Plasma theo lực nhấn), 5 kiểu nổ hạt, 5 kiểu morph (xem docs/EFFECTS-REDESIGN.md); riêng GPU, luồng hạt khi giữ phím dùng cùng họ với burst đã chọn (than hồng, nước bắn, pháo hoa, confetti hoặc bụi), còn tốc độ vật lý của hạt tách khỏi đồng hồ nhạc. |
| Bàn phím | 88 phím vẽ bằng shader ray-trace: BRDF GGX/Smith/Schlick, softbox có penumbra thật, contact occlusion trong khe phím, IBL môi trường, đèn màu hắt từ phím đang kêu, tonemap ACES filmic (Off/Fast/Balanced/Cinematic + key light, bóng, occlusion, gloss, rim, emission, exposure, tilt camera). Kiểu Classic / Studio 3D / Glass, chiều cao, độ dài phím đen, nhãn phím, bóng nắp đàn, dải nỉ đỏ, độ lún khi nhấn. Phím của nốt MIDI đang phát cũng sáng, không chỉ phím người chơi nhấn. Trên sân khấu GPU các thanh trượt đổ bóng ở trên đi thẳng vào shader HLSL và được áp lại ở mỗi khung hình; bộ chọn chất lượng Off/Fast/Balanced/Cinematic chỉ định hình bộ dựng hình phần mềm (đường dự phòng và bản ghi PNG trong suốt), nơi bàn phím bake một lần rồi cache, mỗi phím kêu chỉ vẽ lại một tile nhỏ nên giữ được 60 fps. |
| Nền | Màu đặc / ảnh (PNG, JPEG, BMP, GIF, TIFF + làm tối 0–100) / Green screen — thử ngay với ảnh mẫu docs/samples/stage-backdrop.png hoặc chạy --background-image=<file.png>; aura gradient, sao + sao băng, guide lanes, vignette, horizon glow, light beam; màu và đường halo; riêng GPU có 6 nền shader (Aurora / Nebula / Prism / Ember Haze / Ocean Flow / Retro Grid) có thể chỉnh cường độ/tốc độ/màu nền; 6 kiểu chuyển động hit-line (Pulse / Sweep / Twin Comets / Spectrum / Electric Arc / Ripple) có thanh chỉnh cường độ/tốc độ bên cạnh màu halo; cùng 4 lớp ambient độc lập (Energy/Nature/Light/Cosmic — lớp Light có thêm Spotlights). |
| Sân khấu GPU (mặc định) | Sân khấu chính được vẽ bởi một engine Direct3D 11 viết bằng Vortice.Windows — không còn lựa chọn "Renderer": engine phần mềm WPF chỉ còn là fallback tự động khi Direct3D không khởi động được và đường vẽ cho bản ghi PNG trong suốt (chạy một lần bằng --software): một vòng lặp game riêng trên luồng của nó (60 / 120 / 144 / 240 FPS hoặc không giới hạn) sở hữu ID3D11Device + ID3D11DeviceContext, nên một khung hình nặng chỉ làm giảm FPS chứ không bao giờ làm chậm MIDI. Nốt MIDI vào được gửi tới cả bộ tổng hợp âm thanh lẫn engine GPU ngay trên luồng callback MIDI (nốt và pedal giữ đúng thứ tự đến), nên cả tiếng lẫn hiệu ứng đều không phải chờ dispatcher của WPF. Shader HLSL (Gpu/StageShaders.hlsl): phím 3D có ánh sáng GGX, bóng đổ, AO, đèn màu hắt từ phím đang kêu; vật liệu nốt Solid/Neon/Glass/Fire/Gem/Crystal Outline (Gem có mặt cắt, Crystal Outline có viền góc cạnh trong mờ); tới 24 000 hạt (tia lửa, wisp, lửa, vòng sóng, chớp) mô phỏng trên CPU và vẽ bằng instancing; tất cả dựng ở HDR 16-bit rồi bloom nhiều tầng + tonemap ACES. Nút OPEN GPU STAGE WINDOW mở một cửa sổ swap chain IDXGISwapChain (flip model, VSync tuỳ chọn, F11 toàn màn hình) cho màn hình thứ hai / máy chiếu / OBS. Không khởi động được Direct3D thì app giữ engine phần mềm và nói lý do. Mọi họ hiệu ứng của engine phần mềm đều chạy trên GPU: vệt rơi (Glow, Sparkles, Speed lines, Blur, Ribbon, Rainbow, Stream), bóng ma, thanh giữ / thở / rung / tia điện nối phím đang giữ, sóng va chạm phẳng (Ring, Shockwave, Ripple), chớp Flash / Lightning / Plasma, biến hình nốt (Shatter, Melt, Absorb, Bounce, Star morph), hiệu ứng nhả phím, guide lanes, hạt acoustic, bốn lớp ambient, Pedal Glow và tên nốt trên phím (atlas chữ dựng một lần bằng WPF). Riêng sân khấu GPU có thêm: Note shimmer (dải bóng trôi dọc thân nốt, mang màu nốt), Landing glow (quầng sáng tụ lại trên phím mà nốt sắp chạm — vừa đẹp vừa là trợ thủ luyện tập), Halo light pulses (ở kiểu Pulse, ba đầu sáng màu halo chạy dọc đường chạm; Tempo sync/Pedal Glow/Audio reactive có thể làm chúng mạnh hơn), Shooting stars (sao băng lướt qua nền sao) và lớp ánh sáng Spotlights (ba luồng đèn mềm lảo đảo theo nhạc, được hỗ trợ cả ở WPF fallback và GPU); nền shader (Aurora, Nebula, Prism, Ember Haze, Ocean Flow, Retro Grid) cùng 6 kiểu hit-line (Pulse, Sweep, Twin Comets, Spectrum, Electric Arc, Ripple) với thanh chỉnh cường độ/tốc độ riêng, bên cạnh màu halo. Luồng hạt GPU khi giữ phím dùng chung họ hiệu ứng với burst (Embers, Water Splash, Fireworks, Confetti, Dust), còn tốc độ vật lý hạt độc lập với đồng hồ nhạc. Neon Violet, Galaxy Voyage, Electric Storm, Concert Gold, Moonlight Sonata, Ice Crystal, Sakura Nocturne, Aurora Rainbow và Sunset Drive chọn chuyển động hợp chủ đề; Classic Roll / Lo-Fi / Green Screen giữ sân khấu yên hoặc gọn khi phù hợp. Ghi hình từ GPU: khi sân khấu GPU đang hiện, REC để luồng dựng hình vẽ thêm một khung off-screen đúng kích thước và FPS của bản ghi (không phải bản phóng to của khung xem trước), các lớp WPF (khuông nhạc, camera, bộ đếm) được trộn lên trên; PNG trong suốt vẫn dùng engine phần mềm. Cài đặt nằm ở Ứng dụng → GRAPHICS ENGINE: GPU frame rate (60 / 120 / 144 / 240 FPS / Unlimited, mặc định 144), VSync in the GPU stage window (mặc định bật), nút mở cửa sổ và dòng trạng thái ghi adapter đang dùng cùng thời gian biên dịch shader; đó là thiết lập của máy nên áp preset giữ nguyên chúng, và tệp cũ ghi engine Software được chuyển sang GPU khi nạp. Lớp phủ WPF: khuông nhạc, điểm đánh dấu bàn tay, hình camera, watermark và bộ đếm phím/FPS vẫn là lớp vector vẽ phủ lên khung GPU. Ảnh: docs/previews/vi/stage-gpu.png. |
| Camera & FX | Parallax, zoom, khung hình, saturation, contrast, bloom. |
| Lớp phủ camera & theo dõi bàn tay | Trang Camera & FX có thẻ WEBCAM OVERLAY: công tắc Camera overlay vẽ một camera trực tiếp hoặc một tệp video (chạy lặp) lên sân khấu như một cửa sổ nhỏ — chọn camera trong danh sách máy đang có (REFRESH CAMERAS đọc lại), USE CAMERA / CHOOSE VIDEO… để đổi nguồn, cùng Corner (bốn góc), Size (15–60% chiều rộng sân khấu), Opacity, Mirror và Key tolerance (0 = tắt key, 100 = rộng nhất) để key màu xanh lá thuần — đúng màu của preset Green Screen — nên phông xanh biến mất còn người chơi ở lại. Dòng trạng thái dưới các hàng nói rõ đang mở camera nào, đang chạy tệp nào, hay vì sao không chạy được (máy không có camera, máy không có Media Foundation). Khung hình đọc bằng Media Foundation (Camera/) trên luồng riêng và lên sân khấu tối đa 60 lần mỗi giây nên không chặn vẽ. Với camera trực tiếp, app chọn chế độ gần 720p nhất ở tốc độ cao nhất camera có (tối đa 60 fps; ưu tiên MJPG), đọc theo đúng nhịp camera và hiện chế độ thật trong dòng trạng thái, ví dụ 720p · 60 fps · MJPG. Thẻ HAND TRACKING ngay bên dưới có công tắc Hand tracking và slider Skin sensitivity: ứng dụng đọc bàn tay từ chính khung camera — không mô hình, không tải thêm gì; pixel nào là da thì theo màu (YCbCr), nhóm ô da liền kề lớn nhất là bàn tay — rồi đếm số ngón đang giơ và đánh dấu phím mà bàn tay đang ở trên bằng một dải sáng trên phím, một chấm vị trí và một chấm cho mỗi ngón. Camera vẫn được mở cho lớp này khi ảnh camera đang ẩn, và dòng trạng thái báo số ngón, phím và phần trăm khung hình bàn tay chiếm. Đây là thị giác cổ điển nên cần nền đơn giản và đủ sáng; hai bàn tay cùng lúc thì chỉ bàn tay lớn nhất được theo. |
| AR overlay | Thẻ AR OVERLAY trong Camera & FX: camera làm nền toàn màn hình, 4 góc calibration, FOV và pose bàn phím pitch/yaw/roll tự do được lưu. Calibrate có gizmo kiểu Unity Move / Rotate / Scale cùng thanh trượt số cho tịnh tiến và scale đồng đều; calibration v5 lưu đầy đủ transform, còn v1–v4 nạp translation 0/scale 1. Notes in perspective tạo note 3D camera-facing với cùng đường chiếu đã hiệu chỉnh ở Lite/Standard/Ultra; Quality chỉ đổi ánh sáng/hiệu ứng. Mũi tên vàng là hướng Learn chạy về phím; Perform MIDI đi lên theo hướng ngược lại. Calibrate có căn hướng Perform lên màn hình hoặc căn theo chiều sâu phím, cộng tilt/azimuth độc lập với transform. Các núm hướng nằm sau toggle và trước MinLength. Live press bars riêng mặc định tắt (key tint độc lập); khi có pose 3D, chúng dùng cùng đường chiếu ở mọi bậc Quality; mặc định giữ phím không tự làm thanh mờ, còn pha nhả vẫn giảm sáng. Có độ mờ vệt nhấn theo vòng đời tuỳ chọn (hai thanh trượt, mặc định tắt) để vệt đổi độ đậm theo quãng đã vươn, và thanh trượt Độ phủ (giấu sau chevron) cho đầu vệt chạy vào thân phím — tới mức phủ toàn bộ ở 200 % — cả flat lẫn 3D. Mọi vệt nhấn còn được tô theo tint VelocityColor của look theo strength thật của cú phím. Ánh sáng bấm phím theo tay đúng một lần cho một cú bấm — ngón tay là nguồn duy nhất, không còn cú sáng lặp khi đầu vệt live rơi chạm phím (ở cả hai hướng nốt); flash impact nở vào mượt ~55 ms và tắt dần êm, độ sáng/kích thước đi theo velocity rõ rệt hơn (dải nhạc động rộng gấp ~3 lần), và phím giữ lâu "thở" nhẹ — tất cả ăn đồng thờ cho Live Stage lẫn lớp phủ AR vì chung một pipeline sáng. Từ bậc AR Standard, một vạch damper phát sáng ôm mép sau phím — vươn lên và sáng dậy theo pedal sustain (mức 0..1), màu halo của look; khung nhìn 90° overhead có calibration riêng và được CI chiếu lại trên WARP. Toggle Alignment check (thẻ KEYBOARD ALIGNMENT) vẽ lại khung phím đã lưu kèm vạch quãng tám lên ảnh camera đang chạy — thuộc máy, mọi bậc chất lượng, chỉ khi đã calibrated — để thấy ngay camera có bị xê, và khung chọn được 5 màu tương phản; bảng độ cao catalogue đã khóa (impact nổi 0,2 cm, pulse ≥ 0,3 cm, ArElementHeights). Vạch damper cũng có toggle bật/tắt riêng và màu Theo look (halo)/Tự chọn ở thẻ AR PLAYBACK. Đo trôi tự động (toggle trong thẻ KEYBOARD ALIGNMENT, mặc định bật, thuộc máy) cứ vài giây so vùng phím trên ảnh với calibration; phím trôi quá ngưỡng trong vài lần đo liền thì dòng trạng thái AR nhắc đánh dấu lại — không bao giờ tự chỉnh calibration. Hai nút ngay trong thẻ, EXPORT CALIBRATION / IMPORT CALIBRATION, ghi mọi calibration của camera đang dùng ra một tệp JSON keyflow-ar-calibration (kèm ảnh PNG chụp frame camera cạnh tệp để người dùng dễ nhận ra) và khôi phục khi cần — mỗi hàng khôi phục qua đúng kiểm tra của một lần calibrate mới, hàng hỏng bị bỏ qua, và không đụng tới setting nào nên preset/mã chia sẻ không bao giờ mang calibration. Trong cửa sổ Calibrate, một dòng mẹo đặt camera (treo qua vai, tránh ống quá rộng, tay ngoài khung) khép lại các lỗi quen thuộc của lần đầu, và trạm canh màu da sống bên trong vùng phím đã đánh dấu khẽ cảnh báo khi có tay trong khung hình lúc đang đánh dấu., và lớp AR tự tinh chỉnh theo khung nhìn (ArViewPresets): view nghiêng giữ lift catalogue kèm beam/arc đứng dựng, view 90° trực đỉnh thu về dưới pixel và giấu quad đứng. Một ngân sách hiệu năng (ArQualityBudget) cắt các họ hiệu ứng theo thứ tự roadmap — hạt, pulse, note light, light bars, style nốt, impact glow — khi GPU quá ngân sách và trả lại nguyên vẹn; phím/note/keypress không bao giờ bị cắt. Không vẽ flat stage trails trên camera; ambient cánh hoa/sao băng vẫn phẳng. Đường render có regression WARP; build/xUnit/WARP chưa chạy ở sandbox này, webcam thật vẫn Unverified (xem docs/AR-OVERLAY-ROADMAP.md). |
| Lớp không khí (tuỳ chọn) | Hạt acoustic lơ lửng trong không gian hoà nhạc (số lượng, màu), bật ở Background → ATMOSPHERE → Acoustic motes; 4 lớp ambient độc lập (Energy/Nature/Light/Cosmic) ở trang Background — sét, mưa, thiên hà, matrix... Mặc định tắt để sân khấu sạch. |
| Hiệu năng | Mọi animation (nốt rơi, cánh hoa, backdrop, chuyển panel) chạy trên cùng một đồng hồ vsync (FrameClock) nên không rung, không vẽ thừa khung hình; đồng hồ tự nhả khi sân khấu đứng yên. Mức chuyển động Off/Calm/Full, tôn trọng thiết lập giảm animation của Windows. |
| Chất lượng tự động (GPU) | Chỉ áp dụng trên GPU phần cứng. Nếu p95 timestamp D3D11 của pass scene+bloom+composite vượt 90% ngân sách liên tiếp 3 cửa sổ × 60 mẫu, hệ thống giảm theo thứ tự bloom 6→4→2 tầng, rồi mật độ ambience 100→60→30%; hồi phục từng bước sau 8 cửa sổ p95 dưới 75%. Bộ điều khiển chỉ đổi bloom/ambient, không đổi hình học nốt/phím, pitch/timing, preset hay JSON đã lưu; tier chỉ áp cho sân khấu đang hiển thị, bản ghi luôn Full và các khung chỉ phục vụ ghi hình không được lấy mẫu, kèm trạng thái lý do. Query DONOTFLUSH không chặn luồng dựng hình; WARP/phần mềm bị bỏ qua, còn --bench, probe vật liệu nốt và ảnh tất định luôn Full. Logic đã có test chuyển tier; lợi ích hiệu năng trên iGPU thật vẫn Unmeasured cho tới khi đo lại bằng phần cứng. |
Sân khấu chính chính là engine GPU Direct3D 11 (mặc định từ bản này; ảnh CI dựng bằng WARP):
Cũng sân khấu ấy với một hợp âm đang giữ (--play-chord): thanh giữ và các hiệu ứng giữ phím nối những
phím đang nhấn trên diện mạo mặc định Concert Gold — cùng engine nhưng khác bức hero phía trên, nên hai ảnh không còn là một.
Cùng engine với hai preset dùng các họ hiệu ứng: Galaxy Voyage (vệt cầu vồng, lớp Cosmic thiên hà) và Electric Storm (vệt speed lines, tia điện nối các phím đang giữ, lớp Energy bão sét):
Mọi preset có sẵn, mỗi preset vẽ bằng cả hai engine cạnh nhau (phần mềm | GPU), nằm trong gallery CI docs/previews/presets.jpg. Gallery vật liệu nốt so sánh Solid/Neon/Glass/Fire/Gem/Crystal Outline trên WPF và GPU; các ô được thu còn 50% để soi độ rõ của mặt cắt Gem và viền pha lê:
Gem và Crystal Outline là material của sân khấu WPF/GPU; AR camera-facing quads dùng chung shader material nhưng không dùng mesh stage. Notes3D là trục depth độc lập; Streak vẫn để giai đoạn trail sau. WARP style samples cho AR đang chờ CI; full parameter/value parity, webcam và hardware vẫn Unverified. Workflow dựng lại hai gallery; tools/check_sources.py kiểm tra preset gallery, ma trận note-material 9×2, artifact và drift gate.
| Nhóm | Chi tiết |
|---|---|
| Piano | Chỉ dùng Salamander Grand Piano V3 (Yamaha C5): tải một lần khoảng 748 MB từ SETTINGS → Audio; tiến độ hiển thị ngay trên trang, từng tệp được xác minh, có thể huỷ/tiếp tục, và sau cài sẽ tự nạp lúc mở app. Không đóng gói mẫu, không nhập SF2 hoặc nhạc cụ SFZ khác. Phát stereo 44.1 kHz qua WASAPI shared/exclusive, ASIO hoặc waveOut; trang Audio cũng có gain, reverb, cộng hưởng dây và trạng thái đường ra/buffer thật. |
| Reverb | Hall reverb stereo chạy thời gian thực, bật mặc định, có nút bypass độc lập; reverb vẫn vang sau khi nhả phím. Ngoài hall cổ điển còn có convolution reverb (FFT phân vùng, overlap‑save): phòng hòa nhạc ~2,8 s và bảng cộng hưởng gần máy sinh sẵn trong app, hoặc impulse response .wav/.flac do người dùng nạp. |
| Pedal | Ba pedal MIDI tiêu chuẩn: una corda/soft (CC 67, giảm gain và làm dịu âm), sostenuto (CC 66, giữ những nốt đang nhấn khi pedal xuống), sustain/damper (CC 64, giữ tiếng sau khi nhả phím). Pedal sustain là damper liên tục: pedal nửa chừng (half‑pedal) kéo dài đuôi tiếng thay vì giữ hẳn, và đạp lại kịp lúc (repedal) bắt được dây còn đang kêu — giá trị CC 64 thô từ pedal liên tục đi thẳng vào synthesizer. Khi pedal xuống, dàn cộng hưởng dây (64 waveguide, một dây một phím) cùng ngân vang như trên đàn grand thật và tắt ngay khi nhả pedal; nút STRING RESONANCE trong Settings → Audio bật/tắt. Nút trên màn hình, pedal MIDI vào Salamander và MIDI output đều được kết nối; độ dài thanh nốt live chỉ tăng trong lúc phím được giữ, còn pedal chỉ tác động lên tiếng đàn. |
| MIDI vào | Phím máy tính, click/giữ phím ảo và WinMM MIDI input; tự mở MIDI input đầu tiên, giữ lựa chọn sau khi làm mới danh sách, hiển thị tín hiệu/nốt nhận được. |
| MIDI ra | WinMM MIDI output để gửi Note On/Off tới synthesizer hoặc thiết bị MIDI đã chọn. |
| Bài MIDI | Đọc định dạng 0/1/2 (format 2 là các chuỗi độc lập nên được chạy lần lượt, hết chuỗi này tới chuỗi kia), độ chia PPQ hoặc SMPTE (SMPTE là thời gian tuyệt đối: tempo chỉ đổi nhịp của metronome chứ không đổi vị trí nốt), tempo map, nhịp, tên track, thời lượng/velocity nốt (bỏ qua kênh trống 10), solo/mute và đổi màu từng track, tua, tempo, loop A–B, metronome theo tempo map (nhấn mạnh phách đầu ô nhịp). |
| Luyện tập | Follow along, Wait for my note, Right hand only, Left hand only; chờ nốt đúng mới đi tiếp. Tua, đổi chế độ/track hoặc lặp A–B không tính các nốt đã bỏ qua là miss; mỗi vòng lặp A–B được chấm lại từ đầu. |
| Điểm số | Thống kê hit/miss, accuracy, streak và phản hồi đúng thời điểm trên footer. |
| Nhóm | Chi tiết |
|---|---|
| Ba giao diện hoà nhạc | Concert Grand (Steinway ebony & vàng champagne — theme mặc định nếu chưa có preset chọn giao diện), Concert Noir (obsidian & platinum), Velvet Gold (nhung đỏ mahogany & đồng thau; được preset Concert Gold ghép sẵn). Đổi theme là đổi toàn bộ surface/accent qua DynamicResource trong một khung hình, không cần mở lại app; backdrop động (sóng cộng hưởng, quầng cực quang, nhung) đi kèm từng theme. |
| Theme tự tạo | Trang Theme → USER THEMES: CREATE / EDIT / DELETE mở xưởng theme — đặt tên, chọn họ nền động (Acoustic/Obsidian/Imperial) và năm màu (màu nhấn, màu nhấn phụ, phát sáng, bề mặt, hạt sáng); hai mươi token còn lại của giao diện được suy ra từ năm màu đó nên không thể tạo ra bảng màu chữ không đọc được. Xưởng có dải xem trước cập nhật ngay khi gõ. Theme lưu thành tệp JSON nhỏ trong %LOCALAPPDATA%\Keyflow\themes\*.json (sửa tay được) và hiện cạnh ba theme có sẵn ở cả hai hàng chip. |
| Preset sân khấu | 14 look built-in: Concert Gold (mặc định), Neon Violet, Two Hands, Classic Roll, Green Screen, Inferno, Ice Crystal, Sakura Nocturne, Moonlight Sonata, Aurora Rainbow, Ocean Depths, Galaxy Voyage, Electric Storm, Retro Arcade; kèm 3 preset community Ember Rain, Lo-Fi Study, Sunset Drive. Các look theo vai trò Concert / Neon / Learning với lớp FX tiết chế. Preset người dùng lưu trong %LOCALAPPDATA%\Keyflow\presets\*.json với SAVE AS / DELETE / IMPORT / EXPORT; header hiển thị tên preset và dấu * khi đã chỉnh sửa. |
| Thư viện theo thư mục | Hộp thoại Play có mục LIBRARY: nút icon thư mục để lập chỉ mục một thư mục (mỗi tệp MIDI/MusicXML được đọc bằng chính reader của app để lấy số nốt, số track, độ dài, tempo), nút icon làm mới đọc lại (chỉ đọc lại tệp đã thay đổi), ô tìm kiếm lọc theo tiêu đề/tên tệp/thẻ, mỗi dòng có nút mở bài, thông tin bài và các thẻ (thêm bằng + TAG…, bỏ bằng cách bấm thẻ). Thư mục được theo dõi nên thêm/xoá/đổi tệp là danh sách tự cập nhật; chỉ mục và thẻ nằm trong library-index.json, tệp hỏng đọc thành thư viện rỗng thay vì lỗi. |
| Bóng luyện tập & biểu đồ | Trang History có PRACTICE CHART (mỗi ngày một cột trong 14 ngày gần nhất, cột cao theo độ chính xác của ngày đó, ngày không luyện vẫn nằm trên trục, dòng dưới in số lượt và trung bình của cả cửa sổ) và GHOST OF THIS SONG (mỗi chấm là một nốt đã chấm điểm của lượt chơi: ngang theo vị trí trong bài, dọc theo cao độ, xanh = trúng, đỏ = trượt; lượt tốt nhất và lượt mới nhất dùng chung trục nên so được với nhau; bài chưa chơi lần nào thì in lý do thay vì khung rỗng). |
| Preset thumbnail | Mỗi preset trong danh sách Style có thumbnail mini render thật (nền tối, phím trắng/đen, vạch nốt theo palette). |
| Lớp khuông nhạc | Sheet music (thẻ LAYERS, mặc định tắt) vẽ khuông đôi phía trên piano roll và chạy theo playhead: mỗi nốt nằm trên khuông mà điểm chia tay xếp cho nó, khoá nhạc 𝄞/𝄢 (khi font có glyph, không thì chữ G/F), hoá biểu suy ra từ chính bài (tương quan Krumhansl–Kessler trên thời lượng vang của từng cao độ; bài không khớp tông nào thì viết theo C major) nên ký hiệu viết đúng tông đó — F major viết B♭ trên dòng B chứ không phải A♯ trên dòng A — và dấu hoá chỉ hiện khi ô nhịp thật sự cần: nốt đã có trong hoá biểu thì trơn, dấu thăng/giáng giữ đến hết ô nhịp của chính khuông đó, nốt tự nhiên lấy lại bằng dấu hoàn ♮, dòng kẻ phụ khi nốt ra ngoài khuông, vạch nhịp theo lưới phách của chính bài (ô nhịp đậm hơn), vòng sáng quanh nốt đang vang và nốt đã đánh/đánh trượt đổi màu. Nốt ngắn còn được nối đuôi theo phách (beaming) như bản khắc nhạc: độ dài một phách lấy từ chính lưới phách của bài, nốt đủ ngắn thì mọc cờ (móc đơn/đôi/ba), hai nốt cạnh nhau trong cùng một phách và cùng một tay thì chung một đuôi nối, nốt nằm giữa hai nốt đó làm đứt đuôi, và nốt rỗng (nốt trắng trở lên) không bao giờ vào đuôi nối dù nhịp có nhanh. Mỗi tay là một bè, nên chỗ tay kia im lặng được viết bằng dấu nghỉ trên khuông của chính tay đó, với hình dấu nghỉ đúng độ dài khoảng lặng (tính theo phách của bài: nốt móc đơn/đôi, nốt đen, nốt trắng, nốt tròn), và khoảng lặng liền mạch chỉ viết một dấu nghỉ chứ không phải một dấu cho mỗi nốt tay kia đánh, còn khoảng lặng dài được viết theo từng ô nhịp: tay im suốt bốn ô thì ra bốn dấu nghỉ, ô nào im từ đầu tới cuối ô đó là dấu nghỉ tròn (đúng cách bản khắc viết một ô nhịp im lặng, bất kể nhịp gì), và phần lẻ ở hai đầu — chỗ tay bắt đầu hoặc thôi đánh giữa ô — giữ hình theo độ dài của chính nó. Toàn bộ phần tính trước của khuông (dấu hoá, đuôi nối, dấu nghỉ — SheetLayer.Plan) được giữ lại giữa các khung hình và chỉ tính lại khi đổi bài, đổi lưới phách, đổi điểm chia tay hay đổi tông, nên vẽ 60 khung/giây không phải tính lại từ đầu. Nốt bị vắt qua vạch nhịp (cùng cao độ, nốt sau bắt đầu đúng chỗ nốt trước dứt) được nối bằng dấu luyến: một đường cong nối hai đầu nốt, vẽ về phía đuôi quay đi (đuôi lên thì cong xuống dưới), và nốt được luyến không ghi lại dấu hoá — dấu hoá đi theo dấu luyến qua cả vạch nhịp, nên nốt sau nó trong ô nhịp mới vẫn được ghi đúng. Hợp âm được ghép thành một cột nốt: các nốt cùng lúc trong cùng một tay là một sự kiện — một đầu nốt cho mỗi cao độ nhưng chung một đuôi (đuôi dài từ đầu nốt xa nhất qua giữa cột, cờ theo nốt ngắn nhất), và đuôi nối đi qua hợp âm như đi qua một nốt. SheetLayer.Chords/Streams cũng gom bài thành hai dòng đọc (mỗi tay một dòng, mỗi sự kiện một nhóm nốt xếp theo cao độ) và đưa vào phần tính trước Plan — nền cho cách vẽ đánh dấu theo ngón tay. Chỗ dòng nhạc đổi tay cũng được viết ra: nốt của tay này dứt đúng chỗ nốt tay kia bắt đầu (trong cùng khoảng chờ mà dấu luyến cho phép) là một câu nhạc đổi tay, nên hai đầu nốt được nối bằng một đường cong vắt qua khoảng giữa hai khuông (SheetLayer.Slurs/Slur) — cong theo hướng dòng nhạc đi để ôm lấy khoảng giữa hai khuông chứ không cắt qua khuông nào. Hai nốt cách nhau một quãng tám trở lên là hai bè (bè trầm và giai điệu) chứ không phải đổi tay, và hợp âm không đổi tay: một khối nốt là hợp âm mới chứ không phải dòng nhạc đi tiếp. Đây vẫn là lớp đọc nốt chứ chưa phải bản khắc nhạc đầy đủ: mỗi khuông mới có một bè, chưa vẽ nhiều bè trên cùng một khuông hay dấu luyến/articulation đọc từ chính bản nhạc. |
| Nhập bài | MIDI (.mid, .midi) và MusicXML (.musicxml, .xml, .mxl — tệp nén đọc qua container): nhạc cụ, số phách trong ô nhịp, tempo map, nhịp đổi giữa bài, hợp âm (<chord/>), backup/forward, tên bè; điểm chia tay của bản nhạc đọc thẳng từ <staff> (khuông 2 = tay trái) nên không phải suy luận, và được ghi nhớ theo tệp. |
| Menu & hộp thoại | Menu hoà nhạc và hộp thoại Play được mở từ menu chính hoặc nút Play trên header; có Back theo đúng bề mặt gọi, nút bánh răng/chevron mở dock rồi quay lại Play, nhóm chọn bài / RECENT / LIBRARY, hai thẻ tay thao tác được bằng bàn phím, tốc độ phát, lớp sân khấu và CTA tự đổi giữa chọn MIDI, bắt đầu phát và quay lại phần đang phát mà không khởi động lại bài. Thư viện không để ô tìm kiếm rỗng khi chưa lập chỉ mục. |
| Thẻ F1 | Bảng phím tắt trong ứng dụng, chia ba nhóm (Play the stage / Move around / Session & capture). |
| Hoàn tác / làm lại | Mọi thay đổi trong dock đi vào lịch sử 32 bước: Ctrl+Z lùi, Ctrl+Shift+Z (hoặc Ctrl+Y) tiến. Ảnh chụp là JSON của PianoVisualSettings nên đi qua đúng CopyFrom/Clamp như khi bạn tự đặt lại giá trị; đổi preset cũng là một mốc; còn nhập hồ sơ thì mở một lịch sử mới — trạng thái vừa nhập là mốc đầu tiên nên Ctrl+Z không lùi được qua nó. |
| Hồ sơ cài đặt | IMPORT/EXPORT PROFILE… ở trang General gói cả ba thứ vào một tệp Keyflow.profile.json: cài đặt sân khấu, ngôn ngữ và giao diện. Kéo‑thả tệp hồ sơ vào cửa sổ là áp dụng; thả tệp .mid/.midi để mở bài, thả ảnh để đặt nền. Ngôn ngữ không có trong bản build sẽ rơi về tiếng Anh chứ không để giao diện nửa dịch. |
| Trợ năng | Mọi nút chỉ có glyph (loop A/B/×, ↺, các nút cửa sổ, chevron của hộp thoại Play) đều có tên cho trình đọc màn hình, lấy từ chính tooltip đã dịch nên đổi ngôn ngữ là đổi theo; các hàng sinh tự động của dock (slider, ô số, combo, swatch màu) cũng được đặt tên theo nhãn của hàng. Tab giữ tiêu điểm trong dock (KeyboardNavigation.TabNavigation="Cycle") và đi qua từng trang theo đúng thứ tự các hàng được in; ở cửa sổ nhỏ (1080×700) mọi hàng vẫn nằm trong cột cuộn và mọi điều khiển vẫn nằm trong thẻ của hàng (VerifyDockAccessibility), và khi Windows bật high contrast thì khung giao diện vẽ bằng chính màu hệ thống (SystemColors) còn theme đã chọn vẫn được giữ nguyên trong file cài đặt — Windows báo đổi lúc nào là cửa sổ vẽ lại ngay lúc đó (SystemParameters.StaticPropertyChanged). |
| Đa ngôn ngữ | Hai ngôn ngữ đóng gói: English và Tiếng Việt, chọn ở trang General hoặc bằng chip ngay trên menu khởi động; đổi là toàn bộ nhãn, hộp thoại, thông báo lỗi và menu ngữ cảnh vẽ lại trong khung hình hiện tại. Không có nhãn tiếng Anh lọt sang tiếng Việt: tools/check_sources.py chứng minh hai bảng cùng tập khoá và --verify đổi ngôn ngữ thật trên cửa sổ đang mở. |
| Ghi âm thanh | Trang Recording có công tắc Record audio: bản AVI/chuỗi PNG kèm tệp WAV stereo 16-bit ngay cạnh video (take.wav / audio.wav trong thư mục khung) để ghép sau, còn bản MP4 nhận đúng những mẫu đó vào thẳng trong tệp dưới dạng AAC nên không còn gì phải ghép. Âm thanh lấy từ chính các khối mà engine render (tap này chỉ được gắn trong lúc ghi nên lúc diễn bình thường không tốn gì); dòng thông tin đổi theo thực tế từng máy (Salamander đã nạp → ghi rõ đường tiếng đi đâu, chưa nạp → nói rõ bản ghi sẽ không có tiếng). Với AVI, nút REC vẫn in ra dòng ffmpeg -i video.avi -i take.wav -c:v copy -c:a aac take.mp4 để ghép hai tệp. |
| Ghi hình | REC ghi khung hình sân khấu theo độ phân giải (cửa sổ/720p/1080p) và 15–60 fps, canh theo đồng hồ thật: AVI một tệp, hoặc chuỗi PNG 32-bit vào một thư mục (mỗi khung một tệp frame-000001.png, kèm sequence.json ghi kích thước/khung hình mỗi giây/số khung và dòng lệnh ffmpeg để dựng lại thành video alpha). Bật Transparent background cho chuỗi PNG để sân khấu bỏ các lớp tô đục — nền chụp trong suốt, cây đàn và hiệu ứng vẫn còn (tắt lớp Background nếu chỉ muốn mỗi cây đàn). Đường tiếng: bật Record audio (mặc định bật, trang Recording) thì REC ghi thêm một tệp WAV stereo 16-bit 44,1 kHz cạnh video (trong thư mục khung nếu ghi chuỗi PNG) — đúng những khối PCM mà engine phát ra (hoặc tự render khi máy không có thiết bị ra âm thanh, nên vẫn ghi được đủ độ dài), và hộp thoại sau khi dừng ghi in độ dài cùng dòng lệnh ffmpeg ghép thành MP4. MP4 (H.264 + AAC) ghi bằng Media Foundation sink writer đã có sẵn trong Windows — không thêm thư viện, không cần ffmpeg: khung hình của sân khấu được chuyển sang NV12 (BT.601) rồi mã hoá H.264 với bitrate tính theo kích thước × fps (sàn 2 Mbps, trần 24 Mbps), còn PCM của engine được mã hoá AAC ngay trong cùng tệp, mỗi mẫu mang đúng mốc thời gian của nó (Mp4Recorder.FrameTime/AudioTime). Máy không có bộ mã hoá H.264 thì báo rõ bằng câu có mã lỗi thay vì ghi ra tệp hỏng; máy có H.264 nhưng thiếu bộ mã hoá AAC vẫn ghi được phần hình và hộp thoại nói rõ vì sao thiếu tiếng. Máy chưa nạp Salamander thì chỉ ghi hình và dòng thông tin nói rõ. Preset Green Screen tô nền xanh lá thuần để key trong OBS. Máy không có codec MJPEG thì ghi RGB không nén và tự dừng khi chạm giới hạn 2 GB của AVI. |
Giao diện có hai ngôn ngữ đóng gói — English và Tiếng Việt — và đổi ngay trong ứng dụng, không khởi động lại: mọi nhãn, tooltip, hộp thoại, danh sách combo, thẻ phím tắt F1, menu khởi động và cả thông báo lỗi đều vẽ lại trong khung hình hiện tại.
| Ở đâu | Làm gì |
|---|---|
| Trang General (dock → nhóm APP) | Chọn English, Tiếng Việt hoặc Theo Windows. Ô mô tả bên dưới ghi rõ ngôn ngữ nào đang chạy và nó được chọn từ đâu. |
| Menu khởi động | Thẻ INTERFACE LANGUAGE với chip chọn nhanh — mở app lần đầu là đổi được ngay, không phải đi tìm trang cài đặt. |
| Dòng lệnh | --lang=vi (hoặc en) chạy một lần bằng ngôn ngữ chỉ định, không ghi vào visual-settings.json. CI dùng cờ này để render ảnh tiếng Việt. |
| Tìm kiếm trong dock | Một hàng khớp cả từ tiếng Anh lẫn từ đã dịch: gõ speed hoặc tốc độ đều ra cùng slider Fall speed. Ngoài nhãn, mỗi hàng còn trả lời từ đồng nghĩa (tempo → Fall speed, fps → Frame rate, brighter → các slider độ sáng) và tên setting (NoteFallSpeed → fall+speed), nên không phải nhớ đúng chữ trên nhãn. Nhiều từ khoá là phép giao: speed fall thu hẹp đúng hàng đó. Phần khớp được tô màu accent ngay trong nhãn, và ô tìm kiếm tự nhảy sang trang đầu tiên có kết quả. |
Ảnh chụp trang General khi app chạy tiếng Việt — bộ ảnh tiếng Việt của CI, render với --lang=vi:
Toàn bộ điều hướng, nhãn, chú thích và nút bấm đều là tiếng Việt; tên preset Concert Gold vẫn giữ nguyên vì đó là id ổn định. Ảnh dừng ở thẻ ENGINE ĐỒ HỌA (GRAPHICS ENGINE: tốc độ khung hình GPU, VSync, nút mở cửa sổ sân khấu GPU); phía dưới còn thẻ HỒ SƠ CÀI ĐẶT (SETTINGS PROFILE: nhập/xuất hồ sơ).
Ba điều nguyên tắc:
- Id không đổi, chỉ caption đổi. Giá trị trong
visual-settings.json, tên preset, id theme, tên thiết bị Windows và tên tệp luôn là tiếng Anh — bật tiếng Việt không làm hỏng preset hay file đã lưu, và một preset đem sang máy tiếng Anh vẫn đọc được. - Thiếu bản dịch thì in tiếng Anh, không in ô trống. Khoá lạ không có trong inventory (tên thiết bị, tên tệp người dùng đặt) đi thẳng tới màn hình nguyên văn.
- Thêm ngôn ngữ = thêm một tệp bảng. Sao chép
Localization/Strings.English.cs, dịch phần giá trị, thêm một dòng vàoLanguages.All; renderer, XAML vàvisual-settings.jsonkhông phải sửa gì.tools/check_sources.pyvà--verifysẽ chứng minh bảng mới dịch đủ mọi khoá của inventory.
Chi tiết kiến trúc, quy ước dịch và checklist thêm ngôn ngữ: docs/LOCALIZATION.md.
Dock cài đặt có 12 trang trong ba nhóm, dùng chung một bộ giá trị cho Live Stage/AR. Chọn Stage/AR trên header chỉ nhấn mạnh ngữ cảnh, không bật output hay đổi camera.
| Nhóm | Trang | Nội dung |
|---|---|---|
| THIẾT KẾ HIỆU ỨNG | Tổng quan | Preset built-in/community/người dùng, import/export/share code, công tắc hạt nâng cấp và lối tắt. |
| Nốt & màu | Màu theo pitch/tay/track, vật liệu, hình dáng, chuyển động và falling FX. | |
| Nhấn phím | Impact/rings/flash, spark emitter và physics dùng chung nhấn/giữ. | |
| Giữ phím | Wisps, Flames và Hold FX. | |
| Thả phím | Kiểu nhả và cường độ. | |
| Ánh sáng | Key lighting, AR key tint, light beams, halo, shading, post-FX và modulators. | |
| KHÔNG GIAN | Sân khấu & nền | Cỡ/kiểu bàn phím, ảnh/màu nền, atmosphere và ambient. |
| Camera & AR | Nguồn camera/video, căn chỉnh và backup, đường đi 3D, live bars, delay/quality, output và hand tracking. | |
| PHIÊN & ỨNG DỤNG | Ghi hình | AVI/PNG/MP4, độ phân giải/FPS/audio, ghi và xuất AR offline. |
| Âm thanh & MIDI | Salamander, reverb/IR, output/ASIO/latency/gain, MIDI in/out, track mute/color. | |
| Luyện tập & lịch sử | Practice mode/tempo/auto-tempo/loop, chart/ghost, lịch sử và xuất/xóa báo cáo. | |
| Ứng dụng | Theme, ngôn ngữ, FPS/VSync, trợ năng, cập nhật và profile import/export. |
Điều khiển phổ biến đặt trước; chi tiết nằm trong Nâng cao. Tìm kiếm mở phần có kết quả, xóa query trả lại trạng thái mở trước đó. Tên trang CLI cũ vẫn được chuyển đến nhóm mới; preview Practice và History trước đây nay cùng một trang Luyện tập & lịch sử.
Phần còn lại của giao diện:
| Khu vực | Nội dung |
|---|---|
| Header | Logo + brand, bài đang mở, chip LOOK (preset hiện tại + nút icon đổi diện mạo), HOME, Play (mở hộp thoại chọn bài/Live Play/cài đặt nhanh), nút bánh răng cài đặt, thu nhỏ/toàn màn hình/thoát. |
| Sân khấu | Nốt, hiệu ứng, bàn phím, badge thiết bị, nút REC, nút MENU. |
| Footer | Play/Pause, quay lại đầu, thời gian + nốt đang chơi, ba pedal, ACCURACY / SCORE / STREAK, thanh tua + progress. |
| Dock cài đặt | Cột điều hướng ba nhóm + ô tìm kiếm lọc mọi trang (tự nhảy sang trang có kết quả; khớp cả từ tiếng Anh lẫn từ đã dịch — gõ speed hay tốc độ đều ra cùng slider), mỗi thông số có slider + ô nhập số (chấp nhận đơn vị, dấu phẩy, tên nốt như C4) + nút reset, hàng phụ thuộc tự ẩn/hiện, nút Lưu + nút icon đặt lại trang, tự lưu sau 0,65 s. |
- Salamander Grand Piano V3 là nhạc cụ duy nhất: Yamaha C5 do Alexander Holm thu ở 48 kHz/24-bit, CC BY 3.0. Bộ mẫu gồm 641 FLAC, tổng 748 397 030 byte (khoảng 748 MB), tải từ mirror
sfzinstrumentsvào%LocalAppData%\Keyflow\Instruments\SalamanderGrandPianokhi người dùng bấm tải. Không tải tự động khi khởi động và không kèm mẫu trong Git, ZIP hoặc installer. - Downloader ghim một commit, xác minh hash Git blob cho cả tệp mới lẫn tệp cục bộ trước khi dùng; mỗi tệp được ghi tạm rồi đổi tên nguyên tử. Các tệp hợp lệ được giữ lại sau khi huỷ và được kiểm lại khi chạy tiếp; SFZ mapping và attribution chỉ được ghi khi đủ toàn bộ bộ mẫu.
- Mapping SFZ được Keyflow sinh cho đúng bộ Salamander, hỗ trợ 16 lớp velocity, hammer/release samples, tiếng pedal và cộng hưởng dây.
SfzReaderđọc tập opcode cần thiết (kế thừa header,#define/#include,default_path, round-robin, release sample, vòng lặp, tuning, amp envelope) và WAV PCM 16/24/32-bit, float32 hoặc FLAC. Mẫu giải nén được giữ trong RAM; cần đủ bộ nhớ cho bộ đàn sau khi tải. Không có nhập SF2 hay chọn tệp nhạc cụ bên ngoài. - Bài MIDI định dạng 0/1/2 và cả hai kiểu độ chia (PPQ lẫn SMPTE) đều đọc được; phần còn thiếu của lớp nhập bài là MusicXML nâng cao (dấu luyến, repeat, nhiều bè ghép phách) — MusicXML cơ bản đã đọc được, kể cả
.mxl, điểm chia tay lấy từ khuông, và có lớp khuông nhạc vẽ trên sân khấu — bật Sheet music ở thẻ LAYERS; khuông này đọc nốt theo cao độ, tự suy ra hoá biểu của bài, nối đuôi nốt ngắn theo phách, viết dấu nghỉ cho tay đang im lặng, nối dấu luyến nốt vắt qua vạch nhịp, ghép hợp âm thành một cột nốt chung đuôi và nối một đường cong khi dòng nhạc đổi tay giữa hai khuông; lịch sử luyện tập ghi theo từng lượt chơi (thời điểm, độ chính xác, nốt đúng/sai, chuỗi dài nhất), giữ luôn từng nốt đã chấm điểm (vị trí trong bài + cao độ + trúng/trượt, tối đa 256 nốt một lượt) nên trang History vẽ được biểu đồ 14 ngày và bóng (ghost) của bài đang mở — lượt tốt nhất trên, lượt mới nhất dưới, chấm xanh là trúng, chấm đỏ là trượt — và báo cáo HTML có thêm bảng theo ngày. Thư viện bài đã có hai phần: danh sách RECENT (library.json, 12 bài gần nhất kèm metadata) và thư viện theo thư mục — chọn một thư mục là mọi tệp MIDI/MusicXML bên trong (tối đa 3 tầng, 500 tệp) được lập chỉ mục kèm số nốt/track/độ dài/tempo, có ô tìm kiếm (khớp tên tệp, tiêu đề và thẻ) và theo dõi thư mục nên danh sách tự cập nhật khi bạn thêm hay sửa tệp; chỉ mục nằm ởlibrary-index.jsonvà chỉ đọc lại tệp nào đã đổi. - Điểm chia tay mặc định là C4 = MIDI 60 (Notes → Hand split point) và dùng chung cho màu theo tay lẫn chế độ tập từng tay. Bật Infer hand split from the song thì khi mở một tệp MIDI, ứng dụng tự chọn điểm chia theo cách các nốt trải trên bàn phím (gom hai cụm theo thời lượng vang, chỉ nhận khi giữa hai tay có ít nhất 5 semitone trống (khoảng một quãng bốn) và mỗi tay chiếm ≥10% thời lượng vang, rồi ưu tiên nốt giữa C4 trong khoảng trống), và ghi nhớ cho riêng bài đó nên mở lại không đổi; bài chỉ một tay hoặc hai tay chồng lấn thì giữ nguyên điểm bạn chọn.
- MIDI input vật lý cần đàn/thiết bị tương thích được Windows nhận diện. Nếu chưa cắm thiết bị, dùng bàn phím máy tính hoặc piano ảo.
- Dùng WinMM nên phiên bản này dành cho Windows.
- Định dạng MP4 (H.264 + AAC) đã có và mang luôn tiếng trong tệp, nhưng cần bộ mã hoá của chính Windows: máy không có H.264 thì không ghi được MP4, còn máy thiếu AAC thì bản ghi chỉ có hình (hộp thoại nói rõ lý do). AVI vẫn là phần hình, tiếng nằm ở WAV cạnh đó để ghép bằng ffmpeg; máy không có codec MJPEG thì AVI dùng RGB không nén và dung lượng sẽ tăng nhanh hơn. WebM (VP9 + Opus) cũng ghi trực tiếp, mã hoá bằng ffmpeg mà máy đã có (cạnh ứng dụng hoặc trên PATH — cố ý không đóng gói kèm vì dung lượng và giấy phép): khung hình được bơm cho libvpx-vp9 theo thời gian thực khi ghi, còn đường tiếng piano được ghép thành Opus khi dừng ghi; máy không có ffmpeg thì nhận một câu báo rõ thay vì bắt đầu một bản ghi không thể hoàn tất. Bật Transparent background thì chính tệp WebM giữ kênh alpha (yuva420p) — chuỗi PNG 32-bit (alpha) vẫn là đường hậu kỳ chính xác từng khung, và
sequence.jsongợi ý sẵn lệnh ffmpeg dựng thành WebM alpha. - Lớp phủ camera PiP có ở Camera & FX → Camera overlay (camera/video, góc, mirror, opacity, key xanh); HAND TRACKING dùng thị giác máy tính cổ điển nên với hai bàn tay/nền gần màu da chỉ theo bàn tay lớn nhất. Riêng thẻ AR OVERLAY hỗ trợ camera làm nền, calibration 4 góc và pose bàn phím 3D tự do. Calibrate có gizmo kiểu Unity Move / Rotate / Scale và thanh trượt số cho translation/scale đồng đều; calibration v5 lưu transform + FOV + baseline, còn v1–v4 nạp translation 0 và scale 1. Note direction tilt/azimuth là hướng di chuyển 3D độc lập. Trong cửa sổ Calibrate, mũi tên hướng nốt chiếu lên preview; có thể căn thẳng lên màn hình hoặc dọc chiều sâu phím mà không xoay đàn. Notes in perspective bật nốt camera-facing 3D; các núm hướng nằm ngay sau toggle và trước Minimum length. Live press bars là toggle riêng, mặc định tắt, không tắt key tint; tuỳ chọn Độ mờ vệt nhấn theo vòng đời (hai thanh trượt, mặc định tắt) cho độ đậm của vệt đi theo quãng đã vươn và đi ngược lại khi nhả phím, kèm thanh trượt Độ phủ (giấu sau chevron) đưa đầu vệt vào thân phím tới mức phủ toàn bộ, vẽ flat hay 3D đều dùng được; tint
VelocityColorcủa look tự tô vệt theo strength thật của cú phím (xanh nhẹ → đỏ mạnh). Ánh sáng chạy theo tay đúng một lần cho một cú bấm (không lặp khi vệt live chạm phím, cả hai hướng nốt), flash nở vào ~55 ms rồi tắt dần êm, sáng/to theo velocity rõ rệt hơn, phím giữ lâu "thở" nhẹ — vẻ này áp cho cả Live Stage lẫn AR vì chung một pipeline sáng. Từ bậc AR Standard thêm vạch damper: vạch sáng mềm ôm mép sau hàng phím, sustain (mức 0..1) làm nó vươn ra thân đàn và sáng dậy, màu halo của look, bloom tự lo hào quang — và có thể tắt hẳn hoặc đổi sang màu Tự chọn trong thẻ AR PLAYBACK; muốn hình kiểu quay thẳng từ trên xuống (90°), chọn khung nhìn90° overheadkhi calibrate và treo camera càng xa phím càng tốt để hạn chế méo mép ảnh — góc này được CI chiếu kiểm trên WARP mỗi build. Thêm toggle Alignment check trong thẻ KEYBOARD ALIGNMENT: vẽ lại khung 4 cạnh của vùng phím đã lưu cùng vạch quãng tám ngay trên ảnh camera (mọi bậc, chỉ khi đã calibrated), nên chân máy bị đụng hay đàn bị xê là các vạch lệch khỏi phím thật ngay — chế độ này thuộc máy, preset/chia sẻ không mang theo, và màu khung chọn được trong 5 màu tương phản; bảng độ cao catalogue cũng được khóa (ArElementHeights: impact 0,2 cm, pulse ≥ 0,3 cm trên mặt phẳng phím). Còn toggle Đo trôi (mặc định bật, thuộc máy): cứ ~2 giây chụp mẫu vùng phím và so với calibration, trôi quá ngưỡng trong 3 lần đo liền thì dòng trạng thái AR nhắc mở Calibrate đánh dấu lại (tay chơi/đổi sáng không báo nhầm, và không bao giờ tự sửa calibration). Cạnh đó, EXPORT CALIBRATION / IMPORT CALIBRATION cho backup mọi calibration đã lưu của camera đang dùng ra một tệp JSON kèm ảnh chụp camera, và nhập lại về đúng cameraLink trong tệp với từng hàng được kiểm tra lại như calibrate mới — hàng không đạt được đếm và bỏ qua chứ không bao giờ tin dùng. Trong chính cửa sổ Calibrate, mẹo đặt camera (máy qua vai, không dùng ultra-wide/fisheye, tay ra ngoài khi đánh dấu) nằm trên ảnh, và trạm canh màu da Y'CbCr cảnh báo ngay khi bàn tay lạc vào vùng phím đã đánh dấu (chỉ là lời khuyên, không phải cửa chặn). Ngoài ra lớp AR tự tinh chỉnh theo khung nhìn (ArViewPresets): camera nghiêng giữ độ nâng hiệu ứng siêu mỏng của catalogue kèm các beam/arc đứng dựng, trong khi khung nhìn 90° trực đỉnh thu về dưới pixel và giấu các quad đứng vốn sẽ ép thành nhiễu. Khi GPU chạy vượt ngân sách,ArQualityBudgettỉa lớp phủ theo thứ tự cố định của roadmap — hạt → pulse → note light → light bars → style nốt → impact glow — rồi trả lại nguyên vẹn; phím, note và keypress luôn được giữ. Stage-space falling trails không vẽ lên camera (kể cả trước calibration); cánh hoa/sao băng/ambient tiếp tục phẳng screen-space. Đường render có regression WARP, nhưng build/xUnit/WARP chưa chạy ở sandbox này; AR-S4 vẫn Unverified trên webcam thật cho tới khi thử phần cứng. - Sân khấu mặc định là engine GPU Direct3D 11 (shader HLSL, đổ bóng bàn phím ở mỗi khung hình), nên chi phí đổ bóng nằm trên GPU. Bộ dựng hình phần mềm (shader CPU đa luồng, chạy một lần bằng
--software) chỉ còn là đường dự phòng khi Direct3D không khởi động được và là đường vẽ cho bản ghi PNG trong suốt: ở đó lần bake đầu ở mức Cinematic trên máy yếu có thể tốn vài chục ms; đã có Fast/Off và cache để giảm. Máy không có GPU tương thích dùng WARP (bộ dựng hình phần mềm của Windows): cùng shader, chỉ chậm hơn card thật. - Trên sân khấu GPU, khuông nhạc, điểm đánh dấu bàn tay, hình camera và bộ đếm/FPS vẫn là lớp vector WPF phủ lên khung GPU — riêng watermark do chính engine in vào khung hình (sprite kind 16) nên bản ghi và bản take offline mang đúng dải mà sân khấu đang hiển thị. Chưa có particle compute shader, depth of field, motion blur theo velocity buffer hay camera keyframe (xem
docs/ROADMAP.md).
Bộ xác minh tích hợp nằm trong Diagnostics/VerificationSuite.cs và chạy ngay bằng chính ứng dụng (cần Windows vì khởi động WPF thật):
dotnet run --project .\PianoPath.csproj -- --verify
# tùy chọn: --verify-log=C:\duong-dan\ket-qua.log (mặc định %TEMP%\keyflow-verification.log)
# tùy chọn: --verify-review-out=C:\duong-dan\phase1-review (ghi 48 ảnh + manifest.json + index.html)Mã thoát 0 là đạt, 1 là có lỗi; nhật ký ghi từng mục PASS/FAIL ngay khi mục đó được ghi ra — một tiến trình bị mã gốc làm sập vẫn để lại dòng cuối cùng nói nó đang làm gì. Bộ kiểm thử tạo tệp MIDI, SFZ/WAV và AVI nhỏ trong thư mục tạm; kiểm tra parser MIDI (đa track, tempo map, lưới phách, tên track, bỏ kênh trống, nhóm phách theo ô nhịp (Meter.Of): nhịp đơn đập theo đơn vị đã ghi (4/4 bốn phách đen, 3/4 ba, 2/2 hai phách trắng, 3/8 ba phách móc) còn nhịp ghép sáu/chín/mười hai đập theo nhóm ba (6/8 = hai phách đen chấm, 9/8 = ba, 12/8 = bốn, 6/4 = hai phách trắng chấm, 7/8 vẫn bảy), lưới của tệp 6/8 đặt đúng phách đen chấm và BeatsPerBar theo số phách cảm nhận nên vạch nhịp vẫn rơi đúng mỗi ô, định dạng 2 chạy các chuỗi lần lượt với lưới phách nối tiếp, độ chia SMPTE đọc tick thành giây tuyệt đối còn lưới phách theo tempo map, từ chối định dạng lạ và độ chia không dùng được, tệp hỏng), giải mã vùng mẫu SFZ và kiểm tra kế thừa opcode, lớp velocity, round-robin, release sample và pedal, loop qua giai đoạn release, giới hạn đa âm, tín hiệu âm thanh và Note Off, sustain/sostenuto/soft, lưu và clamp cấu hình hiệu ứng, preset có sẵn/preset người dùng (lưu, nhập, xuất, xóa, tệp hỏng), danh mục dock: 13 trang chia 4 nhóm khớp giữa catalogue, XAML và tiêu đề nhóm, đổi ngôn ngữ trực tiếp trên cửa sổ đang mở (VerifyLanguageSwitching): hai bảng phải cùng tập khoá, nhãn đã dịch phải vẽ lại khi đổi, và ô tìm kiếm dock phải tìm thấy hàng bằng tiếng Việt, id giao diện cũ (sakura/noir/velvet) tự chuyển sang id chuẩn khi nạp file lưu, chip giao diện, lớp hoa anh đào, impact wave/flash, falling/hold/release FX, 4 lớp ambient, smart modulators, 7 combo themes, chế độ màu theo tay/track, hàng phụ thuộc, tìm kiếm, áp preset, thư viện bài (VerifySongLibrary): chỉ mục nằm trong thư mục cài đặt của lượt chạy, mới nhất trước và tối đa 12 bài (hộp thoại Play hiện 5 dòng đầu), mở lại một bài khôi phục điểm chia tay/tốc độ rơi/tempo qua chính các slider rồi ghi lại đúng một dòng, tệp hỏng đọc thành rỗng, bài bị quên hoặc không còn trên đĩa bị loại, suy luận chia tay (VerifyHandSplitInference + VerifyHandSplitInferenceOnSong): gom cụm theo thời lượng vang, đòi khoảng cách tối thiểu một quãng bốn giữa hai tay và ưu tiên nốt giữa, bài một tay, bài chồng lấn hoặc nốt lạ ngắn không kéo được điểm chia, công tắc đưa giá trị suy luận vào đúng slider rồi lưu vào thư viện bài kèm cờ SplitInferred, mở lại dùng đúng giá trị đã nhớ (không đo lại), tempo luyện tập tự động (VerifyPracticeTempo): mặc định tắt, sai quá ngưỡng thì mỗi bước chậm 5% và dừng ở sàn 50%, đúng liên tiếp 4 nốt thì nhanh lại 2% tới 100%, và một phím sai thật trong lúc bài đang chạy cũng đi đúng đường đó, nhập MusicXML (VerifyMusicXmlImport): chia divisions + tempo thành giây, ô nhịp 6/8 đập hai phách đen chấm đúng theo tempo của ô và nốt không bị xê dịch, hợp âm dùng chung một điểm vào, backup/forward đưa con trỏ đi đúng, đổi tempo giữa bài dịch mọi nốt phía sau, tên bè và các bè chạy song song, điểm chia tay đọc từ <staff> (57 giữa G3 và C4) và từ part, không bịa điểm chia khi hai tay chồng lấn hoặc bài một tay, tệp .mxl đọc qua META-INF/container.xml (và cả khi không có container), nạp từ đĩa qua đúng hàm mà app dùng, và từ chối văn bản không phải XML, phần tử gốc lạ, bản time-wise và tệp không có nốt, lớp khuông nhạc (VerifySheetLayer): cao độ viết theo ký hiệu khoa học và phím đen nằm trên dòng của chữ cái dưới nó (C4 = bậc 28, B4 = 35, F#4 = 31), điểm chia tay quyết định khuông (nốt giữa C4 nằm dưới khuông treble một dòng kẻ phụ, G2 nằm trên dòng dưới cùng của khuông bass), nốt cao hơn không bao giờ bị viết thấp hơn và mỗi nửa cung chỉ dịch một bậc, dòng kẻ phụ chỉ xuất hiện khi nốt ra khỏi khuông, nốt dài thì rỗng không có đuôi còn nốt ngắn có đuôi quay ra xa giữa khuông, cửa sổ nhìn đặt playhead ở một phần tư chiều rộng, khoá nhạc chọn theo font đang cài, màu nốt theo trạng thái đang vang/đã đánh/đánh trượt, tông của bài (MusicKey.Infer) suy ra từ thời lượng vang của từng cao độ (thang D major ra hai dấu thăng F rồi C, F major ra một dấu giáng B, E♭ major ra ba dấu B–E–A, thang minor lấy hoá biểu của major cách nó ba nửa cung, bài rỗng hay chạy nửa cung giữ C major), vị trí dấu trên khuông (F♯ dòng trên cùng khuông treble, C♯ ở khe thứ ba, khuông bass lặp lại hai bậc thấp hơn), ký hiệu viết theo tông (F major viết B♭ trên dòng B còn C major viết A♯ trên dòng A), dấu hoá theo ô nhịp (nốt trong hoá biểu không có dấu, dấu giữ đến hết ô nhịp, dấu hoàn lấy lại nốt tự nhiên, hai khuông giữ ô nhịp riêng, nốt trước ô nhịp đầu thuộc về khoảng lặng), tông chỉ suy ra một lần cho mỗi bài chứ không mỗi khung hình, và nối đuôi và cờ (Beams/Flags/BeatSeconds): độ dài một phách là khoảng giữa của chính lưới phách (lưới không đo được thì nửa giây), nốt đen không cờ còn móc đơn/đôi/ba một/hai/ba cờ, hai nốt móc trong cùng một phách và cùng một tay chung một đuôi nối có hướng đuôi theo nốt đầu và số đường theo nốt ngắn nhất, bốn nốt móc qua hai phách thành hai đuôi chứ không phải một, và mỗi thứ làm đứt đuôi: nốt đen chen giữa, nốt tay kia, nốt ở phách sau, hợp âm cùng lúc, nốt trắng trong bài chậm; ảnh render cho thấy đuôi nối thật sự nhiều mực hơn hai cờ rời, dấu nghỉ (Rests/Rest/Bars): mỗi tay là một bè nên chỗ tay kia im lặng thành một dấu nghỉ trên khuông của tay đó — một bè giai điệu để tay trái nghỉ suốt bấy lâu, hai tay thay phiên nhau thì mỗi khoảng lặng nằm đúng khuông của tay đang nghỉ, một hợp âm rồi tay đó thôi đánh thì dấu nghỉ bắt đầu sau khi hợp âm dứt, khoảng lặng liền mạch chỉ một dấu, và không có nốt nào thì không có dấu nghỉ; khoảng lặng dài được chia theo vạch nhịp (Bars): im bốn ô một giây ra bốn dấu nghỉ tròn, khoảng lặng bắt đầu giữa ô thì ô đầu và ô cuối là dấu lẻ còn các ô ở giữa là dấu tròn, khoảng lặng nằm gọn trong một ô hay lưới chỉ có một vạch thì giữ nguyên, và plan của một bài 2/4 có tay trái im suốt hai ô cho ra hai dấu nghỉ tròn đúng mỗi ô một dấu; hình dấu nghỉ đếm theo phách của bài (nốt móc đôi → nốt móc đơn → nốt đen → nốt trắng → nốt tròn, dài hơn nốt tròn vẫn là nốt tròn, không có phách thì là nốt đen), viết bằng glyph nhạc khi font có và bằng chữ cái đầu khi không; Plan gom dấu hoá + đuôi nối + dấu nghỉ thành một lần tính, được giữ giữa các khung hình và chỉ dựng lại khi đổi bài/lưới phách/điểm chia tay/tông, và ảnh render với dấu nghỉ bị bỏ khỏi plan ít mực hơn đúng phần dấu nghỉ, dấu luyến (Ties/TieUnder): nốt sau bắt đầu đúng chỗ nốt trước cùng cao độ dứt thì thành một dấu luyến (có khe hở, chồng lấn, khác cao độ, khác tay hay có nốt khác chen giữa thì là nốt mới), chuỗi ba nốt là hai dấu luyến, hợp âm vắt nhịp thì mỗi cao độ một dấu luyến; nốt được luyến không ghi lại dấu hoá và dấu hoá đó được ghi nhớ cho phần còn lại của ô nhịp (F♮ luyến sang ô sau vẫn trơn, còn F♯ sau nó vẫn phải có dấu thăng); dấu luyến cong về phía đuôi quay đi, và ảnh render cho thấy đường cong thật sự có mực — bỏ dấu luyến khỏi plan thì mất đúng phần mực đó, hợp âm (Chords/Streams): ba nốt cùng lúc cùng tay là một nhóm còn nốt sau đó là nhóm riêng, hai tay khác nhau thì không cùng nhóm, nốt đơn là nhóm một, dòng đọc của mỗi tay gồm các sự kiện xếp theo cao độ (hai tay đánh cùng lúc vẫn là hai dòng), đuôi nối đi qua hợp âm (hợp âm móc đơn + nốt móc đơn sau nó = một đuôi nối, hai hợp âm trong một phách = một đuôi nối, hợp âm đứng một mình không thành đuôi nối), và ảnh render cho thấy cột hợp âm ba đầu nốt chỉ tốn một đuôi — đưa plan coi chúng là ba nhóm riêng thì mực tăng lên, ảnh render cho thấy hoá biểu thật sự đặt mực vào khoảng giữa khoá và nốt, và chính sân khấu vẽ khuông từ nốt + lưới phách của bài qua công tắc trong dock, đường tiếng của bản ghi (VerifyRecordingAudioTrack): header WAV 44 byte đúng từng byte theo đặc tả (PCM, 2 kênh, 44,1 kHz), khối lẻ bị bỏ đúng phần nửa khung còn nguyên khung được giữ, im lặng và khối lớn hơn buffer vẫn ghi đủ, hai trường kích thước chỉ biết được lúc đóng tệp được vá lại đúng, mẫu ghi little-endian đúng thứ tự, mở tệp không được và tệp đã đóng đều không ném lỗi, đường dẫn WAV nằm cạnh AVI (hoặc audio.wav trong thư mục khung), và engine tự render đúng 8 khối vào writer khi không có thiết bị âm thanh rồi thôi render khi gỡ tap, lớp phủ camera (VerifyCameraOverlay + VerifyCameraOverlayDock): vị trí cho cả bốn góc (đúng tỉ lệ khung hình, không cao quá 80% sân khấu, góc lạ rơi về góc dưới‑trái, sân khấu 0×0 ra hình rỗng chứ không âm), khung hình từ camera được lật đúng khi stride âm và khi mirror, mọi pixel ra khỏi CopyFrame đều đục, key xanh theo ngưỡng (0 = tắt, 100 = rộng nhất, xám/trắng/xanh dương luôn sống sót vì xanh lá phải trội), hệ số opacity, số camera máy đang có, câu trả lời cho tệp video không tồn tại, và đọc lại một khung từ chính clip AVI mà app vừa ghi; phía dock: công tắc + hai combo + ba slider có mặt, chọn góc lưu đúng id tiếng Anh, giá trị ngoài dải bị Clamp, khung hình đi qua PumpCameraFrame (đỏ vào đỏ ra, xanh vào trong suốt ra) và sân khấu vẽ nó đúng ô của góc đã chọn, đọc lại từ ảnh render, chuỗi PNG có alpha (VerifyPngSequenceRecorder): mỗi khung là PNG 8-bit RGBA đúng cỡ bản ghi, alpha giữ nguyên (trong suốt ở ngoài, đục ở trong), khung lặp được ghi đủ số bản, manifest có pattern + dòng ffmpeg, sai kích thước/khung hình mỗi giây/độ lớn khung đều bị từ chối, và một lần chụp thật mất nền nhưng giữ cây đàn khi bật Transparent background, theme tự tạo (VerifyUserShellThemes): năm màu gốc suy ra đủ hai mươi token với bề mặt tối, màu nhấn đủ tương phản và các lớp sáng dần đúng thứ tự, bề mặt ngoài dải đọc được bị kẹp lại, tệp sửa tay bị chữa lành còn tệp hỏng bị bỏ qua, id ổn định theo tên, theme lưu trong thư mục cài đặt được ShellThemes.Find phân giải và áp lên giao diện thật, cả hai hàng chip đều có nó, đổi thư mục cài đặt thì nó biến mất, và xưởng theme từ chối lưu khi thiếu tên hoặc màu không phải hex, kệ preset cộng đồng (VerifyCommunityPresets): mỗi tệp trong presets/ là một preset phong bì đầy đủ — tên đúng mọi khoá của PianoVisualSettings và không thừa khoá nào, giá trị đã ở dạng cuối (nạp lần hai không đổi gì), tên không trùng preset có sẵn và không trùng nhau, tệp hỏng hoặc tên lặp bị bỏ qua chứ không xoá cả kệ, danh sách hiện nhãn COMMUNITY, áp kệ thì cả theme đi theo, và tên của kệ bị từ chối khi lưu đè, ảnh xem trước trong file preset (VerifyPresetThumbnails): sân khấu render thật ở 192×112, preset lưu/xuất mang ảnh theo và đọc lại được, tệp cũ (JSON cài đặt trần) vẫn nạp như preset không ảnh, và một ảnh không phải PNG đúng cỡ bị từ chối thay vì làm hỏng danh sách, mã chia sẻ diện mạo (VerifyPresetShareCodes + VerifyPresetSharing): chuỗi gzip + base64url sau tiền tố phiên bản, đi-về đúng từng giá trị nhưng bỏ đường dẫn ảnh nền của máy người gửi (diện mạo dùng ảnh rơi về nền màu), mã bị chat xuống dòng vẫn đọc được, và mã rỗng/lạ/phiên bản khác/quá lớn/hỏng bị từ chối kèm lý do in ngay trong dock mà không đổi cài đặt hiện tại, lịch sử luyện tập (VerifyPracticeHistory): mỗi lượt chơi một dòng JSON trong history/practice.jsonl của thư mục cài đặt, mới nhất trước và có trần, dòng hỏng bị bỏ qua, lượt tốt nhất theo bài, báo cáo HTML (UTF-8 BOM) có bảng lượt chơi lẫn bảng tổng hợp theo bài, và một lượt chơi thật chỉ được ghi đúng một lần khi dừng transport, bóng luyện tập và biểu đồ (VerifyPracticeGhostAndChart): cột mỗi ngày cao theo độ chính xác của ngày đó (có sàn để ngày luyện ít vẫn thấy được), cửa sổ 14 ngày kết thúc ở hôm nay, ngày không luyện vẫn là một hàng 0 và lượt cũ hơn cửa sổ bị loại khỏi biểu đồ dù tệp vẫn giữ, trung bình của cửa sổ đếm từng nốt một, trục cao độ của bóng là dải nốt của chính lượt chơi (rộng tối thiểu một quãng tám), mỗi nốt nằm đúng chỗ theo vị trí và cao độ, app gom nốt ngay lúc chấm điểm rồi ghi vào lượt chơi khi dừng transport, ghost được quên khi chạy lại bài, giới hạn 256 nốt một lượt và đọc lại nguyên vẹn, và một dòng cũ không có ghost vẫn nạp bình thường, thư viện theo thư mục (VerifySongFolderLibrary): quét ra đúng các tệp đọc được (bỏ tệp hỏng và tệp không phải bài hát, đi xuống thư mục con), số liệu lấy từ chính tệp, lần quét sau dùng lại mục đã có cho tệp không đổi và đọc lại tệp vừa sửa mà giữ nguyên thẻ, RESCAN đọc lại tất cả, thẻ được cắt khoảng trắng/viết thường/giới hạn độ dài và tối đa 8 thẻ một bài, tìm kiếm khớp tiêu đề–tên tệp–thẻ và đòi đủ mọi từ, chỉ mục đọc lại được và tệp hỏng thì quên đi, hộp thoại Play in đúng danh sách + lọc theo ô tìm kiếm + hiện thẻ, và watcher báo khi có bài mới thêm vào thư mục, thẻ phím tắt F1 mở/đóng được, trợ năng (VerifyAccessibility): mọi điều khiển chỉ có glyph đều có tên dịch được, Tab không rời khỏi dock, và bảng màu high contrast lấy từ SystemColors, tự vẽ lại khi Windows báo đổi, mà không ghi đè theme đã chọn, dock ở kích thước thu gọn (VerifyDockAccessibility): 13 trang ở 1080×700 vẫn giữ mọi hàng trong cột cuộn và mọi điều khiển trong thẻ của hàng, và Tab đi qua mỗi trang theo đúng thứ tự hàng được in, ghi AVI frame, **bản ghi MP4 có tiếng (VerifyMp4Recorder): bitrate theo kích thước × fps (sàn 2 Mbps, trần 24 Mbps), mốc thời gian từng khung và từng khối âm thanh đúng đơn vị 100 ns (15 khung 15 fps = đúng một giây, 60 khung 60 fps = đúng một giây), khung sân khấu chuyển sang NV12 đúng BT.601 (đen 16, trắng 235, xám trung tính 128, đỏ Y 82/U 90/V 240) và khung sai cỡ bị từ chối, dock có đủ ba định dạng trong ô Format, và **một tệp MP4 thật được mã hoá ngay trên máy (64×48, 15 khung + một giây tiếng) rồi kiểm lại bằng hộp ftyp đầu tệp — việc mã hoá do một tiến trình con làm (--encode-take, xem bảng tham số) vì bộ mã hoá là mã gốc có thể làm sập hoặc treo cả tiến trình: chạy riêng thì lượt kiểm chứng vẫn giữ được kết luận, từng dòng của tiến trình con được chép vào nhật ký để biết nó dừng ở đâu, bản ghi nhỏ thế này chỉ tốn dưới một giây ở máy chạy được còn tiến trình con quá một phút thì bị bỏ — và máy không ghi được MP4 thì ghi một dòng SKIP nêu đúng bước cuối cùng nó báo thay vì báo lỗi. Trước khi khung đầu tiên được đưa cho bộ mã hoá, tiến trình con hỏi thẳng bộ ghi MP4 xem máy này có nhận luồng H.264 hay không (Mf.EncodeSinkProbe: mở bộ ghi, đưa đúng mô tả luồng mà bản ghi sẽ dùng rồi dừng, không ghi khung nào) — máy không có bộ mã hoá thì dừng ngay tại đó kèm mã lỗi. Khi lượt chạy không ra được tệp MP4 nào, lượt kiểm chứng mới hỏi tiếp câu còn lại bằng phép thử đường ống trong một tiến trình con riêng (Mf.EncodeAviProbe + --encode-probe: ba khung vào một tệp AVI không nén, không cần bộ mã hoá, trần 20 giây) — nó chạy sau lượt ghi chứ không bao giờ trước, nên một phép thử có thể treo không chặn mất chính thứ nó định giải thích (một lượt CI đã cho thấy đúng như vậy), và lời đáp của nó tách bạch đường ống mẫu của app với việc ghi tệp của máy. Theo dõi bàn tay đã có: Camera/HandTracker.cs đọc thẳng khung hình camera bằng số học — luật màu da trong YCbCr (có độ nhạy chỉnh được), lưới ô 32×24, nhóm ô liền kề lớn nhất là bàn tay (tâm, bề ngang, bề cao theo tỉ lệ khung), và đếm ngón theo biên dạng cột: đường "lòng bàn tay" là độ cao mà bàn tay đứng nhiều cột nhất, ngón là đoạn vượt qua đường giữa lòng bàn tay và đầu ngón, nên nắm tay ra 0 ngón chứ không phải một. Thẻ HAND TRACKING ở trang Camera & FX có công tắc + slider Skin sensitivity; camera mở cho lớp này kể cả khi lớp phủ hình đang tắt, và sân khấu vẽ dải sáng trên phím bàn tay đang ở, chấm ở vị trí ngang thật của tay và một chấm nhỏ cho mỗi ngón. Và vì bộ mã hoá là mã gốc có thể ngừng trả lời chứ không báo lỗi, cửa sổ mở bộ ghi MP4 trên một luồng riêng với hạn 10 giây (Video/HangGuard.cs + Mp4Recorder.TryOpen): máy nào không trả lời trong hạn đó thì bản ghi tự chuyển sang AVI kèm câu báo, thay vì đóng băng cả cửa sổ — đúng kiểu máy mà lượt kiểm chứng gặp trên CI, nơi media stack treo hẳn khi đóng dấu thời gian cho sample và chưa từng có tệp MP4 thật nào được ghi ra (xem docs/REPO-AUDIT.md)., đóng/mở MIDI input thật nếu có, giải mã WinMM MIM_DATA tới nốt rơi WPF, MIDI output, tự ẩn/hiện giao diện theo chuột và Esc, độ dài nốt khi giữ phím, chế độ tập, loop, tua, tempo, render WPF và việc sân khấu chạy trên đồng hồ khung hình dùng chung. Chỉ xác nhận được phím đàn vật lý phát sự kiện khi nhấn một phím MIDI thực tế.
Bộ kiểm thử shader có hai mục riêng: VerifyShaderPipeline (không cần WPF layout) kiểm tra toán sRGB/ACES/GGX, jitter tất định, chữ ký cache bake (slider không liên quan không gây bake lại), bake nền phải opaque và trải sáng thật, cột phím đen phải tối hơn cột ngà, và tile overlay của một phím kêu phải che đúng phím rồi mờ ra; VerifyShadedStage bật/tắt ShadingQuality trong dock và khẳng định stage thật sự đổi giữa bàn phím vector và bàn phím shader, đồng thời bake được tái sử dụng giữa các khung hình.
Engine GPU có một mục riêng, VerifyGpuStage (Diagnostics/VerificationSuite.Gpu.cs), và không đòi card đồ hoạ thật vì khung hình thử dựng trên WARP — có sẵn ở mọi runner CI. Mục này kiểm: cài đặt (bản cài mới mặc định RenderBackend = Gpu; tệp cũ ghi Software được nâng lên GPU khi nạp; tên engine hay tốc độ khung hình lạ rơi về mặc định; áp preset giữ nguyên engine, FPS và VSync vì chúng thuộc về máy); look (GpuLook mang đúng màu nốt, tỉ lệ bàn phím và giá trị các slider Note shimmer / Halo light pulses / Shooting stars / Landing glow, cùng lựa chọn Spotlights); cầu nối luồng (GpuStageFeed đưa cho luồng dựng hình bài đang chạy, các phím đang giữ và một đồng hồ ngoại suy không bao giờ quá 50 ms; hit và nốt live xếp hàng không mất, yêu cầu xoá chỉ được nhận đúng một lần); shader nhúng (đủ 12 entry point HLSL); một khung thật 640×360 dựng trên WARP rồi đọc ngược để đo (bàn phím phải sáng ở đáy, nổi hơn nền tối phía trên, và ảnh phải nhiều màu chứ không phải một mảng phẳng); các họ hiệu ứng (look mang đủ cài đặt ambient, vệt rơi, nhãn phím, chớp, hồ quang và cánh hoa; Galaxy, guide lanes và cánh hoa dựng ra hàng trăm hình sau nốt; atlas chữ 1024×768 có mực thật; và bầu trời của khung GPU sáng lên khi bật lớp thiên hà); cuối cùng là ghi hình (GpuRecordingTap: vòng dựng hình chạy một mình, không cửa sổ, không xem trước, vẫn giao đủ khung 320×180 có bàn phím trong đó).
Mã nguồn phát hành theo giấy phép MIT (xem LICENSE). Salamander Grand Piano V3 không được đóng gói; ứng dụng tải bộ mẫu theo yêu cầu theo CC BY 3.0 và ghi thông tin ghi công vào thư mục cài piano của người dùng.
Bản quyền thuộc về tác giả Yami và Neyu; Jin là người đóng góp cho dự án.











