customMusicScore.ts 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286
  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. const isElementInViewport = (el: any) => {
  249. const rect = el.getBoundingClientRect();
  250. return (
  251. rect.top >= 0 &&
  252. rect.left >= 0 &&
  253. rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
  254. rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  255. );
  256. }
  257. const isNumeric = (str: any) => {
  258. return /^\d+$/.test(str);
  259. }
  260. // 谱面优化
  261. export const resetFormate = () => {
  262. container.value = document.getElementById('scrollContainer')
  263. // if (state.extStyleConfigJson || !container.value) return;
  264. if (!container.value) return;
  265. moveGracePosition();
  266. // setTimeout(() => {
  267. // initNoteCoord();
  268. // }, 0);
  269. const stafflines: SVGAElement[] = Array.from((container.value as HTMLElement).querySelectorAll(".staffline"));
  270. const baseStep = 4; // 两个元素相间,的间距
  271. const musicalDistance = 28; // 音阶与第一条线谱的间距,默认设置为28
  272. for (let i = 0, len = stafflines.length; i < len; i++) {
  273. const staffline = stafflines[i];
  274. const stafflineBox = staffline.getBBox();
  275. const stafflineCenter = stafflineBox.y + stafflineBox.height / 2;
  276. const vfmeasures: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure"));
  277. const vfcurve: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-curve"));
  278. const vfvoices: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-voices"));
  279. const vfbeams: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-beams"));
  280. const vfties: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-ties"));
  281. const vflines: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-line"));
  282. const texts: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave text"));
  283. const rects: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave rect[fill=none]"));
  284. const staveSection: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-staveSection"));
  285. const paths: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave path"));
  286. const dotModifiers: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-stopDot"));
  287. const staves: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave"));
  288. const numberVfTexts = Array.from((container.value as HTMLElement).querySelectorAll(".vf-text > text"));
  289. // 获取第一个线谱的y轴坐标
  290. const firstLinePathY = paths[0]?.getBBox().y || 0
  291. // D.C循环标记没有显示完全修复
  292. // 反复标记 和 小节碰撞
  293. const repetWord = ["To Coda", "D.S. al Coda", "Coda", "D.C."];
  294. texts
  295. .filter((n) => repetWord.includes(n.textContent || ""))
  296. .forEach((t) => {
  297. // console.log('文本123',t.textContent,'是否在可视区域内',isElementInViewport(t))
  298. // D.C循环标记不在可视区域内,需要修复移动其位置信息
  299. // if (t.textContent?.includes('D.C')) {
  300. // if (!isElementInViewport(t)) {
  301. // t.style.transform = `translateX(-40px)`;
  302. // }
  303. // }
  304. vfbeams.forEach((curve) => {
  305. const result = collisionDetection(t, curve);
  306. const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement;
  307. if (result.isCollision) {
  308. const shift_y = Number(t.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
  309. t.setAttribute("y", shift_y);
  310. // console.log('音阶间距',shift_y)
  311. if (prePath && prePath.getAttribute("stroke-width") === "0.3" && prePath.getAttribute("stroke") === "none" && (prePath.getAttribute("d")?.length || 0) > 3000) {
  312. prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`;
  313. }
  314. }
  315. });
  316. vfvoices.forEach((curve) => {
  317. const result = collisionDetection(t, curve);
  318. const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement;
  319. if (result.isCollision) {
  320. const shift_y = Number(t.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
  321. t.setAttribute("y", shift_y);
  322. // console.log('音阶间距',shift_y)
  323. if (prePath && prePath.getAttribute("stroke-width") === "0.3" && prePath.getAttribute("stroke") === "none" && (prePath.getAttribute("d")?.length || 0) > 3000) {
  324. prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`;
  325. }
  326. }
  327. });
  328. });
  329. // 文字方框和飞线碰撞
  330. staveSection.forEach((t) => {
  331. let shift_y = 0;
  332. [...vfcurve, ...vfties, ...vfvoices].forEach((curve) => {
  333. const result = collisionDetection(t, curve);
  334. if (result.isCollision) {
  335. shift_y = Math.min(shift_y, result.t2 - result.b1 - baseStep);
  336. }
  337. });
  338. t.style.transform = `translateY(${shift_y}px)`;
  339. });
  340. // 文字和小节碰撞
  341. let vftexts = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y < stafflineCenter);
  342. for (let i = 0; i < vftexts.length; i++) {
  343. const _text = vftexts[i];
  344. for (let j = 0; j < vftexts.length; j++) {
  345. if (_text.textContent === 'second time only') {
  346. // @ts-ignore
  347. _text.style.transform = `translateY(15px)`;
  348. }
  349. if (_text.parentNode === vftexts[j].parentNode) continue;
  350. const result = collisionDetection(_text as SVGAElement, vftexts[j] as SVGAElement);
  351. if (result.isCollision) {
  352. if (_text.textContent === vftexts[j].textContent) {
  353. vftexts[j].parentNode?.removeChild(vftexts[j]);
  354. continue;
  355. }
  356. }
  357. }
  358. }
  359. vftexts = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y < stafflineCenter);
  360. let maxY = 0;
  361. let _vftexts: SVGAElement[] = [];
  362. vftexts.forEach((vftext: any) => {
  363. const textBox = vftext.getBBox();
  364. if (textBox.y < stafflineCenter) {
  365. maxY = Math.max(maxY, textBox.y + textBox.height);
  366. //console.log('音阶间距',textBox.y, textBox.height)
  367. _vftexts.push(vftext as SVGAElement);
  368. }
  369. });
  370. if (maxY !== 0 && _vftexts.length > 1) {
  371. _vftexts.forEach((vftext) => {
  372. vftext.setAttribute("y", maxY + "");
  373. //console.log('音阶间距',maxY)
  374. });
  375. }
  376. vftexts.forEach((vftext) => {
  377. [...vfcurve, ...vfmeasures, ...vflines].forEach((vfmeasure) => {
  378. let result = collisionDetection(vftext as SVGAElement, vfmeasure);
  379. if (result.isCollision && result.b1 < result.b2 && result.t1 < result.b2 - (result.b2 - result.t2) / 2) {
  380. const shift_y = Number(vftext.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
  381. vftext.setAttribute("y", shift_y);
  382. //console.log('音阶间距',shift_y)
  383. }
  384. });
  385. });
  386. vftexts.forEach((vftext) => {
  387. vftexts.forEach((text) => {
  388. if (vftext.parentNode !== text.parentNode && !["marcato", "legato"].includes(vftext.textContent as string)) {
  389. if (["marcato", "legato"].includes(text.textContent as string)) {
  390. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30);
  391. if (result.isCollision) {
  392. const textBBox = (vftext as SVGAElement).getBBox();
  393. text.setAttribute("x", textBBox.x + textBBox.width + 5 + "");
  394. text.setAttribute("y", textBBox.y + textBBox.height - 5 + "");
  395. //console.log('音阶间距',textBBox.y + textBBox.height - 5 + "")
  396. }
  397. } else {
  398. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement);
  399. if (result.isCollision) {
  400. const _y = Number(vftext.getAttribute("y"));
  401. const shift_y = result.b2 - result.t2 < 24 ? 24 : result.b2 - result.t2;
  402. text.setAttribute("y", _y - shift_y - 0.5 + "");
  403. //console.log('音阶间距',_y - shift_y - 0.5 + "")
  404. }
  405. }
  406. }
  407. });
  408. });
  409. // 修改音阶和线谱的间距
  410. const clefList = ['C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb', 'D#', 'A#', 'E#']
  411. const btransList = ['Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb']
  412. const jtrsnsList = ['F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'B#']
  413. vftexts.forEach((label: any) => {
  414. const labelText = label.textContent as string
  415. if (clefList.includes(labelText)){
  416. const _y = Number(label.getAttribute("y"))
  417. const endY = firstLinePathY ? firstLinePathY - musicalDistance : _y
  418. label.setAttribute("y", endY)
  419. }
  420. if (btransList.includes(labelText)) {
  421. label.textContent = labelText.replace('b','♭')
  422. }
  423. if (jtrsnsList.includes(labelText)) {
  424. label.textContent = labelText.replace('#','♯')
  425. }
  426. });
  427. // numberVfTexts.forEach((label: any) => {
  428. // const labelText = label.textContent as string
  429. // if (isNumeric(labelText)) {
  430. // const _y = Number(label.getAttribute("y"))
  431. // const endY = firstLinePathY ? firstLinePathY - musicalDistance : _y
  432. // label.setAttribute("y", endY)
  433. // }
  434. // })
  435. const vftextBottom = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y > stafflineCenter);
  436. const vflineBottom = Array.from(staffline.querySelectorAll(".vf-line")).filter((n: any) => n.getBBox().y > stafflineCenter);
  437. // 去重
  438. for (let i = 0; i < vftextBottom.length; i++) {
  439. const _text = vftextBottom[i];
  440. for (let j = 0; j < vftextBottom.length; j++) {
  441. if (_text.parentNode === vftextBottom[j].parentNode) continue;
  442. const result = collisionDetection(_text as SVGAElement, vftextBottom[j] as SVGAElement);
  443. if (result.isCollision) {
  444. if (_text.textContent === vftextBottom[j].textContent) {
  445. vftextBottom[j].parentNode?.removeChild(vftextBottom[j]);
  446. continue;
  447. }
  448. }
  449. }
  450. }
  451. // 1,2线谱底部文字重叠问题
  452. vftextBottom.forEach((vftext) => {
  453. [...vfmeasures].forEach((n) => {
  454. let result = collisionDetection(vftext as SVGAElement, n);
  455. if (result.isCollision) {
  456. vftext.setAttribute("y", result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute("y"))) + "");
  457. //console.log('音阶间距', result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute("y"))) + "")
  458. }
  459. });
  460. });
  461. // 如果渐弱渐强有平行的文字
  462. vflineBottom.forEach((line) => {
  463. const texts: any[] = [];
  464. if (line.nextElementSibling?.classList.contains("vf-line")) {
  465. vftextBottom.forEach((text) => {
  466. let result = collisionDetection(line as SVGAElement, text as SVGAElement, 20, 20);
  467. if (result.isCollision) {
  468. texts.push({
  469. text: text as SVGAElement,
  470. result,
  471. });
  472. }
  473. });
  474. }
  475. if (texts.length === 1) {
  476. const result = texts[0].result;
  477. const text = texts[0].text;
  478. if (result.x2 + result.w2 < result.x1) {
  479. // 左
  480. if (Math.abs(result.y2 - result.y1) > 10) {
  481. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  482. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  483. }
  484. } else if (result.x2 > result.x1 + result.w1) {
  485. // 右
  486. if (Math.abs(result.y2 - result.y1) > 10) {
  487. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  488. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  489. }
  490. } else {
  491. if (Math.abs(result.x2 - result.x1) < Math.abs(result.x2 + result.w2 - result.x1 - result.w1)) {
  492. // console.log(text, '有交集', '靠左')
  493. text.setAttribute("x", result.x1 - result.w2 - 5 + "");
  494. if (Math.abs(result.y2 - result.y1) > 10) {
  495. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  496. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  497. }
  498. } else {
  499. // console.log(text, '有交集', '靠右')
  500. text.setAttribute("x", result.x1 + result.w1 + 5 + "");
  501. if (Math.abs(result.y2 - result.y1) > 10) {
  502. text.setAttribute("y", result.y1 + result.h2 / 2 + "");
  503. //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
  504. }
  505. }
  506. }
  507. } else if (texts.length === 2) {
  508. const result1 = texts[0].result;
  509. const text1 = texts[0].text;
  510. const result2 = texts[1].result;
  511. const text2 = texts[1].text;
  512. text1.setAttribute("x", result1.x1 - result1.w2 - 5 + "");
  513. if (Math.abs(result1.y2 - result1.y1) > 10) {
  514. text1.setAttribute("y", result1.y1 + result1.h2 / 2 + "");
  515. //console.log('音阶间距', result1.y1 + result1.h2 / 2 + "")
  516. }
  517. text2.setAttribute("x", result2.x1 + result2.w1 + 5 + "");
  518. if (Math.abs(result2.y2 - result2.y1) > 10) {
  519. text2.setAttribute("y", result2.y1 + result2.h2 / 2 + "");
  520. //console.log('音阶间距', result2.y1 + result2.h2 / 2 + "")
  521. }
  522. } else if (texts.length === 3) {
  523. // console.log(texts)
  524. }
  525. });
  526. vftextBottom.forEach((vftext) => {
  527. vftextBottom.forEach((text) => {
  528. if (vftext.parentNode !== text.parentNode && !["marcato", "legato", "cresc.", "Cantabile"].includes(vftext.textContent as string)) {
  529. if (["marcato", "legato", "cresc.", "Cantabile"].includes(text.textContent as string)) {
  530. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30);
  531. if (result.isCollision) {
  532. const textBBox = (vftext as SVGAElement).getBBox();
  533. text.setAttribute("x", textBBox.x + textBBox.width + 5 + "");
  534. text.setAttribute("y", textBBox.y + textBBox.height - 5 + "");
  535. //console.log('音阶间距', textBBox.y + textBBox.height - 5 + "")
  536. }
  537. } else {
  538. const result = collisionDetection(vftext as SVGAElement, text as SVGAElement);
  539. if (result.isCollision) {
  540. text.setAttribute("y", result.y1 + result.h1 + result.h2 + "");
  541. //console.log('音阶间距', result.y1 + result.h1 + result.h2 + "")
  542. }
  543. }
  544. }
  545. });
  546. });
  547. // 同一行内,多个飞线碰撞
  548. for (let index = 0; index < vfcurve.length; index++) {
  549. let nextIndex = index + 1;
  550. const cur = vfcurve[index];
  551. let next = vfcurve[nextIndex];
  552. let checkDone = false;
  553. while (nextIndex <= vfcurve.length - 1 && !checkDone) {
  554. let result = collisionDetection(cur, next);
  555. if (result.isCollision) {
  556. checkDone = true;
  557. next.style.transform = `translateY(-12px)`;
  558. } else {
  559. nextIndex = nextIndex + 1;
  560. next = vfcurve[nextIndex];
  561. }
  562. }
  563. };
  564. // 给小节添加背景色
  565. if (!state.isCreateImg && !state.isPreView) {
  566. staves.forEach((stave: any,i: number) => {
  567. const list = [
  568. Array.from(stave?.querySelectorAll(".vf-StaveSection") || []),
  569. Array.from(stave?.getElementsByTagName("text") || []),
  570. Array.from(stave?.querySelectorAll(".vf-Volta") || []),
  571. Array.from(stave?.querySelectorAll(".vf-clef") || []),
  572. Array.from(stave?.querySelectorAll(".vf-keysignature") || []),
  573. Array.from(stave?.querySelectorAll(".vf-Repetition") || []),
  574. ].flat();
  575. try {
  576. if (list.length) {
  577. list.forEach((_el: any) => {
  578. if (_el.parentNode === stave) {
  579. stave?.removeChild(_el)
  580. _el?.style?.setProperty("display", "none");
  581. }
  582. });
  583. }
  584. } catch (error) {}
  585. const bbox = stave?.getBBox() || {};
  586. const bgColor = 'transparent';
  587. const botColor = 'transparent';
  588. const rect = `<rect class="vf-custom-bg" x="${bbox.x}" y="${bbox.y}" width="${bbox.width}" height="${bbox.height}" fill=${bgColor} />`
  589. const rectBottom = `<rect class="vf-custom-bot" x="${bbox.x}" y="${bbox.y+bbox.height}" width="${bbox.width}" height="7.5" fill=${botColor} />`
  590. // const filterDom = `<defs>
  591. // <filter id="shadow">
  592. // <feDropShadow dx="5" dy="5" stdDeviation="3" flood-color="black" />
  593. // </filter>
  594. // </defs>`
  595. const customG = `<g>${rect}${rectBottom}</g>`
  596. try {
  597. if (list.length) {
  598. for(const _el of list) {
  599. if (_el?.parentElement?.classList?.contains('vf-StaveSection')) {
  600. continue;
  601. }
  602. stave?.appendChild(_el)
  603. _el?.style?.removeProperty("display");
  604. }
  605. }
  606. } catch (error) {}
  607. stave.innerHTML = customG + stave.innerHTML;
  608. });
  609. state.vfmeasures = state.vfmeasures.concat(vfmeasures);
  610. }
  611. dotModifiers.forEach((group: any) => {
  612. let parent = group?.parentElement; // 获取父元素
  613. // 如果需要找更外层的祖先元素,可以一直迭代
  614. while (parent && !parent.classList?.contains('vf-measure') && parent.tagName !== 'body' && parent) { // 假设你想找到最外层的 DIV
  615. parent = parent.parentElement;
  616. }
  617. const parentY = parent?.querySelector('.vf-custom-bg')?.getBoundingClientRect()?.y || 0;
  618. const dotY = group?.getBoundingClientRect()?.y || 0;
  619. const distanceY = parentY - dotY;
  620. const translateY = 15 - distanceY;
  621. // console.log('距离111',translateY)
  622. group.setAttribute('transform', `translate(3,${-translateY})`)
  623. // if (state.musicRenderType === 'fixedTone') {
  624. // // group.setAttribute('transform', 'translate(3,-12)')
  625. // } else {
  626. // // group.setAttribute('transform', 'translate(3,-7)')
  627. // }
  628. });
  629. // 修复D.C、D.S等渲染位置不对的问题
  630. const repairWord = ["D.S.", "D.C.", "Fine"];
  631. [...vfmeasures].forEach((measure: any) => {
  632. const needRepairTexts = measure.querySelectorAll('text').length ? Array.from(measure.querySelectorAll('text'))?.filter((item: any) => repairWord.includes(item?.textContent)) : [];
  633. if (needRepairTexts.length) {
  634. // 该小节结束位置的x坐标
  635. const measureCoordinate = measure?.querySelector('.vf-custom-bg')?.getBBox() || null
  636. const measureEndX = measureCoordinate ? measureCoordinate?.x + measureCoordinate?.width - 30 : 0;
  637. needRepairTexts.forEach((text: any) => {
  638. text?.setAttribute('x', measureEndX)
  639. })
  640. }
  641. });
  642. }
  643. if (!state.isCombineRender && state.isSingleLine) {
  644. transSinglePage();
  645. }
  646. // 多行谱,拍号可能被遮挡,需要移动谱面的位置
  647. if (!state.isSingleLine) {
  648. transMultiPosition();
  649. }
  650. // setTimeout(() => this.resetGlobalText());
  651. };
  652. // 一行谱时,五线谱/简谱的谱面staffLine,居中显示
  653. const transSinglePage = () => {
  654. if (state.isSingleLine && !state.isSimplePage) {
  655. const svgPage = document?.getElementById('osmdSvgPage1')?.getBoundingClientRect();
  656. const staffLine = document?.querySelector('.staffline')?.getBoundingClientRect();
  657. if (svgPage && staffLine && svgPage.height > 200) {
  658. // 需要上移的距离
  659. // console.log('need',svgPage.height,staffLine.height)
  660. const rate = svgPage.height > 400 ? 1.2 : 2;
  661. let needTransTop = (svgPage.height - staffLine.height) / rate;
  662. // 给个安全距离
  663. const maxTop = staffLine.top - svgPage.top - 40
  664. needTransTop = Math.min(maxTop, needTransTop)
  665. // @ts-ignore
  666. document.getElementById('osmdSvgPage1').style.transform = `translateY(-${needTransTop}px)`;
  667. // 一行谱需要同时偏移光标的位置
  668. const cursorDom = document.getElementById('cursorImg-0') || null;
  669. if (cursorDom) {
  670. cursorDom.style.transform = state.musicRenderType === 'staff' ? `translate(11Px, -${needTransTop}px)` : `translate(6.3Px, -${needTransTop}px)`;
  671. }
  672. // document.querySelector('.staffline').style.transform = `translateY(-${needTransTop}px)`;
  673. // const musicLine = document.querySelector('.staffline').querySelector('.vf-measure').querySelector('.vf-custom-bg').getBoundingClientRect();
  674. // const needTransDistance = svgPage.height / 2 - (musicLine.top - svgPage.top)
  675. // console.log('svg移动距离',needTransDistance)
  676. // // @ts-ignore
  677. // document.getElementById('osmdSvgPage1').style.transform = `translateY(${needTransDistance}px)`
  678. }
  679. }
  680. if (state.isSimplePage) {
  681. const svgPage = document?.getElementById('osmdSvgPage1')?.getBoundingClientRect();
  682. const staffLine = document?.querySelector('.staffline')?.getBoundingClientRect();
  683. if (svgPage && staffLine) {
  684. const needY = svgPage.height - (staffLine.y+staffLine.height) - 10;
  685. // @ts-ignore
  686. document.getElementById('osmdSvgPage1').style.transform = `translateY(${needY}px)`;
  687. // 一行谱需要同时偏移光标的位置
  688. const cursorDom = document.getElementById('cursorImg-0') || null;
  689. if (cursorDom) {
  690. cursorDom.style.transform = state.musicRenderType === 'staff' ? `translate(6Px, ${needY}px)` : `translate(6.3Px, ${needY}px)`;
  691. console.log('一行谱11111')
  692. }
  693. }
  694. }
  695. }
  696. const transMultiPosition = () => {
  697. const svgPage = document?.getElementById('osmdSvgPage1')?.getBoundingClientRect();
  698. const staffLine = document?.querySelector('.staffline')?.getBoundingClientRect();
  699. if (svgPage && staffLine && staffLine.y < svgPage.y) {
  700. const needY = svgPage.y - staffLine.y + 5;
  701. // @ts-ignore
  702. document.querySelector('.staffline').style.transform = `translateY(${needY}px)`;
  703. }
  704. }
  705. // 技巧文本
  706. const resetGlobalText = () => {
  707. const svg = container.value.querySelector("svg");
  708. if (!svg) return;
  709. const svgBBox = svg.getBBox();
  710. let vfstavetempo: SVGAElement[] = Array.from(container.value.querySelectorAll(".vf-stavetempo")).reduce((eles: SVGAElement[], value: any) => {
  711. if (eles.find((n) => n.outerHTML === value.outerHTML)) value?.parentNode?.removeChild(value);
  712. else eles.push(value);
  713. return eles;
  714. }, []);
  715. const staffline: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline"));
  716. const vfmeasures: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-measure"));
  717. const vftexts: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-text"));
  718. const vfcurves: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-curve"));
  719. vfstavetempo.forEach((child: SVGAElement) => {
  720. let _y = 0;
  721. [...vfmeasures, ...vftexts, ...vfcurves].forEach((ele) => {
  722. const result = collisionDetection(child as SVGAElement, ele);
  723. if (result.isCollision && (result.b1 < result.b2 || result.r1 > result.l2 || result.l1 < result.r2)) {
  724. _y = Math.min(_y, result.t2 - result.b1);
  725. }
  726. });
  727. if (_y !== 0) {
  728. child.style.transform = `translateY(${_y}px)`;
  729. }
  730. const childBBox = child.getBBox();
  731. const rightY = (childBBox.x + childBBox.width) * 0.7 - Number(svg.getAttribute("width"));
  732. if (rightY > 0) {
  733. [...staffline, ...vfstavetempo].forEach((tempo) => {
  734. if (child != tempo) {
  735. const result = collisionDetection(child as SVGAElement, tempo, Math.abs(rightY), Math.abs(_y));
  736. if (result.isCollision) {
  737. _y = result.t2 - result.b1;
  738. }
  739. }
  740. });
  741. child.style.transform = `translate(-${rightY / 0.7}px,${_y}px)`;
  742. }
  743. });
  744. if (svgBBox.y < 0) {
  745. svg.setAttribute("height", Number(svg.getAttribute("height")) - svgBBox.y + 10);
  746. }
  747. };
  748. // 碰撞检测
  749. const collisionDetection = (a: SVGAElement, b: SVGAElement, distance: number = 0, distance_y: number = 0) => {
  750. const abbox = a.getBBox();
  751. const bbbox = b.getBBox();
  752. let t1 = abbox.y - distance_y;
  753. let l1 = abbox.x - distance;
  754. let r1 = abbox.x + abbox.width + distance;
  755. let b1 = abbox.y + abbox.height + distance_y;
  756. let t2 = bbbox.y;
  757. let l2 = bbbox.x;
  758. let r2 = bbbox.x + bbbox.width;
  759. let b2 = bbbox.y + bbbox.height;
  760. if (b1 < t2 || l1 > r2 || t1 > b2 || r1 < l2) {
  761. // 表示没碰上
  762. return {
  763. isCollision: false,
  764. t1,
  765. l1,
  766. r1,
  767. b1,
  768. t2,
  769. l2,
  770. r2,
  771. b2,
  772. x1: abbox.x,
  773. y1: abbox.y,
  774. x2: bbbox.x,
  775. y2: bbbox.y,
  776. h1: abbox.height,
  777. h2: bbbox.height,
  778. w1: abbox.width,
  779. w2: bbbox.width,
  780. };
  781. } else {
  782. return {
  783. isCollision: true,
  784. t1,
  785. l1,
  786. r1,
  787. b1,
  788. t2,
  789. l2,
  790. r2,
  791. b2,
  792. x1: abbox.x,
  793. y1: abbox.y,
  794. x2: bbbox.x,
  795. y2: bbbox.y,
  796. h1: abbox.height,
  797. h2: bbbox.height,
  798. w1: abbox.width,
  799. w2: bbbox.width,
  800. };
  801. }
  802. };
  803. /** 全局曲谱配置 */
  804. export const setGlobalMusicSheet = () => {
  805. const partIndex = state.partIndex + ""
  806. /** 延音线方向问题 start */
  807. const stavetieList = [
  808. {id: '12644', part_index: '25', direction: 1}
  809. ]
  810. const tieItem = stavetieList.find(({id, part_index}) => {
  811. return id == state.cbsExamSongId && part_index == partIndex
  812. })
  813. setGlobalData('tieDirection', tieItem ? tieItem.direction : undefined)
  814. /** 延音线方向问题 end */
  815. const graceList = [
  816. {id: '3509', part_index: '16', direction: 1}
  817. ]
  818. const graceItem = graceList.find(({id, part_index}) => {
  819. return id == state.cbsExamSongId && part_index == partIndex
  820. })
  821. if (graceItem){
  822. setGlobalData('graceCustom', {direction: graceItem.direction})
  823. }
  824. const bassDrumList = [
  825. {id: '3030', part_index: '17', line: 4},
  826. {id: '12704', part_index: '23', line: 3}
  827. ]
  828. const bassDrumItem = bassDrumList.find(({id, part_index}) => {
  829. return id == state.cbsExamSongId && part_index == partIndex
  830. })
  831. if (bassDrumItem){
  832. setGlobalData('customBassDrum', bassDrumItem.line)
  833. }
  834. /** 打击乐多声部,双声部休止符重叠 end */
  835. /** 符杆朝向 */
  836. const stemDirectionList = [
  837. {
  838. id: '11654',
  839. part_index: '16',
  840. stemNotes: [
  841. {id: 124, direction: 0},
  842. {id: 125, direction: 0},
  843. {id: 126, direction: 0},
  844. {id: 127, direction: 0},
  845. {id: 128, direction: 0}
  846. ]
  847. },
  848. {
  849. id: '3581',
  850. part_index: '4',
  851. stemNotes: [
  852. {id: 380, direction: 1},
  853. ]
  854. },
  855. {
  856. id: '3470',
  857. part_index: '0',
  858. stemNotes: [
  859. {id: 36, direction: 1},
  860. {id: 37, direction: 1},
  861. ]
  862. },
  863. {
  864. id: '3470',
  865. part_index: '11',
  866. stemNotes: [
  867. {id: 33, direction: 1},
  868. {id: 56, direction: 1},
  869. ]
  870. },
  871. {
  872. id: '12644',
  873. part_index: '22',
  874. stemNotes: [
  875. {id: 22, direction: 1},
  876. {id: 26, direction: 1},
  877. {id: 135, direction: 1},
  878. {id: 163, direction: 1},
  879. {id: 199, direction: 1},
  880. {id: 204, direction: 1},
  881. {id: 206, direction: 1},
  882. {id: 208, direction: 1},
  883. {id: 210, direction: 1},
  884. {id: 213, direction: 1},
  885. ]
  886. },
  887. {
  888. id: '12303',
  889. part_index: '18',
  890. stemNotes: [
  891. {id: 1, direction: 1},
  892. {id: 4, direction: 1},
  893. {id: 6, direction: 1},
  894. {id: 9, direction: 1},
  895. {id: 12, direction: 1},
  896. {id: 14, direction: 1},
  897. ]
  898. },
  899. {
  900. id: '12669',
  901. part_index: '24',
  902. stemNotes: [
  903. {id: 65, direction: 1},
  904. {id: 296, direction: 1},
  905. {id: 298, direction: 1},
  906. {id: 300, direction: 1},
  907. {id: 338, direction: 1},
  908. ]
  909. },
  910. {
  911. id: '12420',
  912. part_index: '21',
  913. stemNotes: [
  914. {id: 614, direction: 0},
  915. {id: 617, direction: 0},
  916. {id: 619, direction: 0},
  917. {id: 621, direction: 0},
  918. ]
  919. },
  920. {
  921. id: '12711',
  922. part_index: '22',
  923. stemNotes: []
  924. },
  925. {
  926. id: '12973',
  927. part_index: '21',
  928. stemNotes: [
  929. {id: 619, direction: 1},
  930. {id: 622, direction: 1},
  931. {id: 745, direction: 1},
  932. ]
  933. },
  934. ]
  935. const stemDirectionItem = stemDirectionList.find(({id, part_index}) => {
  936. return id == state.cbsExamSongId && part_index == partIndex
  937. })
  938. if (stemDirectionItem) {
  939. setGlobalData('stemDirectionNote', stemDirectionItem.stemNotes)
  940. }
  941. /** vfcure */
  942. const vfcurveList = [
  943. {
  944. id: '12711',
  945. part_index: '4',
  946. vfcurve: [
  947. {MeasureNumberXML: 25, index: 1, bezierEndControlPt: {y: -2}},
  948. {MeasureNumberXML: 33, index: 1, bezierEndControlPt: {y: -2}},
  949. ]
  950. },
  951. {
  952. id: '12059',
  953. part_index: '0',
  954. vfcurve: [
  955. {MeasureNumberXML: 15, bezierEndControlPt: {y: 2.8}, bezierEndPt:{y: 1.1}},
  956. {MeasureNumberXML: 16, bezierEndControlPt: {y: -1}},
  957. {MeasureNumberXML: 19, index: 1, bezierEndControlPt: {y: 2}},
  958. {MeasureNumberXML: 20, bezierEndControlPt: {y: -1}},
  959. {MeasureNumberXML: 42, index: 1, bezierEndControlPt: {y: -1.5}, bezierStartControlPt: {y: -1.5}},
  960. {MeasureNumberXML: 46, index: 3, bezierEndControlPt: {y: -1.5}, bezierStartControlPt: {y: -1.5}},
  961. ]
  962. },
  963. {
  964. id: '12668',
  965. part_index: '11',
  966. vfcurve: [
  967. {MeasureNumberXML: 8, index: 2, bezierEndControlPt: {y: -3}, bezierStartControlPt:{y: -3}, bezierEndPt:{y: -1}},
  968. ]
  969. },
  970. {
  971. id: '11976',
  972. part_index: '0',
  973. vfcurve: [
  974. {MeasureNumberXML: 14, index: 4, bezierEndControlPt: {y: -3}},
  975. {MeasureNumberXML: 14, index: 1, bezierEndPt: {y: 1.5}, bezierEndControlPt: {y: 1}},
  976. ]
  977. },
  978. ]
  979. const vfcurveItem = vfcurveList.find(({id, part_index}) => {
  980. return id == state.cbsExamSongId && part_index == partIndex
  981. })
  982. if (vfcurveItem) {
  983. setGlobalData('vfcurveItem', vfcurveItem.vfcurve)
  984. }
  985. /** drum set声部 重音 */
  986. const customArtPositionList = [
  987. {id: '12644', part_index: '25'}
  988. ]
  989. const customArtPositionItem = customArtPositionList.find(({id, part_index}) => {
  990. return id == state.cbsExamSongId && part_index == partIndex
  991. })
  992. if (customArtPositionItem) {
  993. setGlobalData('customArtPosition', true)
  994. }
  995. /** 全声部声部 - & 全音符 */
  996. const customTenutoList = [
  997. {id: '12645', part_index: '5'}
  998. ]
  999. const customTenutoItem = customTenutoList.find(({id, part_index}) => {
  1000. return id == state.cbsExamSongId && part_index == partIndex
  1001. })
  1002. if (customTenutoItem) {
  1003. setGlobalData('customTenutoItem', true)
  1004. }
  1005. /** 全声部声部 > */
  1006. const customAccentList = [
  1007. {id: '12711', part_index: '22'},
  1008. {id: '12711', part_index: '25'},
  1009. ]
  1010. const customAccentItem = customAccentList.find(({id, part_index}) => {
  1011. return id == state.cbsExamSongId && part_index == partIndex
  1012. })
  1013. if (customAccentItem || state.isEvxml) {
  1014. setGlobalData('customAccentItem', true)
  1015. }
  1016. /** 全声部声部 + */
  1017. const customLefthandpizzicatoList = [
  1018. {id: '12711', part_index: '25'},
  1019. {id: '7755', part_index: '10'},
  1020. {id: '6226', part_index: '16'},
  1021. ]
  1022. const customLefthandpizzicatoItem = customLefthandpizzicatoList.find(({id, part_index}) => {
  1023. return id == state.cbsExamSongId && part_index == partIndex
  1024. })
  1025. if (customLefthandpizzicatoItem) {
  1026. setGlobalData('customLefthandpizzicatoItem', true)
  1027. }
  1028. }
  1029. /** 设置自定义音符数据 */
  1030. export const setCustomNoteRealValue = () => {
  1031. const detailId = state.cbsExamSongId + "";
  1032. if (["12667", "12673"].includes(detailId)){
  1033. customData.customNoteCurrentTime = true
  1034. }
  1035. };
  1036. /** 转换简谱的全休止符和二分休止符 */
  1037. export const transferJianNote = (measure: any, divisions: number, preBeats: number, preBeatType: number) => {
  1038. const multipleXs = preBeatType / 4;
  1039. const notes = measure.getElementsByTagName("note")
  1040. for (const note of notes) {
  1041. // 是否需要考虑带上附点
  1042. let needAddDot = true;
  1043. const noteType = note.getElementsByTagName("type")?.[0]?.textContent || '';
  1044. if ((noteType === 'whole' || noteType === 'half') && note.getElementsByTagName("rest").length) {
  1045. // 4/4拍,3/4拍
  1046. if (preBeatType === 4) {
  1047. let maxNumber = noteType === 'half' ? 2 : preBeats / multipleXs;
  1048. if (noteType === 'whole') {
  1049. // 有可能是全休止符,但是该小节又不是整小节都休止,此时这个全休止符不能按照整小节休止来计算
  1050. const noteDivisions = parseInt(note.getElementsByTagName("duration")[0]?.textContent);
  1051. if (noteDivisions/divisions !== preBeats) {
  1052. maxNumber = 4;
  1053. } else {
  1054. // 满足了时值,则不需要考虑加上附点
  1055. needAddDot = false;
  1056. }
  1057. }
  1058. // 如果音符带附点,需要判断处理下
  1059. if (note.getElementsByTagName("dot").length && needAddDot) {
  1060. maxNumber = noteType === 'whole' ? maxNumber + 2 : maxNumber + 1;
  1061. }
  1062. if (!Number.isInteger(maxNumber)) {
  1063. return;
  1064. }
  1065. // console.log('几个1/4音符',maxNumber)
  1066. let quarterNoteNumber = 1;
  1067. while (quarterNoteNumber <= maxNumber) {
  1068. const newnote = document.createElement('note');
  1069. newnote.innerHTML = `
  1070. <rest></rest>
  1071. <duration>${divisions}</duration>
  1072. <voice>1</voice>
  1073. <type>quarter</type>`
  1074. measure.insertBefore(newnote, note);
  1075. quarterNoteNumber += 1;
  1076. };
  1077. measure.removeChild(note);
  1078. } else if (preBeats === 3 && preBeatType === 8) {
  1079. const maxNumber = noteType === 'half' ? 2 : 3;
  1080. let quarterNoteNumber = 1;
  1081. while (quarterNoteNumber <= maxNumber) {
  1082. const newnote = document.createElement('note');
  1083. newnote.innerHTML = `
  1084. <rest></rest>
  1085. <duration>${divisions/2}</duration>
  1086. <voice>1</voice>
  1087. <type>eighth</type>`
  1088. measure.insertBefore(newnote, note);
  1089. quarterNoteNumber += 1;
  1090. };
  1091. measure.removeChild(note);
  1092. } else if (preBeats === 5 && preBeatType === 8) {
  1093. if (noteType === 'whole') {
  1094. const newnote = document.createElement('note');
  1095. newnote.innerHTML = `
  1096. <rest></rest>
  1097. <duration>${divisions+divisions/2}</duration>
  1098. <voice>1</voice>
  1099. <type>quarter</type>
  1100. <dot></dot>`
  1101. measure.insertBefore(newnote, note);
  1102. const newnote2 = document.createElement('note');
  1103. newnote2.innerHTML = `
  1104. <rest></rest>
  1105. <duration>${divisions}</duration>
  1106. <voice>1</voice>
  1107. <type>quarter</type>`
  1108. measure.insertBefore(newnote2, note);
  1109. measure.removeChild(note);
  1110. } else if (noteType === 'half') {
  1111. dealDotHalfNote(measure, divisions, note)
  1112. }
  1113. } else if (preBeats === 6 && preBeatType === 8) {
  1114. if (noteType === 'whole') {
  1115. const maxNumber = 2;
  1116. let quarterNoteNumber = 1;
  1117. while (quarterNoteNumber <= maxNumber) {
  1118. const newnote = document.createElement('note');
  1119. newnote.innerHTML = `
  1120. <rest></rest>
  1121. <duration>${divisions+divisions/2}</duration>
  1122. <voice>1</voice>
  1123. <type>quarter</type>
  1124. <dot></dot>`
  1125. measure.insertBefore(newnote, note);
  1126. quarterNoteNumber += 1;
  1127. };
  1128. measure.removeChild(note);
  1129. } else if (noteType === 'half') {
  1130. dealDotHalfNote(measure, divisions, note)
  1131. }
  1132. } else if (preBeats === 7 && preBeatType === 8) {
  1133. if (noteType === 'whole') {
  1134. const newnote2 = document.createElement('note');
  1135. newnote2.innerHTML = `
  1136. <rest></rest>
  1137. <duration>${divisions+divisions/2}</duration>
  1138. <voice>1</voice>
  1139. <type>quarter</type>
  1140. <dot></dot>`
  1141. measure.insertBefore(newnote2, note);
  1142. const maxNumber = 2;
  1143. let quarterNoteNumber = 1;
  1144. while (quarterNoteNumber <= maxNumber) {
  1145. const newnote = document.createElement('note');
  1146. newnote.innerHTML = `
  1147. <rest></rest>
  1148. <duration>${divisions}</duration>
  1149. <voice>1</voice>
  1150. <type>quarter</type>`
  1151. measure.insertBefore(newnote, note);
  1152. quarterNoteNumber += 1;
  1153. };
  1154. measure.removeChild(note);
  1155. } else if (noteType === 'half') {
  1156. dealDotHalfNote(measure, divisions, note)
  1157. }
  1158. } else if (preBeats === 9 && preBeatType === 8) {
  1159. if (noteType === 'whole') {
  1160. const maxNumber = 3;
  1161. let quarterNoteNumber = 1;
  1162. while (quarterNoteNumber <= maxNumber) {
  1163. const newnote = document.createElement('note');
  1164. newnote.innerHTML = `
  1165. <rest></rest>
  1166. <duration>${divisions+divisions/2}</duration>
  1167. <voice>1</voice>
  1168. <type>quarter</type>
  1169. <dot></dot>`
  1170. measure.insertBefore(newnote, note);
  1171. quarterNoteNumber += 1;
  1172. };
  1173. measure.removeChild(note);
  1174. } else if (noteType === 'half') {
  1175. dealDotHalfNote(measure, divisions, note)
  1176. }
  1177. } else if (preBeats === 12 && preBeatType === 8) {
  1178. if (noteType === 'whole') {
  1179. const maxNumber = 4;
  1180. let quarterNoteNumber = 1;
  1181. while (quarterNoteNumber <= maxNumber) {
  1182. const newnote = document.createElement('note');
  1183. newnote.innerHTML = `
  1184. <rest></rest>
  1185. <duration>${divisions+divisions/2}</duration>
  1186. <voice>1</voice>
  1187. <type>quarter</type>
  1188. <dot></dot>`
  1189. measure.insertBefore(newnote, note);
  1190. quarterNoteNumber += 1;
  1191. };
  1192. measure.removeChild(note);
  1193. } else if (noteType === 'half') {
  1194. dealDotHalfNote(measure, divisions, note)
  1195. }
  1196. }
  1197. }
  1198. }
  1199. }
  1200. /** 八几排的小节,二分休止符带附点 */
  1201. const dealDotHalfNote = (measure: any, divisions: number, note: any) => {
  1202. // 如果音符带附点,需要判断处理下
  1203. if (note.getElementsByTagName("dot").length) {
  1204. const maxNumber = 2;
  1205. let quarterNoteNumber = 1;
  1206. while (quarterNoteNumber <= maxNumber) {
  1207. const newnote = document.createElement('note');
  1208. newnote.innerHTML = `
  1209. <rest></rest>
  1210. <duration>${divisions+divisions/2}</duration>
  1211. <voice>1</voice>
  1212. <type>quarter</type>
  1213. <dot></dot>`
  1214. measure.insertBefore(newnote, note);
  1215. quarterNoteNumber += 1;
  1216. };
  1217. measure.removeChild(note);
  1218. } else {
  1219. const newnote = document.createElement('note');
  1220. newnote.innerHTML = `
  1221. <rest></rest>
  1222. <duration>${divisions+divisions/2}</duration>
  1223. <voice>1</voice>
  1224. <type>quarter</type>
  1225. <dot></dot>`
  1226. measure.insertBefore(newnote, note);
  1227. const newnote2 = document.createElement('note');
  1228. newnote2.innerHTML = `
  1229. <rest></rest>
  1230. <duration>${divisions/2}</duration>
  1231. <voice>1</voice>
  1232. <type>eighth</type>`
  1233. measure.insertBefore(newnote2, note);
  1234. measure.removeChild(note);
  1235. }
  1236. }