customMusicScore.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. import { ref } from "vue";
  2. import state, { customData } from "../state"
  3. import { getQuery } from "/src/utils/queryString";
  4. import { setGlobalData } from "/src/utils";
  5. const query: any = getQuery();
  6. interface IItem {
  7. id?: string
  8. y?: number
  9. isLast?: boolean
  10. childIndex?: number[]
  11. }
  12. interface IItemList {
  13. parts: string[],
  14. tieId?: string[],
  15. staveSection?: IItem[],
  16. vfmodifiers?: IItem[],
  17. voltas?: number
  18. vfcurve?: IItem[]
  19. stavenote?: IItem[]
  20. }
  21. interface IMusicList {
  22. [_key: string]: IItemList[]
  23. }
  24. const container = ref();
  25. /** 曲谱配置: 重叠 */
  26. export const resetGivenFormate = () => {
  27. interface IItem {
  28. id?: string
  29. y?: number
  30. isLast?: boolean
  31. childIndex?: number[]
  32. }
  33. interface IItemList {
  34. parts: string[],
  35. tieId?: string[],
  36. staveSection?: IItem[],
  37. vfmodifiers?: IItem[],
  38. voltas?: number
  39. vfcurve?: IItem[]
  40. stavenote?: IItem[]
  41. }
  42. interface IMusicList {
  43. [_key: string]: IItemList[]
  44. }
  45. const musicList: IMusicList = {
  46. '12200': [
  47. {parts: ['0', '1'], tieId: ['1483']},
  48. {parts: ['2'], tieId: ['1463']},
  49. {parts: ['10'], tieId: ['1246']},
  50. {parts: ['11'], tieId: ['2455']},
  51. {parts: ['13'], tieId: ['1488', '1688']},
  52. {parts: ['14', '15'], tieId: ['1272']},
  53. {parts: ['16'], tieId: ['1264', '1368'], staveSection: [{id: 'section-0', y: -10}]},
  54. ],
  55. '12420': [
  56. {parts: ['0'], tieId: ['1298', '1405', '1998', '2598', '3229', '2731', '2617']}
  57. ],
  58. '7729': [
  59. {parts: ['3'], tieId: ['1498', '1660']}
  60. ],
  61. '7439': [
  62. {parts: ['23'], vfmodifiers: [{id: 'modifiers-130', y: -18, isLast: true}]}
  63. ],
  64. '12711': [
  65. { parts: ['0'], voltas: -12},
  66. { parts: ['4'],voltas: -8},
  67. ],
  68. '3581': [
  69. { parts: ['0'], voltas: -8},
  70. ],
  71. '6244': [
  72. { parts: ['15'], stavenote: [{id: 'vf-auto1608', y: -15}]},
  73. ],
  74. '7473': [
  75. { parts: ['0'], voltas: -8},
  76. ]
  77. }
  78. const tieList = musicList[state.cbsExamSongId as string]
  79. if (tieList) {
  80. const partIndex = query["part-index"] || '0'
  81. const tie = tieList.find((item) => item.parts.includes(partIndex))
  82. if (!tie) return
  83. // 延音线和连线重叠
  84. if (tie.tieId && tie.tieId.length) {
  85. for(let tieIndex = 0; tieIndex < tie.tieId.length; tieIndex++){
  86. const vftie: any = document.querySelector(`#vf-auto${tie.tieId[tieIndex]}-tie`)
  87. const vfcurve = vftie?.parentNode?.parentNode?.querySelectorAll('.vf-curve')
  88. if (vfcurve && vfcurve.length){
  89. for(let i = 0; i < vfcurve.length; i++){
  90. const result = collisionDetection(vftie, vfcurve[i])
  91. if (result.isCollision){
  92. vfcurve[i].style.transform = `translateY(-8px)`;
  93. break;
  94. }
  95. }
  96. }
  97. }
  98. }
  99. // 小节数字
  100. if (tie.staveSection && tie.staveSection.length) {
  101. const sectionList = document.querySelectorAll('.vf-StaveSection')
  102. sectionList.forEach((node, index) => {
  103. node.classList.add(`section-${index}`)
  104. })
  105. for(let i = 0; i < tie.staveSection.length; i++){
  106. const item: any = document.querySelector( '.' + tie.staveSection[i].id)
  107. if (item){
  108. item.style.transform = `translateY(${tie.staveSection[i].y}px)`;
  109. }
  110. }
  111. }
  112. // modifiers 里面的符号
  113. if(tie.vfmodifiers && tie.vfmodifiers.length){
  114. const modifierList = document.querySelectorAll('.vf-modifiers')
  115. modifierList.forEach((node, index) => {
  116. node.classList.add(`modifiers-${index}`)
  117. })
  118. for(let i = 0; i < tie.vfmodifiers.length; i++){
  119. const modifier = tie.vfmodifiers[i]
  120. const item: SVGAElement = document.querySelector( '.' + modifier.id)!
  121. if (item){
  122. if (modifier.isLast){
  123. const lastEle: any = Array.from(item.childNodes).at(-1)
  124. if (lastEle){
  125. lastEle.style.transform = `translateY(${modifier.y}px)`;
  126. }
  127. }
  128. }
  129. }
  130. }
  131. // 房子
  132. if (tie.voltas){
  133. const modifierList = document.querySelectorAll('.vf-Volta') as unknown as HTMLElement[]
  134. modifierList.forEach((node, index) => {
  135. node.style.transform = `translateY(${tie.voltas}px)`;
  136. })
  137. }
  138. // 单个音符
  139. if (tie.stavenote && tie.stavenote.length) {
  140. for(let i = 0; i < tie.stavenote.length; i++){
  141. const item = tie.stavenote[i]
  142. const ele = document.querySelector('#' + item.id)! as unknown as HTMLElement
  143. ele && (ele.style.transform = `translateY(${item.y}px)`)
  144. }
  145. }
  146. }
  147. };
  148. // 谱面优化
  149. export const resetFormate = () => {
  150. container.value = document.getElementById('scrollContainer')
  151. // if (state.extStyleConfigJson || !container.value) return;
  152. if (!container.value) return;
  153. const stafflines: SVGAElement[] = Array.from((container.value as HTMLElement).querySelectorAll(".staffline"));
  154. const baseStep = 4; // 两个元素相间,的间距
  155. const musicalDistance = 28; // 音阶与第一条线谱的间距,默认设置为28
  156. for (let i = 0, len = stafflines.length; i < len; i++) {
  157. const staffline = stafflines[i];
  158. const stafflineBox = staffline.getBBox();
  159. const stafflineCenter = stafflineBox.y + stafflineBox.height / 2;
  160. const vfmeasures: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure"));
  161. const vfcurve: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-curve"));
  162. const vfvoices: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-voices"));
  163. const vfbeams: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-beams"));
  164. const vfties: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-ties"));
  165. const vflines: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-line"));
  166. const texts: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave text"));
  167. const rects: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave rect[fill=none]"));
  168. const staveSection: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-staveSection"));
  169. const paths: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave path"));
  170. const dotModifiers: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-stopDot"));
  171. // 获取第一个线谱的y轴坐标
  172. const firstLinePathY = paths[0]?.getBBox().y || 0
  173. // 反复标记 和 小节碰撞
  174. const repetWord = ["To Coda", "D.S. al Coda", "Coda"];
  175. texts
  176. .filter((n) => repetWord.includes(n.textContent || ""))
  177. .forEach((t) => {
  178. vfbeams.forEach((curve) => {
  179. const result = collisionDetection(t, curve);
  180. const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement;
  181. if (result.isCollision) {
  182. const shift_y = Number(t.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
  183. t.setAttribute("y", shift_y);
  184. // console.log('音阶间距',shift_y)
  185. if (prePath && prePath.getAttribute("stroke-width") === "0.3" && prePath.getAttribute("stroke") === "none" && (prePath.getAttribute("d")?.length || 0) > 3000) {
  186. prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`;
  187. }
  188. }
  189. });
  190. vfvoices.forEach((curve) => {
  191. const result = collisionDetection(t, curve);
  192. const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement;
  193. if (result.isCollision) {
  194. const shift_y = Number(t.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
  195. t.setAttribute("y", shift_y);
  196. // console.log('音阶间距',shift_y)
  197. if (prePath && prePath.getAttribute("stroke-width") === "0.3" && prePath.getAttribute("stroke") === "none" && (prePath.getAttribute("d")?.length || 0) > 3000) {
  198. prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`;
  199. }
  200. }
  201. });
  202. });
  203. // 文字方框和飞线碰撞
  204. staveSection.forEach((t) => {
  205. let shift_y = 0;
  206. [...vfcurve, ...vfties, ...vfvoices].forEach((curve) => {
  207. const result = collisionDetection(t, curve);
  208. if (result.isCollision) {
  209. shift_y = Math.min(shift_y, result.t2 - result.b1 - baseStep);
  210. }
  211. });
  212. t.style.transform = `translateY(${shift_y}px)`;
  213. });
  214. // 文字和小节碰撞
  215. let vftexts = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y < stafflineCenter);
  216. for (let i = 0; i < vftexts.length; i++) {
  217. const _text = vftexts[i];
  218. for (let j = 0; j < vftexts.length; j++) {
  219. if (_text.parentNode === vftexts[j].parentNode) continue;
  220. const result = collisionDetection(_text as SVGAElement, vftexts[j] as SVGAElement);
  221. if (result.isCollision) {
  222. if (_text.textContent === vftexts[j].textContent) {
  223. vftexts[j].parentNode?.removeChild(vftexts[j]);
  224. continue;
  225. }
  226. }
  227. }
  228. }
  229. vftexts = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y < stafflineCenter);
  230. let maxY = 0;
  231. let _vftexts: SVGAElement[] = [];
  232. vftexts.forEach((vftext: any) => {
  233. const textBox = vftext.getBBox();
  234. if (textBox.y < stafflineCenter) {
  235. maxY = Math.max(maxY, textBox.y + textBox.height);
  236. //console.log('音阶间距',textBox.y, textBox.height)
  237. _vftexts.push(vftext as SVGAElement);
  238. }
  239. });
  240. if (maxY !== 0 && _vftexts.length > 1) {
  241. _vftexts.forEach((vftext) => {
  242. vftext.setAttribute("y", maxY + "");
  243. //console.log('音阶间距',maxY)
  244. });
  245. }
  246. vftexts.forEach((vftext) => {
  247. [...vfcurve, ...vfmeasures, ...vflines].forEach((vfmeasure) => {
  248. let result = collisionDetection(vftext as SVGAElement, vfmeasure);
  249. if (result.isCollision && result.b1 < result.b2 && result.t1 < result.b2 - (result.b2 - result.t2) / 2) {
  250. const shift_y = Number(vftext.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
  251. vftext.setAttribute("y", shift_y);
  252. //console.log('音阶间距',shift_y)
  253. }
  254. });
  255. });
  256. vftexts.forEach((vftext) => {
  257. vftexts.forEach((text) => {
  258. if (vftext.parentNode !== text.parentNode && !["marcato", "legato"].includes(vftext.textContent as string)) {
  259. if (["marcato", "legato"].includes(text.textContent as string)) {
  260. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30);
  261. if (result.isCollision) {
  262. const textBBox = (vftext as SVGAElement).getBBox();
  263. text.setAttribute("x", textBBox.x + textBBox.width + 5 + "");
  264. text.setAttribute("y", textBBox.y + textBBox.height - 5 + "");
  265. //console.log('音阶间距',textBBox.y + textBBox.height - 5 + "")
  266. }
  267. } else {
  268. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement);
  269. if (result.isCollision) {
  270. const _y = Number(vftext.getAttribute("y"));
  271. const shift_y = result.b2 - result.t2 < 24 ? 24 : result.b2 - result.t2;
  272. text.setAttribute("y", _y - shift_y - 0.5 + "");
  273. //console.log('音阶间距',_y - shift_y - 0.5 + "")
  274. }
  275. }
  276. }
  277. });
  278. });
  279. // 修改音阶和线谱的间距
  280. const clefList = ['C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb', 'D#', 'A#', 'E#']
  281. const btransList = ['Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb']
  282. const jtrsnsList = ['F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'B#']
  283. vftexts.forEach((label: any) => {
  284. const labelText = label.textContent as string
  285. if (clefList.includes(labelText)){
  286. const _y = Number(label.getAttribute("y"))
  287. const endY = firstLinePathY ? firstLinePathY - musicalDistance : _y
  288. label.setAttribute("y", endY)
  289. }
  290. if (btransList.includes(labelText)) {
  291. label.textContent = labelText.replace('b','♭')
  292. }
  293. if (jtrsnsList.includes(labelText)) {
  294. label.textContent = labelText.replace('#','♯')
  295. }
  296. });
  297. dotModifiers.forEach((group: any) => {
  298. if (state.musicRenderType === 'fixedTone') {
  299. group.setAttribute('transform', 'translate(3,-12)')
  300. } else {
  301. group.setAttribute('transform', 'translate(3,-7)')
  302. }
  303. });
  304. const vftextBottom = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y > stafflineCenter);
  305. const vflineBottom = Array.from(staffline.querySelectorAll(".vf-line")).filter((n: any) => n.getBBox().y > stafflineCenter);
  306. // 去重
  307. for (let i = 0; i < vftextBottom.length; i++) {
  308. const _text = vftextBottom[i];
  309. for (let j = 0; j < vftextBottom.length; j++) {
  310. if (_text.parentNode === vftextBottom[j].parentNode) continue;
  311. const result = collisionDetection(_text as SVGAElement, vftextBottom[j] as SVGAElement);
  312. if (result.isCollision) {
  313. if (_text.textContent === vftextBottom[j].textContent) {
  314. vftextBottom[j].parentNode?.removeChild(vftextBottom[j]);
  315. continue;
  316. }
  317. }
  318. }
  319. }
  320. // 1,2线谱底部文字重叠问题
  321. vftextBottom.forEach((vftext) => {
  322. [...vfmeasures].forEach((n) => {
  323. let result = collisionDetection(vftext as SVGAElement, n);
  324. if (result.isCollision) {
  325. vftext.setAttribute("y", result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute("y"))) + "");
  326. //console.log('音阶间距', result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute("y"))) + "")
  327. }
  328. });
  329. });
  330. // 如果渐弱渐强有平行的文字
  331. vflineBottom.forEach((line) => {
  332. const texts: any[] = [];
  333. if (line.nextElementSibling?.classList.contains("vf-line")) {
  334. vftextBottom.forEach((text) => {
  335. let result = collisionDetection(line as SVGAElement, text as SVGAElement, 20, 20);
  336. if (result.isCollision) {
  337. texts.push({
  338. text: text as SVGAElement,
  339. result,
  340. });
  341. }
  342. });
  343. }
  344. if (texts.length === 1) {
  345. const result = texts[0].result;
  346. const text = texts[0].text;
  347. if (result.x2 + result.w2 < result.x1) {
  348. // 左
  349. if (Math.abs(result.y2 - result.y1) > 10) {
  350. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  351. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  352. }
  353. } else if (result.x2 > result.x1 + result.w1) {
  354. // 右
  355. if (Math.abs(result.y2 - result.y1) > 10) {
  356. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  357. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  358. }
  359. } else {
  360. if (Math.abs(result.x2 - result.x1) < Math.abs(result.x2 + result.w2 - result.x1 - result.w1)) {
  361. // console.log(text, '有交集', '靠左')
  362. text.setAttribute("x", result.x1 - result.w2 - 5 + "");
  363. if (Math.abs(result.y2 - result.y1) > 10) {
  364. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  365. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  366. }
  367. } else {
  368. // console.log(text, '有交集', '靠右')
  369. text.setAttribute("x", result.x1 + result.w1 + 5 + "");
  370. if (Math.abs(result.y2 - result.y1) > 10) {
  371. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  372. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  373. }
  374. }
  375. }
  376. } else if (texts.length === 2) {
  377. const result1 = texts[0].result;
  378. const text1 = texts[0].text;
  379. const result2 = texts[1].result;
  380. const text2 = texts[1].text;
  381. text1.setAttribute("x", result1.x1 - result1.w2 - 5 + "");
  382. if (Math.abs(result1.y2 - result1.y1) > 10) {
  383. text1.setAttribute("y", result1.y1 + result1.h2 / 2 + "");
  384. //console.log('音阶间距', result1.y1 + result1.h2 / 2 + "")
  385. }
  386. text2.setAttribute("x", result2.x1 + result2.w1 + 5 + "");
  387. if (Math.abs(result2.y2 - result2.y1) > 10) {
  388. text2.setAttribute("y", result2.y1 + result2.h2 / 2 + "");
  389. //console.log('音阶间距', result2.y1 + result2.h2 / 2 + "")
  390. }
  391. } else if (texts.length === 3) {
  392. // console.log(texts)
  393. }
  394. });
  395. vftextBottom.forEach((vftext) => {
  396. vftextBottom.forEach((text) => {
  397. if (vftext.parentNode !== text.parentNode && !["marcato", "legato", "cresc.", "Cantabile"].includes(vftext.textContent as string)) {
  398. if (["marcato", "legato", "cresc.", "Cantabile"].includes(text.textContent as string)) {
  399. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30);
  400. if (result.isCollision) {
  401. const textBBox = (vftext as SVGAElement).getBBox();
  402. text.setAttribute("x", textBBox.x + textBBox.width + 5 + "");
  403. text.setAttribute("y", textBBox.y + textBBox.height - 5 + "");
  404. //console.log('音阶间距', textBBox.y + textBBox.height - 5 + "")
  405. }
  406. } else {
  407. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement);
  408. if (result.isCollision) {
  409. text.setAttribute("y", result.y1 + result.h1 + result.h2 + "");
  410. //console.log('音阶间距', result.y1 + result.h1 + result.h2 + "")
  411. }
  412. }
  413. }
  414. });
  415. });
  416. // 同一行内,多个飞线碰撞
  417. for (let index = 0; index < vfcurve.length; index++) {
  418. let nextIndex = index + 1;
  419. const cur = vfcurve[index];
  420. let next = vfcurve[nextIndex];
  421. let checkDone = false;
  422. while (nextIndex <= vfcurve.length - 1 && !checkDone) {
  423. let result = collisionDetection(cur, next);
  424. if (result.isCollision) {
  425. checkDone = true;
  426. next.style.transform = `translateY(-12px)`;
  427. } else {
  428. nextIndex = nextIndex + 1;
  429. next = vfcurve[nextIndex];
  430. }
  431. }
  432. };
  433. }
  434. // setTimeout(() => this.resetGlobalText());
  435. };
  436. // 技巧文本
  437. const resetGlobalText = () => {
  438. const svg = container.value.querySelector("svg");
  439. if (!svg) return;
  440. const svgBBox = svg.getBBox();
  441. let vfstavetempo: SVGAElement[] = Array.from(container.value.querySelectorAll(".vf-stavetempo")).reduce((eles: SVGAElement[], value: any) => {
  442. if (eles.find((n) => n.outerHTML === value.outerHTML)) value?.parentNode?.removeChild(value);
  443. else eles.push(value);
  444. return eles;
  445. }, []);
  446. const staffline: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline"));
  447. const vfmeasures: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-measure"));
  448. const vftexts: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-text"));
  449. const vfcurves: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-curve"));
  450. vfstavetempo.forEach((child: SVGAElement) => {
  451. let _y = 0;
  452. [...vfmeasures, ...vftexts, ...vfcurves].forEach((ele) => {
  453. const result = collisionDetection(child as SVGAElement, ele);
  454. if (result.isCollision && (result.b1 < result.b2 || result.r1 > result.l2 || result.l1 < result.r2)) {
  455. _y = Math.min(_y, result.t2 - result.b1);
  456. }
  457. });
  458. if (_y !== 0) {
  459. child.style.transform = `translateY(${_y}px)`;
  460. }
  461. const childBBox = child.getBBox();
  462. const rightY = (childBBox.x + childBBox.width) * 0.7 - Number(svg.getAttribute("width"));
  463. if (rightY > 0) {
  464. [...staffline, ...vfstavetempo].forEach((tempo) => {
  465. if (child != tempo) {
  466. const result = collisionDetection(child as SVGAElement, tempo, Math.abs(rightY), Math.abs(_y));
  467. if (result.isCollision) {
  468. _y = result.t2 - result.b1;
  469. }
  470. }
  471. });
  472. child.style.transform = `translate(-${rightY / 0.7}px,${_y}px)`;
  473. }
  474. });
  475. if (svgBBox.y < 0) {
  476. svg.setAttribute("height", Number(svg.getAttribute("height")) - svgBBox.y + 10);
  477. }
  478. };
  479. // 碰撞检测
  480. const collisionDetection = (a: SVGAElement, b: SVGAElement, distance: number = 0, distance_y: number = 0) => {
  481. const abbox = a.getBBox();
  482. const bbbox = b.getBBox();
  483. let t1 = abbox.y - distance_y;
  484. let l1 = abbox.x - distance;
  485. let r1 = abbox.x + abbox.width + distance;
  486. let b1 = abbox.y + abbox.height + distance_y;
  487. let t2 = bbbox.y;
  488. let l2 = bbbox.x;
  489. let r2 = bbbox.x + bbbox.width;
  490. let b2 = bbbox.y + bbbox.height;
  491. if (b1 < t2 || l1 > r2 || t1 > b2 || r1 < l2) {
  492. // 表示没碰上
  493. return {
  494. isCollision: false,
  495. t1,
  496. l1,
  497. r1,
  498. b1,
  499. t2,
  500. l2,
  501. r2,
  502. b2,
  503. x1: abbox.x,
  504. y1: abbox.y,
  505. x2: bbbox.x,
  506. y2: bbbox.y,
  507. h1: abbox.height,
  508. h2: bbbox.height,
  509. w1: abbox.width,
  510. w2: bbbox.width,
  511. };
  512. } else {
  513. return {
  514. isCollision: true,
  515. t1,
  516. l1,
  517. r1,
  518. b1,
  519. t2,
  520. l2,
  521. r2,
  522. b2,
  523. x1: abbox.x,
  524. y1: abbox.y,
  525. x2: bbbox.x,
  526. y2: bbbox.y,
  527. h1: abbox.height,
  528. h2: bbbox.height,
  529. w1: abbox.width,
  530. w2: bbbox.width,
  531. };
  532. }
  533. };
  534. /** 全局曲谱配置 */
  535. export const setGlobalMusicSheet = () => {
  536. const partIndex = query["part-index"] || '0'
  537. /** 延音线方向问题 start */
  538. const stavetieList = [
  539. {id: '12644', part_index: '25', direction: 1}
  540. ]
  541. const tieItem = stavetieList.find(({id, part_index}) => {
  542. return id == state.cbsExamSongId && part_index == partIndex
  543. })
  544. setGlobalData('tieDirection', tieItem ? tieItem.direction : undefined)
  545. /** 延音线方向问题 end */
  546. const graceList = [
  547. {id: '3509', part_index: '16', direction: 1}
  548. ]
  549. const graceItem = graceList.find(({id, part_index}) => {
  550. return id == state.cbsExamSongId && part_index == partIndex
  551. })
  552. if (graceItem){
  553. setGlobalData('graceCustom', {direction: graceItem.direction})
  554. }
  555. const bassDrumList = [
  556. {id: '3030', part_index: '17', line: 4},
  557. {id: '12704', part_index: '23', line: 3}
  558. ]
  559. const bassDrumItem = bassDrumList.find(({id, part_index}) => {
  560. return id == state.cbsExamSongId && part_index == partIndex
  561. })
  562. if (bassDrumItem){
  563. setGlobalData('customBassDrum', bassDrumItem.line)
  564. }
  565. /** 打击乐多声部,双声部休止符重叠 end */
  566. /** 符杆朝向 */
  567. const stemDirectionList = [
  568. {
  569. id: '11654',
  570. part_index: '16',
  571. stemNotes: [
  572. {id: 124, direction: 0},
  573. {id: 125, direction: 0},
  574. {id: 126, direction: 0},
  575. {id: 127, direction: 0},
  576. {id: 128, direction: 0}
  577. ]
  578. },
  579. {
  580. id: '3581',
  581. part_index: '4',
  582. stemNotes: [
  583. {id: 380, direction: 1},
  584. ]
  585. },
  586. {
  587. id: '3470',
  588. part_index: '0',
  589. stemNotes: [
  590. {id: 36, direction: 1},
  591. {id: 37, direction: 1},
  592. ]
  593. },
  594. {
  595. id: '3470',
  596. part_index: '11',
  597. stemNotes: [
  598. {id: 33, direction: 1},
  599. {id: 56, direction: 1},
  600. ]
  601. },
  602. {
  603. id: '12644',
  604. part_index: '22',
  605. stemNotes: [
  606. {id: 22, direction: 1},
  607. {id: 26, direction: 1},
  608. {id: 135, direction: 1},
  609. {id: 163, direction: 1},
  610. {id: 199, direction: 1},
  611. {id: 204, direction: 1},
  612. {id: 206, direction: 1},
  613. {id: 208, direction: 1},
  614. {id: 210, direction: 1},
  615. {id: 213, direction: 1},
  616. ]
  617. },
  618. {
  619. id: '12303',
  620. part_index: '18',
  621. stemNotes: [
  622. {id: 1, direction: 1},
  623. {id: 4, direction: 1},
  624. {id: 6, direction: 1},
  625. {id: 9, direction: 1},
  626. {id: 12, direction: 1},
  627. {id: 14, direction: 1},
  628. ]
  629. },
  630. {
  631. id: '12669',
  632. part_index: '24',
  633. stemNotes: [
  634. {id: 65, direction: 1},
  635. {id: 296, direction: 1},
  636. {id: 298, direction: 1},
  637. {id: 300, direction: 1},
  638. {id: 338, direction: 1},
  639. ]
  640. },
  641. {
  642. id: '12420',
  643. part_index: '21',
  644. stemNotes: [
  645. {id: 614, direction: 0},
  646. {id: 617, direction: 0},
  647. {id: 619, direction: 0},
  648. {id: 621, direction: 0},
  649. ]
  650. },
  651. {
  652. id: '12711',
  653. part_index: '22',
  654. stemNotes: []
  655. },
  656. {
  657. id: '12973',
  658. part_index: '21',
  659. stemNotes: [
  660. {id: 619, direction: 1},
  661. {id: 622, direction: 1},
  662. {id: 745, direction: 1},
  663. ]
  664. },
  665. ]
  666. const stemDirectionItem = stemDirectionList.find(({id, part_index}) => {
  667. return id == state.cbsExamSongId && part_index == partIndex
  668. })
  669. if (stemDirectionItem) {
  670. setGlobalData('stemDirectionNote', stemDirectionItem.stemNotes)
  671. }
  672. /** vfcure */
  673. const vfcurveList = [
  674. {
  675. id: '12711',
  676. part_index: '4',
  677. vfcurve: [
  678. {MeasureNumberXML: 25, index: 1, bezierEndControlPt: {y: -2}},
  679. {MeasureNumberXML: 33, index: 1, bezierEndControlPt: {y: -2}},
  680. ]
  681. },
  682. {
  683. id: '12059',
  684. part_index: '0',
  685. vfcurve: [
  686. {MeasureNumberXML: 15, bezierEndControlPt: {y: 2.8}, bezierEndPt:{y: 1.1}},
  687. {MeasureNumberXML: 16, bezierEndControlPt: {y: -1}},
  688. {MeasureNumberXML: 19, index: 1, bezierEndControlPt: {y: 2}},
  689. {MeasureNumberXML: 20, bezierEndControlPt: {y: -1}},
  690. {MeasureNumberXML: 42, index: 1, bezierEndControlPt: {y: -1.5}, bezierStartControlPt: {y: -1.5}},
  691. {MeasureNumberXML: 46, index: 3, bezierEndControlPt: {y: -1.5}, bezierStartControlPt: {y: -1.5}},
  692. ]
  693. },
  694. {
  695. id: '12668',
  696. part_index: '11',
  697. vfcurve: [
  698. {MeasureNumberXML: 8, index: 2, bezierEndControlPt: {y: -3}, bezierStartControlPt:{y: -3}, bezierEndPt:{y: -1}},
  699. ]
  700. },
  701. {
  702. id: '11976',
  703. part_index: '0',
  704. vfcurve: [
  705. {MeasureNumberXML: 14, index: 4, bezierEndControlPt: {y: -3}},
  706. {MeasureNumberXML: 14, index: 1, bezierEndPt: {y: 1.5}, bezierEndControlPt: {y: 1}},
  707. ]
  708. },
  709. ]
  710. const vfcurveItem = vfcurveList.find(({id, part_index}) => {
  711. return id == state.cbsExamSongId && part_index == partIndex
  712. })
  713. if (vfcurveItem) {
  714. setGlobalData('vfcurveItem', vfcurveItem.vfcurve)
  715. }
  716. /** drum set声部 重音 */
  717. const customArtPositionList = [
  718. {id: '12644', part_index: '25'}
  719. ]
  720. const customArtPositionItem = customArtPositionList.find(({id, part_index}) => {
  721. return id == state.cbsExamSongId && part_index == partIndex
  722. })
  723. if (customArtPositionItem) {
  724. setGlobalData('customArtPosition', true)
  725. }
  726. /** 全声部声部 - & 全音符 */
  727. const customTenutoList = [
  728. {id: '12645', part_index: '5'}
  729. ]
  730. const customTenutoItem = customTenutoList.find(({id, part_index}) => {
  731. return id == state.cbsExamSongId && part_index == partIndex
  732. })
  733. if (customTenutoItem) {
  734. setGlobalData('customTenutoItem', true)
  735. }
  736. /** 全声部声部 > */
  737. const customAccentList = [
  738. {id: '12711', part_index: '22'},
  739. {id: '12711', part_index: '25'},
  740. ]
  741. const customAccentItem = customAccentList.find(({id, part_index}) => {
  742. return id == state.cbsExamSongId && part_index == partIndex
  743. })
  744. if (customAccentItem) {
  745. setGlobalData('customAccentItem', true)
  746. }
  747. /** 全声部声部 + */
  748. const customLefthandpizzicatoList = [
  749. {id: '12711', part_index: '25'},
  750. {id: '7755', part_index: '10'},
  751. {id: '6226', part_index: '16'},
  752. ]
  753. const customLefthandpizzicatoItem = customLefthandpizzicatoList.find(({id, part_index}) => {
  754. return id == state.cbsExamSongId && part_index == partIndex
  755. })
  756. if (customLefthandpizzicatoItem) {
  757. setGlobalData('customLefthandpizzicatoItem', true)
  758. }
  759. }
  760. /** 设置自定义渐慢 */
  761. export const setCustomGradual = () => {
  762. if (state.gradualTimes) {
  763. const detailId = state.cbsExamSongId + "";
  764. const partIndex = state.partIndex + "";
  765. if (["12280"].includes(detailId) && ["24"].includes(partIndex)) {
  766. state.gradualTimes["8"] = "00:26:10";
  767. state.gradualTimes["66"] = "01:53:35";
  768. state.gradualTimes["90"] = "02:41:40";
  769. }
  770. }
  771. };
  772. /** 设置自定义音符数据 */
  773. export const setCustomNoteRealValue = () => {
  774. const detailId = state.cbsExamSongId + "";
  775. const partIndex = state.partIndex + "";
  776. if (["2670"].includes(detailId)) {
  777. customData.customNoteRealValue = {
  778. 0: 0.03125,
  779. };
  780. }
  781. if (["12673"].includes(detailId) && ['22'].includes(partIndex)) {
  782. customData.customNoteRealValue = {
  783. 208: 0.125,
  784. };
  785. }
  786. if (["12667", "12673"].includes(detailId)){
  787. customData.customNoteCurrentTime = true
  788. }
  789. };