customMusicScore.ts 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  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 = state.partIndex + ""
  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. const initNoteCoord = () => {
  149. const allNoteDot: any = Array.from(document.querySelectorAll('.node-dot'));
  150. state.noteCoords = allNoteDot.map((note: any) => {
  151. const note_bbox = note?.getBoundingClientRect?.() || { x: 0, y: 0 };
  152. return {
  153. x: note_bbox.x,
  154. y: note_bbox.y
  155. }
  156. })
  157. console.log(11111,state.noteCoords)
  158. }
  159. export const moveGracePosition = (needTrans?: boolean) => {
  160. /**
  161. * TODO:曲目:摇篮曲(节奏练习)-倚音位置 特殊处理
  162. */
  163. const specialIds = ['1788850864767643649','1788502467554750466','1789839575249596417','1788501975122489346','1796006876341813249'];
  164. if (specialIds.includes(state.cbsExamSongId) || needTrans) {
  165. const lastCurve: any = Array.from(document.getElementsByClassName('vf-curve'))?.last();
  166. if (lastCurve) {
  167. lastCurve.style.display = 'none';
  168. }
  169. if (state.musicRenderType === 'staff') {
  170. // const transNoteDom = document.getElementById('vf-auto2182')?.getElementsByClassName('vf-modifiers')?.[0];
  171. // const transBeamDom = document.getElementById('auto3167')?.parentNode?.getElementsByClassName('vf-beams')?.[0];
  172. // if (transNoteDom) {
  173. // transNoteDom.style.transform = 'translateX(-0.5rem)';
  174. // }
  175. // if (transBeamDom) {
  176. // transBeamDom.style.transform = 'translateX(-0.5rem)';
  177. // }
  178. } else {
  179. // vf-auto2172 , vf-auto2384
  180. const signatureDom = document.getElementById('auto2670');
  181. const signatureDom2 = document.getElementById('auto2710');
  182. const signatureDom3 = document.getElementById('auto3099');
  183. const signatureDom4 = document.getElementById('auto3339');
  184. const needTransLateDom: any = state.cbsExamSongId == '1789839575249596417' && document.getElementById('vf-auto1554')?.getElementsByClassName('vf-modifier')?.[0];
  185. const arrowDom = state.cbsExamSongId == '1789839575249596417' && document.getElementById('vf-auto1554-lines');
  186. const needTransLateDom2: any = state.cbsExamSongId == '1788501975122489346' && document.getElementById('vf-auto2116')?.getElementsByClassName('vf-modifier')?.[0];
  187. const arrowDom2 = state.cbsExamSongId == '1788501975122489346' && document.getElementById('vf-auto2116-lines');
  188. const needTransLateDom3: any = state.cbsExamSongId == '1788502467554750466' && document.getElementById('vf-auto2122')?.getElementsByClassName('vf-modifier')?.[0];
  189. const arrowDom3 = state.cbsExamSongId == '1788502467554750466' && document.getElementById('vf-auto2122-lines');
  190. if (signatureDom) signatureDom.style.display = 'none';
  191. if (signatureDom2) signatureDom2.style.display = 'none';
  192. if (signatureDom3) signatureDom3.style.display = 'none';
  193. if (signatureDom4) signatureDom4.style.display = 'none';
  194. if (needTransLateDom) needTransLateDom.style.transform = 'translateX(-0.65rem)';
  195. if (needTransLateDom2) needTransLateDom2.style.transform = 'translateX(-0.65rem)';
  196. if (needTransLateDom3) needTransLateDom3.style.transform = 'translateX(-0.65rem)';
  197. if (arrowDom) arrowDom.style.transform = 'translateX(-0.65rem)';
  198. if (arrowDom2) arrowDom2.style.transform = 'translateX(-0.65rem)';
  199. if (arrowDom3) arrowDom3.style.transform = 'translateX(-0.65rem)';
  200. if (arrowDom || arrowDom2 || arrowDom3) {
  201. const path: any = arrowDom ? arrowDom.querySelector('path') : arrowDom2 ? arrowDom2.querySelector('path') : arrowDom3 ? arrowDom3.querySelector('path') : null;
  202. let d = path?.getAttribute("d");
  203. if (d) {
  204. const patchStr = d.split('L')?.last()?.split(" ")?.[0];
  205. let startX = d.split("M")?.[1]?.split(" ")[0] || 0;
  206. startX = startX ? Number(startX) : 0;
  207. let endX = d.split("L")?.last().split(" ")[0] || 0;
  208. endX = endX ? Number(endX) : 0;
  209. const distanceX = endX - startX;
  210. const transX = startX - distanceX;
  211. d = d.replace(`L${patchStr}`,`L${transX}`);
  212. path.setAttribute("d", d);
  213. }
  214. }
  215. }
  216. }
  217. }
  218. // 处理一行谱高度太高的问题
  219. export const limitSingleSvgPageHeight = () => {
  220. if (state.isSingleLine && !state.isCombineRender) {
  221. // 获取生成的 SVG 元素
  222. const osmdSvgPage = document.getElementById('osmdSvgPage1');
  223. if (osmdSvgPage) {
  224. // 获取当前的 viewBox 值
  225. const viewBox = osmdSvgPage.getAttribute('viewBox');
  226. if (viewBox) {
  227. // 分割 viewBox 的值,viewBox 形式为 "min-x min-y width height"
  228. const viewBoxValues = viewBox.split(' ');
  229. // 修改 viewBox 高度
  230. const newHeight = 300;
  231. const staffHeight = osmdSvgPage.querySelector('.staffline')?.getBoundingClientRect()?.height;
  232. if (viewBoxValues[3] > 300 && staffHeight && staffHeight < 240) {
  233. const originY = state.isSimplePage ? 0 : 0.25;
  234. let transY = (240 - staffHeight * state.zoom) / staffHeight;
  235. transY = state.isSimplePage ? transY / 2 : transY;
  236. const realY = (originY + transY) * 100 + '%';
  237. viewBoxValues[3] = newHeight; // 这是第四个值,代表高度
  238. // 设置新的 viewBox 值
  239. osmdSvgPage.setAttribute('viewBox', viewBoxValues.join(' '));
  240. osmdSvgPage.setAttribute('height','240');
  241. // @ts-ignore
  242. osmdSvgPage.querySelector('.staffline').style.transform = `translateY(-${realY})`
  243. }
  244. }
  245. }
  246. }
  247. }
  248. // 谱面优化
  249. export const resetFormate = () => {
  250. container.value = document.getElementById('scrollContainer')
  251. // if (state.extStyleConfigJson || !container.value) return;
  252. if (!container.value) return;
  253. moveGracePosition();
  254. // setTimeout(() => {
  255. // initNoteCoord();
  256. // }, 0);
  257. const stafflines: SVGAElement[] = Array.from((container.value as HTMLElement).querySelectorAll(".staffline"));
  258. const baseStep = 4; // 两个元素相间,的间距
  259. const musicalDistance = 28; // 音阶与第一条线谱的间距,默认设置为28
  260. for (let i = 0, len = stafflines.length; i < len; i++) {
  261. const staffline = stafflines[i];
  262. const stafflineBox = staffline.getBBox();
  263. const stafflineCenter = stafflineBox.y + stafflineBox.height / 2;
  264. const vfmeasures: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure"));
  265. const vfcurve: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-curve"));
  266. const vfvoices: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-voices"));
  267. const vfbeams: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-beams"));
  268. const vfties: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-ties"));
  269. const vflines: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-line"));
  270. const texts: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave text"));
  271. const rects: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave rect[fill=none]"));
  272. const staveSection: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-staveSection"));
  273. const paths: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave path"));
  274. const dotModifiers: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-stopDot"));
  275. const staves: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave"));
  276. // 获取第一个线谱的y轴坐标
  277. const firstLinePathY = paths[0]?.getBBox().y || 0
  278. // 反复标记 和 小节碰撞
  279. const repetWord = ["To Coda", "D.S. al Coda", "Coda"];
  280. texts
  281. .filter((n) => repetWord.includes(n.textContent || ""))
  282. .forEach((t) => {
  283. vfbeams.forEach((curve) => {
  284. const result = collisionDetection(t, curve);
  285. const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement;
  286. if (result.isCollision) {
  287. const shift_y = Number(t.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
  288. t.setAttribute("y", shift_y);
  289. // console.log('音阶间距',shift_y)
  290. if (prePath && prePath.getAttribute("stroke-width") === "0.3" && prePath.getAttribute("stroke") === "none" && (prePath.getAttribute("d")?.length || 0) > 3000) {
  291. prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`;
  292. }
  293. }
  294. });
  295. vfvoices.forEach((curve) => {
  296. const result = collisionDetection(t, curve);
  297. const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement;
  298. if (result.isCollision) {
  299. const shift_y = Number(t.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
  300. t.setAttribute("y", shift_y);
  301. // console.log('音阶间距',shift_y)
  302. if (prePath && prePath.getAttribute("stroke-width") === "0.3" && prePath.getAttribute("stroke") === "none" && (prePath.getAttribute("d")?.length || 0) > 3000) {
  303. prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`;
  304. }
  305. }
  306. });
  307. });
  308. // 文字方框和飞线碰撞
  309. staveSection.forEach((t) => {
  310. let shift_y = 0;
  311. [...vfcurve, ...vfties, ...vfvoices].forEach((curve) => {
  312. const result = collisionDetection(t, curve);
  313. if (result.isCollision) {
  314. shift_y = Math.min(shift_y, result.t2 - result.b1 - baseStep);
  315. }
  316. });
  317. t.style.transform = `translateY(${shift_y}px)`;
  318. });
  319. // 文字和小节碰撞
  320. let vftexts = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y < stafflineCenter);
  321. for (let i = 0; i < vftexts.length; i++) {
  322. const _text = vftexts[i];
  323. for (let j = 0; j < vftexts.length; j++) {
  324. if (_text.textContent === 'second time only') {
  325. // @ts-ignore
  326. _text.style.transform = `translateY(15px)`;
  327. }
  328. if (_text.parentNode === vftexts[j].parentNode) continue;
  329. const result = collisionDetection(_text as SVGAElement, vftexts[j] as SVGAElement);
  330. if (result.isCollision) {
  331. if (_text.textContent === vftexts[j].textContent) {
  332. vftexts[j].parentNode?.removeChild(vftexts[j]);
  333. continue;
  334. }
  335. }
  336. }
  337. }
  338. vftexts = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y < stafflineCenter);
  339. let maxY = 0;
  340. let _vftexts: SVGAElement[] = [];
  341. vftexts.forEach((vftext: any) => {
  342. const textBox = vftext.getBBox();
  343. if (textBox.y < stafflineCenter) {
  344. maxY = Math.max(maxY, textBox.y + textBox.height);
  345. //console.log('音阶间距',textBox.y, textBox.height)
  346. _vftexts.push(vftext as SVGAElement);
  347. }
  348. });
  349. if (maxY !== 0 && _vftexts.length > 1) {
  350. _vftexts.forEach((vftext) => {
  351. vftext.setAttribute("y", maxY + "");
  352. //console.log('音阶间距',maxY)
  353. });
  354. }
  355. vftexts.forEach((vftext) => {
  356. [...vfcurve, ...vfmeasures, ...vflines].forEach((vfmeasure) => {
  357. let result = collisionDetection(vftext as SVGAElement, vfmeasure);
  358. if (result.isCollision && result.b1 < result.b2 && result.t1 < result.b2 - (result.b2 - result.t2) / 2) {
  359. const shift_y = Number(vftext.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
  360. vftext.setAttribute("y", shift_y);
  361. //console.log('音阶间距',shift_y)
  362. }
  363. });
  364. });
  365. vftexts.forEach((vftext) => {
  366. vftexts.forEach((text) => {
  367. if (vftext.parentNode !== text.parentNode && !["marcato", "legato"].includes(vftext.textContent as string)) {
  368. if (["marcato", "legato"].includes(text.textContent as string)) {
  369. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30);
  370. if (result.isCollision) {
  371. const textBBox = (vftext as SVGAElement).getBBox();
  372. text.setAttribute("x", textBBox.x + textBBox.width + 5 + "");
  373. text.setAttribute("y", textBBox.y + textBBox.height - 5 + "");
  374. //console.log('音阶间距',textBBox.y + textBBox.height - 5 + "")
  375. }
  376. } else {
  377. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement);
  378. if (result.isCollision) {
  379. const _y = Number(vftext.getAttribute("y"));
  380. const shift_y = result.b2 - result.t2 < 24 ? 24 : result.b2 - result.t2;
  381. text.setAttribute("y", _y - shift_y - 0.5 + "");
  382. //console.log('音阶间距',_y - shift_y - 0.5 + "")
  383. }
  384. }
  385. }
  386. });
  387. });
  388. // 修改音阶和线谱的间距
  389. const clefList = ['C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb', 'D#', 'A#', 'E#']
  390. const btransList = ['Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb']
  391. const jtrsnsList = ['F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'B#']
  392. vftexts.forEach((label: any) => {
  393. const labelText = label.textContent as string
  394. if (clefList.includes(labelText)){
  395. const _y = Number(label.getAttribute("y"))
  396. const endY = firstLinePathY ? firstLinePathY - musicalDistance : _y
  397. label.setAttribute("y", endY)
  398. }
  399. if (btransList.includes(labelText)) {
  400. label.textContent = labelText.replace('b','♭')
  401. }
  402. if (jtrsnsList.includes(labelText)) {
  403. label.textContent = labelText.replace('#','♯')
  404. }
  405. });
  406. dotModifiers.forEach((group: any) => {
  407. if (state.musicRenderType === 'fixedTone') {
  408. group.setAttribute('transform', 'translate(3,-12)')
  409. } else {
  410. group.setAttribute('transform', 'translate(3,-7)')
  411. }
  412. });
  413. const vftextBottom = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y > stafflineCenter);
  414. const vflineBottom = Array.from(staffline.querySelectorAll(".vf-line")).filter((n: any) => n.getBBox().y > stafflineCenter);
  415. // 去重
  416. for (let i = 0; i < vftextBottom.length; i++) {
  417. const _text = vftextBottom[i];
  418. for (let j = 0; j < vftextBottom.length; j++) {
  419. if (_text.parentNode === vftextBottom[j].parentNode) continue;
  420. const result = collisionDetection(_text as SVGAElement, vftextBottom[j] as SVGAElement);
  421. if (result.isCollision) {
  422. if (_text.textContent === vftextBottom[j].textContent) {
  423. vftextBottom[j].parentNode?.removeChild(vftextBottom[j]);
  424. continue;
  425. }
  426. }
  427. }
  428. }
  429. // 1,2线谱底部文字重叠问题
  430. vftextBottom.forEach((vftext) => {
  431. [...vfmeasures].forEach((n) => {
  432. let result = collisionDetection(vftext as SVGAElement, n);
  433. if (result.isCollision) {
  434. vftext.setAttribute("y", result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute("y"))) + "");
  435. //console.log('音阶间距', result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute("y"))) + "")
  436. }
  437. });
  438. });
  439. // 如果渐弱渐强有平行的文字
  440. vflineBottom.forEach((line) => {
  441. const texts: any[] = [];
  442. if (line.nextElementSibling?.classList.contains("vf-line")) {
  443. vftextBottom.forEach((text) => {
  444. let result = collisionDetection(line as SVGAElement, text as SVGAElement, 20, 20);
  445. if (result.isCollision) {
  446. texts.push({
  447. text: text as SVGAElement,
  448. result,
  449. });
  450. }
  451. });
  452. }
  453. if (texts.length === 1) {
  454. const result = texts[0].result;
  455. const text = texts[0].text;
  456. if (result.x2 + result.w2 < result.x1) {
  457. // 左
  458. if (Math.abs(result.y2 - result.y1) > 10) {
  459. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  460. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  461. }
  462. } else if (result.x2 > result.x1 + result.w1) {
  463. // 右
  464. if (Math.abs(result.y2 - result.y1) > 10) {
  465. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  466. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  467. }
  468. } else {
  469. if (Math.abs(result.x2 - result.x1) < Math.abs(result.x2 + result.w2 - result.x1 - result.w1)) {
  470. // console.log(text, '有交集', '靠左')
  471. text.setAttribute("x", result.x1 - result.w2 - 5 + "");
  472. if (Math.abs(result.y2 - result.y1) > 10) {
  473. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  474. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  475. }
  476. } else {
  477. // console.log(text, '有交集', '靠右')
  478. text.setAttribute("x", result.x1 + result.w1 + 5 + "");
  479. if (Math.abs(result.y2 - result.y1) > 10) {
  480. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  481. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  482. }
  483. }
  484. }
  485. } else if (texts.length === 2) {
  486. const result1 = texts[0].result;
  487. const text1 = texts[0].text;
  488. const result2 = texts[1].result;
  489. const text2 = texts[1].text;
  490. text1.setAttribute("x", result1.x1 - result1.w2 - 5 + "");
  491. if (Math.abs(result1.y2 - result1.y1) > 10) {
  492. text1.setAttribute("y", result1.y1 + result1.h2 / 2 + "");
  493. //console.log('音阶间距', result1.y1 + result1.h2 / 2 + "")
  494. }
  495. text2.setAttribute("x", result2.x1 + result2.w1 + 5 + "");
  496. if (Math.abs(result2.y2 - result2.y1) > 10) {
  497. text2.setAttribute("y", result2.y1 + result2.h2 / 2 + "");
  498. //console.log('音阶间距', result2.y1 + result2.h2 / 2 + "")
  499. }
  500. } else if (texts.length === 3) {
  501. // console.log(texts)
  502. }
  503. });
  504. vftextBottom.forEach((vftext) => {
  505. vftextBottom.forEach((text) => {
  506. if (vftext.parentNode !== text.parentNode && !["marcato", "legato", "cresc.", "Cantabile"].includes(vftext.textContent as string)) {
  507. if (["marcato", "legato", "cresc.", "Cantabile"].includes(text.textContent as string)) {
  508. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30);
  509. if (result.isCollision) {
  510. const textBBox = (vftext as SVGAElement).getBBox();
  511. text.setAttribute("x", textBBox.x + textBBox.width + 5 + "");
  512. text.setAttribute("y", textBBox.y + textBBox.height - 5 + "");
  513. //console.log('音阶间距', textBBox.y + textBBox.height - 5 + "")
  514. }
  515. } else {
  516. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement);
  517. if (result.isCollision) {
  518. text.setAttribute("y", result.y1 + result.h1 + result.h2 + "");
  519. //console.log('音阶间距', result.y1 + result.h1 + result.h2 + "")
  520. }
  521. }
  522. }
  523. });
  524. });
  525. // 同一行内,多个飞线碰撞
  526. for (let index = 0; index < vfcurve.length; index++) {
  527. let nextIndex = index + 1;
  528. const cur = vfcurve[index];
  529. let next = vfcurve[nextIndex];
  530. let checkDone = false;
  531. while (nextIndex <= vfcurve.length - 1 && !checkDone) {
  532. let result = collisionDetection(cur, next);
  533. if (result.isCollision) {
  534. checkDone = true;
  535. next.style.transform = `translateY(-12px)`;
  536. } else {
  537. nextIndex = nextIndex + 1;
  538. next = vfcurve[nextIndex];
  539. }
  540. }
  541. };
  542. // 给小节添加背景色
  543. if (!state.isCreateImg && !state.isPreView) {
  544. staves.forEach((stave: any,i: number) => {
  545. const list = [
  546. Array.from(stave?.getElementsByTagName("text") || []),
  547. Array.from(stave?.querySelectorAll(".vf-StaveSection") || []),
  548. Array.from(stave?.querySelectorAll(".vf-Volta") || []),
  549. Array.from(stave?.querySelectorAll(".vf-clef") || []),
  550. Array.from(stave?.querySelectorAll(".vf-keysignature") || []),
  551. Array.from(stave?.querySelectorAll(".vf-Repetition") || []),
  552. ].flat();
  553. try {
  554. if (list.length) {
  555. list.forEach((_el: any) => {
  556. if (_el.parentNode === stave) {
  557. stave?.removeChild(_el)
  558. _el?.style?.setProperty("display", "none");
  559. }
  560. });
  561. }
  562. } catch (error) {}
  563. const bbox = stave?.getBBox() || {};
  564. const bgColor = state.isEvaluatReport ? '#132D4C' : '#609FCF';
  565. const botColor = state.isEvaluatReport ? '#040D1E' : '#2B70A5';
  566. const rect = `<rect class="vf-custom-bg" x="${bbox.x}" y="${bbox.y}" width="${bbox.width}" height="${bbox.height}" fill=${bgColor} />`
  567. const rectBottom = `<rect class="vf-custom-bot" x="${bbox.x}" y="${bbox.y+bbox.height}" width="${bbox.width}" height="7.5" fill=${botColor} />`
  568. // const filterDom = `<defs>
  569. // <filter id="shadow">
  570. // <feDropShadow dx="5" dy="5" stdDeviation="3" flood-color="black" />
  571. // </filter>
  572. // </defs>`
  573. const customG = `<g>${rect}${rectBottom}</g>`
  574. try {
  575. if (list.length) {
  576. list.forEach((_el: any) => {
  577. stave?.appendChild(_el)
  578. _el?.style?.removeProperty("display");
  579. });
  580. }
  581. } catch (error) {}
  582. stave.innerHTML = customG + stave.innerHTML;
  583. });
  584. state.vfmeasures = state.vfmeasures.concat(vfmeasures);
  585. }
  586. }
  587. if (!state.isCombineRender && state.isSingleLine) {
  588. transSinglePage();
  589. }
  590. // setTimeout(() => this.resetGlobalText());
  591. };
  592. // 一行谱时,五线谱/简谱的谱面staffLine,居中显示
  593. const transSinglePage = () => {
  594. if (state.isSingleLine && !state.isSimplePage) {
  595. const svgPage = document?.getElementById('osmdSvgPage1')?.getBoundingClientRect();
  596. const staffLine = document?.querySelector('.staffline')?.getBoundingClientRect();
  597. if (svgPage && staffLine && svgPage.height > 200 && state.platform !== 'PC') {
  598. // 需要上移的距离
  599. // console.log('need',svgPage.height,staffLine.height)
  600. const rate = svgPage.height > 400 ? 1.2 : 2;
  601. const needTransTop = (svgPage.height - staffLine.height) / rate;
  602. // @ts-ignore
  603. document.getElementById('osmdSvgPage1').style.transform = `translateY(-${needTransTop}px)`;
  604. // document.querySelector('.staffline').style.transform = `translateY(-${needTransTop}px)`;
  605. // const musicLine = document.querySelector('.staffline').querySelector('.vf-measure').querySelector('.vf-custom-bg').getBoundingClientRect();
  606. // const needTransDistance = svgPage.height / 2 - (musicLine.top - svgPage.top)
  607. // console.log('svg移动距离',needTransDistance)
  608. // // @ts-ignore
  609. // document.getElementById('osmdSvgPage1').style.transform = `translateY(${needTransDistance}px)`
  610. }
  611. }
  612. }
  613. // 技巧文本
  614. const resetGlobalText = () => {
  615. const svg = container.value.querySelector("svg");
  616. if (!svg) return;
  617. const svgBBox = svg.getBBox();
  618. let vfstavetempo: SVGAElement[] = Array.from(container.value.querySelectorAll(".vf-stavetempo")).reduce((eles: SVGAElement[], value: any) => {
  619. if (eles.find((n) => n.outerHTML === value.outerHTML)) value?.parentNode?.removeChild(value);
  620. else eles.push(value);
  621. return eles;
  622. }, []);
  623. const staffline: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline"));
  624. const vfmeasures: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-measure"));
  625. const vftexts: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-text"));
  626. const vfcurves: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-curve"));
  627. vfstavetempo.forEach((child: SVGAElement) => {
  628. let _y = 0;
  629. [...vfmeasures, ...vftexts, ...vfcurves].forEach((ele) => {
  630. const result = collisionDetection(child as SVGAElement, ele);
  631. if (result.isCollision && (result.b1 < result.b2 || result.r1 > result.l2 || result.l1 < result.r2)) {
  632. _y = Math.min(_y, result.t2 - result.b1);
  633. }
  634. });
  635. if (_y !== 0) {
  636. child.style.transform = `translateY(${_y}px)`;
  637. }
  638. const childBBox = child.getBBox();
  639. const rightY = (childBBox.x + childBBox.width) * 0.7 - Number(svg.getAttribute("width"));
  640. if (rightY > 0) {
  641. [...staffline, ...vfstavetempo].forEach((tempo) => {
  642. if (child != tempo) {
  643. const result = collisionDetection(child as SVGAElement, tempo, Math.abs(rightY), Math.abs(_y));
  644. if (result.isCollision) {
  645. _y = result.t2 - result.b1;
  646. }
  647. }
  648. });
  649. child.style.transform = `translate(-${rightY / 0.7}px,${_y}px)`;
  650. }
  651. });
  652. if (svgBBox.y < 0) {
  653. svg.setAttribute("height", Number(svg.getAttribute("height")) - svgBBox.y + 10);
  654. }
  655. };
  656. // 碰撞检测
  657. const collisionDetection = (a: SVGAElement, b: SVGAElement, distance: number = 0, distance_y: number = 0) => {
  658. const abbox = a.getBBox();
  659. const bbbox = b.getBBox();
  660. let t1 = abbox.y - distance_y;
  661. let l1 = abbox.x - distance;
  662. let r1 = abbox.x + abbox.width + distance;
  663. let b1 = abbox.y + abbox.height + distance_y;
  664. let t2 = bbbox.y;
  665. let l2 = bbbox.x;
  666. let r2 = bbbox.x + bbbox.width;
  667. let b2 = bbbox.y + bbbox.height;
  668. if (b1 < t2 || l1 > r2 || t1 > b2 || r1 < l2) {
  669. // 表示没碰上
  670. return {
  671. isCollision: false,
  672. t1,
  673. l1,
  674. r1,
  675. b1,
  676. t2,
  677. l2,
  678. r2,
  679. b2,
  680. x1: abbox.x,
  681. y1: abbox.y,
  682. x2: bbbox.x,
  683. y2: bbbox.y,
  684. h1: abbox.height,
  685. h2: bbbox.height,
  686. w1: abbox.width,
  687. w2: bbbox.width,
  688. };
  689. } else {
  690. return {
  691. isCollision: true,
  692. t1,
  693. l1,
  694. r1,
  695. b1,
  696. t2,
  697. l2,
  698. r2,
  699. b2,
  700. x1: abbox.x,
  701. y1: abbox.y,
  702. x2: bbbox.x,
  703. y2: bbbox.y,
  704. h1: abbox.height,
  705. h2: bbbox.height,
  706. w1: abbox.width,
  707. w2: bbbox.width,
  708. };
  709. }
  710. };
  711. /** 全局曲谱配置 */
  712. export const setGlobalMusicSheet = () => {
  713. const partIndex = state.partIndex + ""
  714. /** 延音线方向问题 start */
  715. const stavetieList = [
  716. {id: '12644', part_index: '25', direction: 1}
  717. ]
  718. const tieItem = stavetieList.find(({id, part_index}) => {
  719. return id == state.cbsExamSongId && part_index == partIndex
  720. })
  721. setGlobalData('tieDirection', tieItem ? tieItem.direction : undefined)
  722. /** 延音线方向问题 end */
  723. const graceList = [
  724. {id: '3509', part_index: '16', direction: 1}
  725. ]
  726. const graceItem = graceList.find(({id, part_index}) => {
  727. return id == state.cbsExamSongId && part_index == partIndex
  728. })
  729. if (graceItem){
  730. setGlobalData('graceCustom', {direction: graceItem.direction})
  731. }
  732. const bassDrumList = [
  733. {id: '3030', part_index: '17', line: 4},
  734. {id: '12704', part_index: '23', line: 3}
  735. ]
  736. const bassDrumItem = bassDrumList.find(({id, part_index}) => {
  737. return id == state.cbsExamSongId && part_index == partIndex
  738. })
  739. if (bassDrumItem){
  740. setGlobalData('customBassDrum', bassDrumItem.line)
  741. }
  742. /** 打击乐多声部,双声部休止符重叠 end */
  743. /** 符杆朝向 */
  744. const stemDirectionList = [
  745. {
  746. id: '11654',
  747. part_index: '16',
  748. stemNotes: [
  749. {id: 124, direction: 0},
  750. {id: 125, direction: 0},
  751. {id: 126, direction: 0},
  752. {id: 127, direction: 0},
  753. {id: 128, direction: 0}
  754. ]
  755. },
  756. {
  757. id: '3581',
  758. part_index: '4',
  759. stemNotes: [
  760. {id: 380, direction: 1},
  761. ]
  762. },
  763. {
  764. id: '3470',
  765. part_index: '0',
  766. stemNotes: [
  767. {id: 36, direction: 1},
  768. {id: 37, direction: 1},
  769. ]
  770. },
  771. {
  772. id: '3470',
  773. part_index: '11',
  774. stemNotes: [
  775. {id: 33, direction: 1},
  776. {id: 56, direction: 1},
  777. ]
  778. },
  779. {
  780. id: '12644',
  781. part_index: '22',
  782. stemNotes: [
  783. {id: 22, direction: 1},
  784. {id: 26, direction: 1},
  785. {id: 135, direction: 1},
  786. {id: 163, direction: 1},
  787. {id: 199, direction: 1},
  788. {id: 204, direction: 1},
  789. {id: 206, direction: 1},
  790. {id: 208, direction: 1},
  791. {id: 210, direction: 1},
  792. {id: 213, direction: 1},
  793. ]
  794. },
  795. {
  796. id: '12303',
  797. part_index: '18',
  798. stemNotes: [
  799. {id: 1, direction: 1},
  800. {id: 4, direction: 1},
  801. {id: 6, direction: 1},
  802. {id: 9, direction: 1},
  803. {id: 12, direction: 1},
  804. {id: 14, direction: 1},
  805. ]
  806. },
  807. {
  808. id: '12669',
  809. part_index: '24',
  810. stemNotes: [
  811. {id: 65, direction: 1},
  812. {id: 296, direction: 1},
  813. {id: 298, direction: 1},
  814. {id: 300, direction: 1},
  815. {id: 338, direction: 1},
  816. ]
  817. },
  818. {
  819. id: '12420',
  820. part_index: '21',
  821. stemNotes: [
  822. {id: 614, direction: 0},
  823. {id: 617, direction: 0},
  824. {id: 619, direction: 0},
  825. {id: 621, direction: 0},
  826. ]
  827. },
  828. {
  829. id: '12711',
  830. part_index: '22',
  831. stemNotes: []
  832. },
  833. {
  834. id: '12973',
  835. part_index: '21',
  836. stemNotes: [
  837. {id: 619, direction: 1},
  838. {id: 622, direction: 1},
  839. {id: 745, direction: 1},
  840. ]
  841. },
  842. ]
  843. const stemDirectionItem = stemDirectionList.find(({id, part_index}) => {
  844. return id == state.cbsExamSongId && part_index == partIndex
  845. })
  846. if (stemDirectionItem) {
  847. setGlobalData('stemDirectionNote', stemDirectionItem.stemNotes)
  848. }
  849. /** vfcure */
  850. const vfcurveList = [
  851. {
  852. id: '12711',
  853. part_index: '4',
  854. vfcurve: [
  855. {MeasureNumberXML: 25, index: 1, bezierEndControlPt: {y: -2}},
  856. {MeasureNumberXML: 33, index: 1, bezierEndControlPt: {y: -2}},
  857. ]
  858. },
  859. {
  860. id: '12059',
  861. part_index: '0',
  862. vfcurve: [
  863. {MeasureNumberXML: 15, bezierEndControlPt: {y: 2.8}, bezierEndPt:{y: 1.1}},
  864. {MeasureNumberXML: 16, bezierEndControlPt: {y: -1}},
  865. {MeasureNumberXML: 19, index: 1, bezierEndControlPt: {y: 2}},
  866. {MeasureNumberXML: 20, bezierEndControlPt: {y: -1}},
  867. {MeasureNumberXML: 42, index: 1, bezierEndControlPt: {y: -1.5}, bezierStartControlPt: {y: -1.5}},
  868. {MeasureNumberXML: 46, index: 3, bezierEndControlPt: {y: -1.5}, bezierStartControlPt: {y: -1.5}},
  869. ]
  870. },
  871. {
  872. id: '12668',
  873. part_index: '11',
  874. vfcurve: [
  875. {MeasureNumberXML: 8, index: 2, bezierEndControlPt: {y: -3}, bezierStartControlPt:{y: -3}, bezierEndPt:{y: -1}},
  876. ]
  877. },
  878. {
  879. id: '11976',
  880. part_index: '0',
  881. vfcurve: [
  882. {MeasureNumberXML: 14, index: 4, bezierEndControlPt: {y: -3}},
  883. {MeasureNumberXML: 14, index: 1, bezierEndPt: {y: 1.5}, bezierEndControlPt: {y: 1}},
  884. ]
  885. },
  886. ]
  887. const vfcurveItem = vfcurveList.find(({id, part_index}) => {
  888. return id == state.cbsExamSongId && part_index == partIndex
  889. })
  890. if (vfcurveItem) {
  891. setGlobalData('vfcurveItem', vfcurveItem.vfcurve)
  892. }
  893. /** drum set声部 重音 */
  894. const customArtPositionList = [
  895. {id: '12644', part_index: '25'}
  896. ]
  897. const customArtPositionItem = customArtPositionList.find(({id, part_index}) => {
  898. return id == state.cbsExamSongId && part_index == partIndex
  899. })
  900. if (customArtPositionItem) {
  901. setGlobalData('customArtPosition', true)
  902. }
  903. /** 全声部声部 - & 全音符 */
  904. const customTenutoList = [
  905. {id: '12645', part_index: '5'}
  906. ]
  907. const customTenutoItem = customTenutoList.find(({id, part_index}) => {
  908. return id == state.cbsExamSongId && part_index == partIndex
  909. })
  910. if (customTenutoItem) {
  911. setGlobalData('customTenutoItem', true)
  912. }
  913. /** 全声部声部 > */
  914. const customAccentList = [
  915. {id: '12711', part_index: '22'},
  916. {id: '12711', part_index: '25'},
  917. ]
  918. const customAccentItem = customAccentList.find(({id, part_index}) => {
  919. return id == state.cbsExamSongId && part_index == partIndex
  920. })
  921. if (customAccentItem || state.isEvxml) {
  922. setGlobalData('customAccentItem', true)
  923. }
  924. /** 全声部声部 + */
  925. const customLefthandpizzicatoList = [
  926. {id: '12711', part_index: '25'},
  927. {id: '7755', part_index: '10'},
  928. {id: '6226', part_index: '16'},
  929. ]
  930. const customLefthandpizzicatoItem = customLefthandpizzicatoList.find(({id, part_index}) => {
  931. return id == state.cbsExamSongId && part_index == partIndex
  932. })
  933. if (customLefthandpizzicatoItem) {
  934. setGlobalData('customLefthandpizzicatoItem', true)
  935. }
  936. }
  937. /** 设置自定义渐慢 */
  938. export const setCustomGradual = () => {
  939. if (state.gradualTimes) {
  940. const detailId = state.cbsExamSongId + "";
  941. const partIndex = state.partIndex + "";
  942. if (["12280"].includes(detailId) && ["24"].includes(partIndex)) {
  943. state.gradualTimes["8"] = "00:26:10";
  944. state.gradualTimes["66"] = "01:53:35";
  945. state.gradualTimes["90"] = "02:41:40";
  946. }
  947. }
  948. };
  949. /** 设置自定义音符数据 */
  950. export const setCustomNoteRealValue = () => {
  951. const detailId = state.cbsExamSongId + "";
  952. const partIndex = state.partIndex + "";
  953. if (["2670"].includes(detailId)) {
  954. customData.customNoteRealValue = {
  955. 0: 0.03125,
  956. };
  957. }
  958. if (["12673"].includes(detailId) && ['22'].includes(partIndex)) {
  959. customData.customNoteRealValue = {
  960. 208: 0.125,
  961. };
  962. }
  963. if (["12667", "12673"].includes(detailId)){
  964. customData.customNoteCurrentTime = true
  965. }
  966. };