progress.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>简谱渲染引擎 - 开发进度可视化</title>
  7. <style>
  8. :root {
  9. --primary: #6366f1;
  10. --primary-light: #818cf8;
  11. --success: #10b981;
  12. --warning: #f59e0b;
  13. --error: #ef4444;
  14. --bg: #0f172a;
  15. --bg-card: #1e293b;
  16. --bg-hover: #334155;
  17. --text: #f1f5f9;
  18. --text-muted: #94a3b8;
  19. --border: #334155;
  20. }
  21. * { box-sizing: border-box; margin: 0; padding: 0; }
  22. body {
  23. font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  24. background: var(--bg);
  25. color: var(--text);
  26. line-height: 1.6;
  27. min-height: 100vh;
  28. }
  29. .container {
  30. max-width: 1400px;
  31. margin: 0 auto;
  32. padding: 24px;
  33. }
  34. /* 头部 */
  35. header {
  36. text-align: center;
  37. padding: 40px 20px;
  38. background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%);
  39. border-radius: 16px;
  40. margin-bottom: 32px;
  41. position: relative;
  42. overflow: hidden;
  43. }
  44. header::before {
  45. content: '';
  46. position: absolute;
  47. top: 0;
  48. left: 0;
  49. right: 0;
  50. bottom: 0;
  51. background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
  52. opacity: 0.3;
  53. }
  54. header h1 {
  55. font-size: 2.5rem;
  56. font-weight: 700;
  57. margin-bottom: 12px;
  58. position: relative;
  59. }
  60. header p {
  61. font-size: 1.1rem;
  62. opacity: 0.9;
  63. position: relative;
  64. }
  65. .version-badge {
  66. display: inline-block;
  67. background: rgba(255,255,255,0.2);
  68. padding: 6px 16px;
  69. border-radius: 20px;
  70. font-size: 0.9rem;
  71. margin-top: 16px;
  72. position: relative;
  73. }
  74. /* 进度概览 */
  75. .progress-overview {
  76. display: grid;
  77. grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  78. gap: 16px;
  79. margin-bottom: 32px;
  80. }
  81. .progress-card {
  82. background: var(--bg-card);
  83. border-radius: 12px;
  84. padding: 20px;
  85. text-align: center;
  86. border: 1px solid var(--border);
  87. transition: transform 0.2s, box-shadow 0.2s;
  88. }
  89. .progress-card:hover {
  90. transform: translateY(-2px);
  91. box-shadow: 0 8px 24px rgba(0,0,0,0.3);
  92. }
  93. .progress-card .icon {
  94. font-size: 2rem;
  95. margin-bottom: 8px;
  96. }
  97. .progress-card .value {
  98. font-size: 2rem;
  99. font-weight: 700;
  100. color: var(--primary-light);
  101. }
  102. .progress-card .label {
  103. font-size: 0.85rem;
  104. color: var(--text-muted);
  105. margin-top: 4px;
  106. }
  107. .progress-card.success .value { color: var(--success); }
  108. .progress-card.warning .value { color: var(--warning); }
  109. /* 阶段进度 */
  110. .stages {
  111. background: var(--bg-card);
  112. border-radius: 16px;
  113. padding: 24px;
  114. margin-bottom: 32px;
  115. border: 1px solid var(--border);
  116. }
  117. .stages h2 {
  118. font-size: 1.3rem;
  119. margin-bottom: 20px;
  120. display: flex;
  121. align-items: center;
  122. gap: 8px;
  123. }
  124. .stage-list {
  125. display: flex;
  126. flex-direction: column;
  127. gap: 12px;
  128. }
  129. .stage-item {
  130. display: flex;
  131. align-items: center;
  132. gap: 16px;
  133. padding: 16px;
  134. background: var(--bg);
  135. border-radius: 10px;
  136. }
  137. .stage-status {
  138. width: 32px;
  139. height: 32px;
  140. border-radius: 50%;
  141. display: flex;
  142. align-items: center;
  143. justify-content: center;
  144. font-size: 1rem;
  145. flex-shrink: 0;
  146. }
  147. .stage-status.done { background: var(--success); }
  148. .stage-status.active { background: var(--primary); animation: pulse 2s infinite; }
  149. .stage-status.pending { background: var(--border); color: var(--text-muted); }
  150. @keyframes pulse {
  151. 0%, 100% { box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.4); }
  152. 50% { box-shadow: 0 0 0 8px rgba(99, 102, 241, 0); }
  153. }
  154. .stage-info {
  155. flex: 1;
  156. }
  157. .stage-info h3 {
  158. font-size: 1rem;
  159. margin-bottom: 4px;
  160. }
  161. .stage-info p {
  162. font-size: 0.85rem;
  163. color: var(--text-muted);
  164. }
  165. .stage-progress {
  166. width: 120px;
  167. text-align: right;
  168. }
  169. .stage-progress .bar {
  170. height: 6px;
  171. background: var(--border);
  172. border-radius: 3px;
  173. overflow: hidden;
  174. margin-bottom: 4px;
  175. }
  176. .stage-progress .bar-fill {
  177. height: 100%;
  178. background: linear-gradient(90deg, var(--primary), var(--success));
  179. transition: width 0.5s ease;
  180. }
  181. .stage-progress .text {
  182. font-size: 0.8rem;
  183. color: var(--text-muted);
  184. }
  185. /* 演示区域 */
  186. .demo-section {
  187. background: var(--bg-card);
  188. border-radius: 16px;
  189. padding: 24px;
  190. margin-bottom: 32px;
  191. border: 1px solid var(--border);
  192. }
  193. .demo-section h2 {
  194. font-size: 1.3rem;
  195. margin-bottom: 20px;
  196. display: flex;
  197. align-items: center;
  198. gap: 8px;
  199. }
  200. .demo-tabs {
  201. display: flex;
  202. gap: 8px;
  203. margin-bottom: 20px;
  204. flex-wrap: wrap;
  205. }
  206. .demo-tab {
  207. padding: 10px 20px;
  208. background: var(--bg);
  209. border: 1px solid var(--border);
  210. border-radius: 8px;
  211. cursor: pointer;
  212. font-size: 0.9rem;
  213. color: var(--text-muted);
  214. transition: all 0.2s;
  215. }
  216. .demo-tab:hover {
  217. background: var(--bg-hover);
  218. color: var(--text);
  219. }
  220. .demo-tab.active {
  221. background: var(--primary);
  222. border-color: var(--primary);
  223. color: white;
  224. }
  225. .demo-content {
  226. background: var(--bg);
  227. border-radius: 12px;
  228. padding: 24px;
  229. min-height: 300px;
  230. }
  231. /* SVG演示 */
  232. .svg-demo {
  233. display: flex;
  234. flex-wrap: wrap;
  235. gap: 24px;
  236. justify-content: center;
  237. }
  238. .note-demo {
  239. background: white;
  240. border-radius: 8px;
  241. padding: 20px;
  242. text-align: center;
  243. }
  244. .note-demo svg {
  245. display: block;
  246. margin: 0 auto 12px;
  247. }
  248. .note-demo .label {
  249. font-size: 0.8rem;
  250. color: #64748b;
  251. }
  252. /* 测试统计 */
  253. .test-stats {
  254. display: grid;
  255. grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  256. gap: 16px;
  257. }
  258. .test-stat {
  259. background: var(--bg);
  260. border-radius: 10px;
  261. padding: 16px;
  262. display: flex;
  263. align-items: center;
  264. gap: 12px;
  265. }
  266. .test-stat .icon {
  267. width: 40px;
  268. height: 40px;
  269. border-radius: 8px;
  270. display: flex;
  271. align-items: center;
  272. justify-content: center;
  273. font-size: 1.2rem;
  274. }
  275. .test-stat .icon.success { background: rgba(16, 185, 129, 0.2); }
  276. .test-stat .icon.info { background: rgba(99, 102, 241, 0.2); }
  277. .test-stat .info h4 {
  278. font-size: 1.2rem;
  279. font-weight: 600;
  280. }
  281. .test-stat .info p {
  282. font-size: 0.8rem;
  283. color: var(--text-muted);
  284. }
  285. /* 模块列表 */
  286. .modules-grid {
  287. display: grid;
  288. grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  289. gap: 16px;
  290. }
  291. .module-card {
  292. background: var(--bg);
  293. border-radius: 10px;
  294. padding: 16px;
  295. border: 1px solid var(--border);
  296. }
  297. .module-card h4 {
  298. font-size: 0.95rem;
  299. margin-bottom: 8px;
  300. display: flex;
  301. align-items: center;
  302. gap: 8px;
  303. }
  304. .module-card .status-badge {
  305. padding: 2px 8px;
  306. border-radius: 4px;
  307. font-size: 0.7rem;
  308. font-weight: 500;
  309. }
  310. .status-badge.done { background: rgba(16, 185, 129, 0.2); color: var(--success); }
  311. .status-badge.wip { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
  312. .status-badge.pending { background: rgba(148, 163, 184, 0.2); color: var(--text-muted); }
  313. .module-card p {
  314. font-size: 0.8rem;
  315. color: var(--text-muted);
  316. margin-bottom: 8px;
  317. }
  318. .module-card .tests {
  319. font-size: 0.75rem;
  320. color: var(--success);
  321. }
  322. /* 底部 */
  323. footer {
  324. text-align: center;
  325. padding: 24px;
  326. color: var(--text-muted);
  327. font-size: 0.85rem;
  328. }
  329. footer a {
  330. color: var(--primary-light);
  331. text-decoration: none;
  332. }
  333. </style>
  334. </head>
  335. <body>
  336. <div class="container">
  337. <header>
  338. <h1>🎵 简谱渲染引擎</h1>
  339. <p>JianpuRenderer - 高性能简谱渲染解决方案</p>
  340. <div class="version-badge">开发版本 v0.3.0 | 阶段3进行中</div>
  341. </header>
  342. <!-- 进度概览 -->
  343. <div class="progress-overview">
  344. <div class="progress-card success">
  345. <div class="icon">✅</div>
  346. <div class="value">193</div>
  347. <div class="label">测试用例通过</div>
  348. </div>
  349. <div class="progress-card">
  350. <div class="icon">📦</div>
  351. <div class="value">12</div>
  352. <div class="label">已实现模块</div>
  353. </div>
  354. <div class="progress-card">
  355. <div class="icon">📄</div>
  356. <div class="value">~3500</div>
  357. <div class="label">代码行数</div>
  358. </div>
  359. <div class="progress-card warning">
  360. <div class="icon">🎯</div>
  361. <div class="value">45%</div>
  362. <div class="label">总体进度</div>
  363. </div>
  364. </div>
  365. <!-- 阶段进度 -->
  366. <div class="stages">
  367. <h2>📊 开发阶段进度</h2>
  368. <div class="stage-list">
  369. <div class="stage-item">
  370. <div class="stage-status done">✓</div>
  371. <div class="stage-info">
  372. <h3>阶段0:准备工作</h3>
  373. <p>项目结构、数据模型、测试环境</p>
  374. </div>
  375. <div class="stage-progress">
  376. <div class="bar"><div class="bar-fill" style="width: 100%"></div></div>
  377. <div class="text">100% 完成</div>
  378. </div>
  379. </div>
  380. <div class="stage-item">
  381. <div class="stage-status done">✓</div>
  382. <div class="stage-info">
  383. <h3>阶段0.5:规范文档编写</h3>
  384. <p>MusicXML映射、渲染规范、VexFlow兼容</p>
  385. </div>
  386. <div class="stage-progress">
  387. <div class="bar"><div class="bar-fill" style="width: 100%"></div></div>
  388. <div class="text">100% 完成</div>
  389. </div>
  390. </div>
  391. <div class="stage-item">
  392. <div class="stage-status done">✓</div>
  393. <div class="stage-info">
  394. <h3>阶段1:核心解析器</h3>
  395. <p>Divisions处理、OSMD数据解析、时间计算</p>
  396. </div>
  397. <div class="stage-progress">
  398. <div class="bar"><div class="bar-fill" style="width: 100%"></div></div>
  399. <div class="text">100% 完成</div>
  400. </div>
  401. </div>
  402. <div class="stage-item">
  403. <div class="stage-status done">✓</div>
  404. <div class="stage-info">
  405. <h3>阶段2:布局引擎</h3>
  406. <p>小节布局、多声部对齐、行布局、Y坐标计算</p>
  407. </div>
  408. <div class="stage-progress">
  409. <div class="bar"><div class="bar-fill" style="width: 100%"></div></div>
  410. <div class="text">100% 完成</div>
  411. </div>
  412. </div>
  413. <div class="stage-item">
  414. <div class="stage-status active">▶</div>
  415. <div class="stage-info">
  416. <h3>阶段3:绘制引擎</h3>
  417. <p>音符绘制、线条绘制、歌词绘制、修饰符绘制</p>
  418. </div>
  419. <div class="stage-progress">
  420. <div class="bar"><div class="bar-fill" style="width: 25%"></div></div>
  421. <div class="text">25% 进行中</div>
  422. </div>
  423. </div>
  424. <div class="stage-item">
  425. <div class="stage-status pending">○</div>
  426. <div class="stage-info">
  427. <h3>阶段4:兼容层实现</h3>
  428. <p>OSMD兼容适配器、业务集成</p>
  429. </div>
  430. <div class="stage-progress">
  431. <div class="bar"><div class="bar-fill" style="width: 0%"></div></div>
  432. <div class="text">待开始</div>
  433. </div>
  434. </div>
  435. <div class="stage-item">
  436. <div class="stage-status pending">○</div>
  437. <div class="stage-info">
  438. <h3>阶段5:测试与优化</h3>
  439. <p>完整性测试、兼容性测试、性能优化</p>
  440. </div>
  441. <div class="stage-progress">
  442. <div class="bar"><div class="bar-fill" style="width: 0%"></div></div>
  443. <div class="text">待开始</div>
  444. </div>
  445. </div>
  446. </div>
  447. </div>
  448. <!-- 演示区域 -->
  449. <div class="demo-section">
  450. <h2>🎨 渲染演示</h2>
  451. <div class="demo-tabs">
  452. <button class="demo-tab active" onclick="showDemo('notes')">音符绘制</button>
  453. <button class="demo-tab" onclick="showDemo('octaves')">高低音点</button>
  454. <button class="demo-tab" onclick="showDemo('accidentals')">升降号</button>
  455. <button class="demo-tab" onclick="showDemo('durations')">附点音符</button>
  456. <button class="demo-tab" onclick="showDemo('complex')">复杂音符</button>
  457. </div>
  458. <div class="demo-content" id="demo-content">
  459. <!-- 动态内容 -->
  460. </div>
  461. </div>
  462. <!-- 测试统计 -->
  463. <div class="demo-section">
  464. <h2>🧪 测试覆盖</h2>
  465. <div class="test-stats">
  466. <div class="test-stat">
  467. <div class="icon success">✓</div>
  468. <div class="info">
  469. <h4>39 通过</h4>
  470. <p>DivisionsHandler.test.ts</p>
  471. </div>
  472. </div>
  473. <div class="test-stat">
  474. <div class="icon success">✓</div>
  475. <div class="info">
  476. <h4>30 通过</h4>
  477. <p>OSMDDataParser.test.ts</p>
  478. </div>
  479. </div>
  480. <div class="test-stat">
  481. <div class="icon success">✓</div>
  482. <div class="info">
  483. <h4>23 通过</h4>
  484. <p>TimeCalculator.test.ts</p>
  485. </div>
  486. </div>
  487. <div class="test-stat">
  488. <div class="icon success">✓</div>
  489. <div class="info">
  490. <h4>31 通过</h4>
  491. <p>MeasureLayoutEngine.test.ts</p>
  492. </div>
  493. </div>
  494. <div class="test-stat">
  495. <div class="icon success">✓</div>
  496. <div class="info">
  497. <h4>31 通过</h4>
  498. <p>MultiVoiceAligner.test.ts</p>
  499. </div>
  500. </div>
  501. <div class="test-stat">
  502. <div class="icon success">✓</div>
  503. <div class="info">
  504. <h4>40 通过</h4>
  505. <p>SystemLayoutEngine.test.ts</p>
  506. </div>
  507. </div>
  508. <div class="test-stat">
  509. <div class="icon success">✓</div>
  510. <div class="info">
  511. <h4>35 通过</h4>
  512. <p>NotePositionCalculator.test.ts</p>
  513. </div>
  514. </div>
  515. <div class="test-stat">
  516. <div class="icon success">✓</div>
  517. <div class="info">
  518. <h4>49 通过</h4>
  519. <p>NoteDrawer.test.ts</p>
  520. </div>
  521. </div>
  522. </div>
  523. </div>
  524. <!-- 模块列表 -->
  525. <div class="demo-section">
  526. <h2>📦 已实现模块</h2>
  527. <div class="modules-grid">
  528. <div class="module-card">
  529. <h4>DivisionsHandler <span class="status-badge done">完成</span></h4>
  530. <p>MusicXML divisions处理,时值转换</p>
  531. <div class="tests">✓ 39个测试通过</div>
  532. </div>
  533. <div class="module-card">
  534. <h4>OSMDDataParser <span class="status-badge done">完成</span></h4>
  535. <p>OSMD数据解析,音符提取</p>
  536. <div class="tests">✓ 30个测试通过</div>
  537. </div>
  538. <div class="module-card">
  539. <h4>TimeCalculator <span class="status-badge done">完成</span></h4>
  540. <p>时间计算,BPM处理,弱起检测</p>
  541. <div class="tests">✓ 23个测试通过</div>
  542. </div>
  543. <div class="module-card">
  544. <h4>MeasureLayoutEngine <span class="status-badge done">完成</span></h4>
  545. <p>小节布局,固定时间比例算法</p>
  546. <div class="tests">✓ 31个测试通过</div>
  547. </div>
  548. <div class="module-card">
  549. <h4>MultiVoiceAligner <span class="status-badge done">完成</span></h4>
  550. <p>多声部对齐,时间戳统一</p>
  551. <div class="tests">✓ 31个测试通过</div>
  552. </div>
  553. <div class="module-card">
  554. <h4>SystemLayoutEngine <span class="status-badge done">完成</span></h4>
  555. <p>行布局,自动换行,Y坐标</p>
  556. <div class="tests">✓ 40个测试通过</div>
  557. </div>
  558. <div class="module-card">
  559. <h4>NotePositionCalculator <span class="status-badge done">完成</span></h4>
  560. <p>音符Y坐标,声部间距</p>
  561. <div class="tests">✓ 35个测试通过</div>
  562. </div>
  563. <div class="module-card">
  564. <h4>NoteDrawer <span class="status-badge done">完成</span></h4>
  565. <p>音符SVG绘制,高低音点,附点,升降号</p>
  566. <div class="tests">✓ 49个测试通过</div>
  567. </div>
  568. <div class="module-card">
  569. <h4>LineDrawer <span class="status-badge wip">开发中</span></h4>
  570. <p>增时线、减时线、小节线绘制</p>
  571. <div class="tests">待实现</div>
  572. </div>
  573. <div class="module-card">
  574. <h4>LyricDrawer <span class="status-badge pending">待开始</span></h4>
  575. <p>歌词绘制,多遍歌词</p>
  576. <div class="tests">待实现</div>
  577. </div>
  578. <div class="module-card">
  579. <h4>ModifierDrawer <span class="status-badge pending">待开始</span></h4>
  580. <p>装饰音、连音符绘制</p>
  581. <div class="tests">待实现</div>
  582. </div>
  583. <div class="module-card">
  584. <h4>OSMDCompatibilityAdapter <span class="status-badge pending">待开始</span></h4>
  585. <p>OSMD兼容层,state.times生成</p>
  586. <div class="tests">待实现</div>
  587. </div>
  588. </div>
  589. </div>
  590. <footer>
  591. <p>简谱渲染引擎 | 开发中 | <a href="compare.html">对比测试</a> | <a href="collect-baseline.html">基准收集</a></p>
  592. </footer>
  593. </div>
  594. <script>
  595. // SVG命名空间
  596. const SVG_NS = 'http://www.w3.org/2000/svg';
  597. // 创建SVG容器
  598. function createSVG(width, height) {
  599. const svg = document.createElementNS(SVG_NS, 'svg');
  600. svg.setAttribute('width', width);
  601. svg.setAttribute('height', height);
  602. svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
  603. return svg;
  604. }
  605. // 绘制音符
  606. function drawNote(x, y, pitch, options = {}) {
  607. const { octave = 0, dots = 0, accidental = null, fontSize = 24 } = options;
  608. const g = document.createElementNS(SVG_NS, 'g');
  609. g.setAttribute('transform', `translate(${x}, ${y})`);
  610. // 升降号
  611. if (accidental) {
  612. const accText = document.createElementNS(SVG_NS, 'text');
  613. accText.setAttribute('x', '-12');
  614. accText.setAttribute('y', '-8');
  615. accText.setAttribute('font-size', '12');
  616. accText.setAttribute('fill', '#333');
  617. accText.setAttribute('text-anchor', 'end');
  618. accText.textContent = accidental === 'sharp' ? '#' : accidental === 'flat' ? '♭' : '♮';
  619. g.appendChild(accText);
  620. }
  621. // 高音点
  622. if (octave > 0) {
  623. for (let i = 0; i < octave; i++) {
  624. const dot = document.createElementNS(SVG_NS, 'circle');
  625. dot.setAttribute('cx', '0');
  626. dot.setAttribute('cy', String(-fontSize/2 - 6 - i * 5));
  627. dot.setAttribute('r', '2.5');
  628. dot.setAttribute('fill', '#333');
  629. g.appendChild(dot);
  630. }
  631. }
  632. // 音符数字
  633. const text = document.createElementNS(SVG_NS, 'text');
  634. text.setAttribute('x', '0');
  635. text.setAttribute('y', '0');
  636. text.setAttribute('font-size', String(fontSize));
  637. text.setAttribute('font-family', 'Arial, sans-serif');
  638. text.setAttribute('fill', '#333');
  639. text.setAttribute('text-anchor', 'middle');
  640. text.setAttribute('dominant-baseline', 'central');
  641. text.textContent = String(pitch);
  642. g.appendChild(text);
  643. // 低音点
  644. if (octave < 0) {
  645. for (let i = 0; i < Math.abs(octave); i++) {
  646. const dot = document.createElementNS(SVG_NS, 'circle');
  647. dot.setAttribute('cx', '0');
  648. dot.setAttribute('cy', String(fontSize/2 + 6 + i * 5));
  649. dot.setAttribute('r', '2.5');
  650. dot.setAttribute('fill', '#333');
  651. g.appendChild(dot);
  652. }
  653. }
  654. // 附点
  655. if (dots > 0) {
  656. for (let i = 0; i < dots; i++) {
  657. const dot = document.createElementNS(SVG_NS, 'circle');
  658. dot.setAttribute('cx', String(fontSize * 0.4 + 4 + i * 6));
  659. dot.setAttribute('cy', '0');
  660. dot.setAttribute('r', '2');
  661. dot.setAttribute('fill', '#333');
  662. g.appendChild(dot);
  663. }
  664. }
  665. return g;
  666. }
  667. // 显示演示
  668. function showDemo(type) {
  669. // 更新标签状态
  670. document.querySelectorAll('.demo-tab').forEach(tab => tab.classList.remove('active'));
  671. event.target.classList.add('active');
  672. const container = document.getElementById('demo-content');
  673. container.innerHTML = '';
  674. const demos = {
  675. notes: [
  676. { pitch: 1, label: 'Do (1)' },
  677. { pitch: 2, label: 'Re (2)' },
  678. { pitch: 3, label: 'Mi (3)' },
  679. { pitch: 4, label: 'Fa (4)' },
  680. { pitch: 5, label: 'Sol (5)' },
  681. { pitch: 6, label: 'La (6)' },
  682. { pitch: 7, label: 'Si (7)' },
  683. { pitch: 0, label: '休止符 (0)' },
  684. ],
  685. octaves: [
  686. { pitch: 5, octave: 2, label: '高两个八度' },
  687. { pitch: 5, octave: 1, label: '高一个八度' },
  688. { pitch: 5, octave: 0, label: '中音' },
  689. { pitch: 5, octave: -1, label: '低一个八度' },
  690. { pitch: 5, octave: -2, label: '低两个八度' },
  691. ],
  692. accidentals: [
  693. { pitch: 4, accidental: 'sharp', label: '升号 #4' },
  694. { pitch: 7, accidental: 'flat', label: '降号 ♭7' },
  695. { pitch: 3, accidental: 'natural', label: '还原号 ♮3' },
  696. ],
  697. durations: [
  698. { pitch: 5, dots: 0, label: '四分音符' },
  699. { pitch: 5, dots: 1, label: '附点四分' },
  700. { pitch: 5, dots: 2, label: '双附点四分' },
  701. ],
  702. complex: [
  703. { pitch: 5, octave: 1, dots: 1, accidental: 'sharp', label: '升5·(高)' },
  704. { pitch: 3, octave: -1, dots: 2, accidental: 'flat', label: '降3··(低)' },
  705. { pitch: 1, octave: 2, dots: 1, label: '高高Do·' },
  706. { pitch: 7, octave: -2, accidental: 'sharp', label: '升Si(低低)' },
  707. ],
  708. };
  709. const items = demos[type] || demos.notes;
  710. const wrapper = document.createElement('div');
  711. wrapper.className = 'svg-demo';
  712. items.forEach(item => {
  713. const noteDemo = document.createElement('div');
  714. noteDemo.className = 'note-demo';
  715. const svg = createSVG(80, 80);
  716. const note = drawNote(40, 40, item.pitch, {
  717. octave: item.octave || 0,
  718. dots: item.dots || 0,
  719. accidental: item.accidental || null,
  720. });
  721. svg.appendChild(note);
  722. const label = document.createElement('div');
  723. label.className = 'label';
  724. label.textContent = item.label;
  725. noteDemo.appendChild(svg);
  726. noteDemo.appendChild(label);
  727. wrapper.appendChild(noteDemo);
  728. });
  729. container.appendChild(wrapper);
  730. }
  731. // 初始化
  732. document.addEventListener('DOMContentLoaded', () => {
  733. showDemo('notes');
  734. });
  735. </script>
  736. </body>
  737. </html>