import { ref } from "vue"; import state, { customData } from "../state" import { getQuery } from "/src/utils/queryString"; import { setGlobalData } from "/src/utils"; const query: any = getQuery(); interface IItem { id?: string y?: number isLast?: boolean childIndex?: number[] } interface IItemList { parts: string[], tieId?: string[], staveSection?: IItem[], vfmodifiers?: IItem[], voltas?: number vfcurve?: IItem[] stavenote?: IItem[] } interface IMusicList { [_key: string]: IItemList[] } const container = ref(); /** 曲谱配置: 重叠 */ export const resetGivenFormate = () => { interface IItem { id?: string y?: number isLast?: boolean childIndex?: number[] } interface IItemList { parts: string[], tieId?: string[], staveSection?: IItem[], vfmodifiers?: IItem[], voltas?: number vfcurve?: IItem[] stavenote?: IItem[] } interface IMusicList { [_key: string]: IItemList[] } const musicList: IMusicList = { '12200': [ {parts: ['0', '1'], tieId: ['1483']}, {parts: ['2'], tieId: ['1463']}, {parts: ['10'], tieId: ['1246']}, {parts: ['11'], tieId: ['2455']}, {parts: ['13'], tieId: ['1488', '1688']}, {parts: ['14', '15'], tieId: ['1272']}, {parts: ['16'], tieId: ['1264', '1368'], staveSection: [{id: 'section-0', y: -10}]}, ], '12420': [ {parts: ['0'], tieId: ['1298', '1405', '1998', '2598', '3229', '2731', '2617']} ], '7729': [ {parts: ['3'], tieId: ['1498', '1660']} ], '7439': [ {parts: ['23'], vfmodifiers: [{id: 'modifiers-130', y: -18, isLast: true}]} ], '12711': [ { parts: ['0'], voltas: -12}, { parts: ['4'],voltas: -8}, ], '3581': [ { parts: ['0'], voltas: -8}, ], '6244': [ { parts: ['15'], stavenote: [{id: 'vf-auto1608', y: -15}]}, ], '7473': [ { parts: ['0'], voltas: -8}, ] } const tieList = musicList[state.cbsExamSongId as string] if (tieList) { const partIndex = state.partIndex + "" const tie = tieList.find((item) => item.parts.includes(partIndex)) if (!tie) return // 延音线和连线重叠 if (tie.tieId && tie.tieId.length) { for(let tieIndex = 0; tieIndex < tie.tieId.length; tieIndex++){ const vftie: any = document.querySelector(`#vf-auto${tie.tieId[tieIndex]}-tie`) const vfcurve = vftie?.parentNode?.parentNode?.querySelectorAll('.vf-curve') if (vfcurve && vfcurve.length){ for(let i = 0; i < vfcurve.length; i++){ const result = collisionDetection(vftie, vfcurve[i]) if (result.isCollision){ vfcurve[i].style.transform = `translateY(-8px)`; break; } } } } } // 小节数字 if (tie.staveSection && tie.staveSection.length) { const sectionList = document.querySelectorAll('.vf-StaveSection') sectionList.forEach((node, index) => { node.classList.add(`section-${index}`) }) for(let i = 0; i < tie.staveSection.length; i++){ const item: any = document.querySelector( '.' + tie.staveSection[i].id) if (item){ item.style.transform = `translateY(${tie.staveSection[i].y}px)`; } } } // modifiers 里面的符号 if(tie.vfmodifiers && tie.vfmodifiers.length){ const modifierList = document.querySelectorAll('.vf-modifiers') modifierList.forEach((node, index) => { node.classList.add(`modifiers-${index}`) }) for(let i = 0; i < tie.vfmodifiers.length; i++){ const modifier = tie.vfmodifiers[i] const item: SVGAElement = document.querySelector( '.' + modifier.id)! if (item){ if (modifier.isLast){ const lastEle: any = Array.from(item.childNodes).at(-1) if (lastEle){ lastEle.style.transform = `translateY(${modifier.y}px)`; } } } } } // 房子 if (tie.voltas){ const modifierList = document.querySelectorAll('.vf-Volta') as unknown as HTMLElement[] modifierList.forEach((node, index) => { node.style.transform = `translateY(${tie.voltas}px)`; }) } // 单个音符 if (tie.stavenote && tie.stavenote.length) { for(let i = 0; i < tie.stavenote.length; i++){ const item = tie.stavenote[i] const ele = document.querySelector('#' + item.id)! as unknown as HTMLElement ele && (ele.style.transform = `translateY(${item.y}px)`) } } } }; const initNoteCoord = () => { const allNoteDot: any = Array.from(document.querySelectorAll('.node-dot')); state.noteCoords = allNoteDot.map((note: any) => { const note_bbox = note?.getBoundingClientRect?.() || { x: 0, y: 0 }; return { x: note_bbox.x, y: note_bbox.y } }) console.log(11111,state.noteCoords) } export const moveGracePosition = (needTrans?: boolean) => { /** * TODO:曲目:摇篮曲(节奏练习)-倚音位置 特殊处理 */ const specialIds = ['1788850864767643649','1788502467554750466','1789839575249596417','1788501975122489346','1796006876341813249']; if (specialIds.includes(state.cbsExamSongId) || needTrans) { const lastCurve: any = Array.from(document.getElementsByClassName('vf-curve'))?.last(); if (lastCurve) { lastCurve.style.display = 'none'; } if (state.musicRenderType === 'staff') { // const transNoteDom = document.getElementById('vf-auto2182')?.getElementsByClassName('vf-modifiers')?.[0]; // const transBeamDom = document.getElementById('auto3167')?.parentNode?.getElementsByClassName('vf-beams')?.[0]; // if (transNoteDom) { // transNoteDom.style.transform = 'translateX(-0.5rem)'; // } // if (transBeamDom) { // transBeamDom.style.transform = 'translateX(-0.5rem)'; // } } else { // vf-auto2172 , vf-auto2384 const signatureDom = document.getElementById('auto2670'); const signatureDom2 = document.getElementById('auto2710'); const signatureDom3 = document.getElementById('auto3099'); const signatureDom4 = document.getElementById('auto3339'); const needTransLateDom: any = state.cbsExamSongId == '1789839575249596417' && document.getElementById('vf-auto1554')?.getElementsByClassName('vf-modifier')?.[0]; const arrowDom = state.cbsExamSongId == '1789839575249596417' && document.getElementById('vf-auto1554-lines'); const needTransLateDom2: any = state.cbsExamSongId == '1788501975122489346' && document.getElementById('vf-auto2116')?.getElementsByClassName('vf-modifier')?.[0]; const arrowDom2 = state.cbsExamSongId == '1788501975122489346' && document.getElementById('vf-auto2116-lines'); const needTransLateDom3: any = state.cbsExamSongId == '1788502467554750466' && document.getElementById('vf-auto2122')?.getElementsByClassName('vf-modifier')?.[0]; const arrowDom3 = state.cbsExamSongId == '1788502467554750466' && document.getElementById('vf-auto2122-lines'); if (signatureDom) signatureDom.style.display = 'none'; if (signatureDom2) signatureDom2.style.display = 'none'; if (signatureDom3) signatureDom3.style.display = 'none'; if (signatureDom4) signatureDom4.style.display = 'none'; if (needTransLateDom) needTransLateDom.style.transform = 'translateX(-0.65rem)'; if (needTransLateDom2) needTransLateDom2.style.transform = 'translateX(-0.65rem)'; if (needTransLateDom3) needTransLateDom3.style.transform = 'translateX(-0.65rem)'; if (arrowDom) arrowDom.style.transform = 'translateX(-0.65rem)'; if (arrowDom2) arrowDom2.style.transform = 'translateX(-0.65rem)'; if (arrowDom3) arrowDom3.style.transform = 'translateX(-0.65rem)'; if (arrowDom || arrowDom2 || arrowDom3) { const path: any = arrowDom ? arrowDom.querySelector('path') : arrowDom2 ? arrowDom2.querySelector('path') : arrowDom3 ? arrowDom3.querySelector('path') : null; let d = path?.getAttribute("d"); if (d) { const patchStr = d.split('L')?.last()?.split(" ")?.[0]; let startX = d.split("M")?.[1]?.split(" ")[0] || 0; startX = startX ? Number(startX) : 0; let endX = d.split("L")?.last().split(" ")[0] || 0; endX = endX ? Number(endX) : 0; const distanceX = endX - startX; const transX = startX - distanceX; d = d.replace(`L${patchStr}`,`L${transX}`); path.setAttribute("d", d); } } } } } // 处理一行谱高度太高的问题 export const limitSingleSvgPageHeight = () => { if (state.isSingleLine && !state.isCombineRender) { // 获取生成的 SVG 元素 const osmdSvgPage = document.getElementById('osmdSvgPage1'); if (osmdSvgPage) { // 获取当前的 viewBox 值 const viewBox = osmdSvgPage.getAttribute('viewBox'); if (viewBox) { // 分割 viewBox 的值,viewBox 形式为 "min-x min-y width height" const viewBoxValues = viewBox.split(' '); // 修改 viewBox 高度 const newHeight = 300; const staffHeight = osmdSvgPage.querySelector('.staffline')?.getBoundingClientRect()?.height; if (viewBoxValues[3] > 300 && staffHeight && staffHeight < 240) { const originY = state.isSimplePage ? 0 : 0.25; let transY = (240 - staffHeight * state.zoom) / staffHeight; transY = state.isSimplePage ? transY / 2 : transY; const realY = (originY + transY) * 100 + '%'; viewBoxValues[3] = newHeight; // 这是第四个值,代表高度 // 设置新的 viewBox 值 osmdSvgPage.setAttribute('viewBox', viewBoxValues.join(' ')); osmdSvgPage.setAttribute('height','240'); // @ts-ignore osmdSvgPage.querySelector('.staffline').style.transform = `translateY(-${realY})` } } } } } const isElementInViewport = (el: any) => { const rect = el.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } const isNumeric = (str: any) => { return /^\d+$/.test(str); } // 谱面优化 export const resetFormate = () => { container.value = document.getElementById('scrollContainer') // if (state.extStyleConfigJson || !container.value) return; if (!container.value) return; moveGracePosition(); // setTimeout(() => { // initNoteCoord(); // }, 0); const stafflines: SVGAElement[] = Array.from((container.value as HTMLElement).querySelectorAll(".staffline")); const baseStep = 4; // 两个元素相间,的间距 const musicalDistance = 28; // 音阶与第一条线谱的间距,默认设置为28 for (let i = 0, len = stafflines.length; i < len; i++) { const staffline = stafflines[i]; const stafflineBox = staffline.getBBox(); const stafflineCenter = stafflineBox.y + stafflineBox.height / 2; const vfmeasures: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure")); const vfcurve: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-curve")); const vfvoices: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-voices")); const vfbeams: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-beams")); const vfties: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-ties")); const vflines: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-line")); const texts: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave text")); const rects: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave rect[fill=none]")); const staveSection: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-staveSection")); const paths: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave path")); const dotModifiers: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-stopDot")); const staves: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave")); const numberVfTexts = Array.from((container.value as HTMLElement).querySelectorAll(".vf-text > text")); // 获取第一个线谱的y轴坐标 const firstLinePathY = paths[0]?.getBBox().y || 0 // D.C循环标记没有显示完全修复 // 反复标记 和 小节碰撞 const repetWord = ["To Coda", "D.S. al Coda", "Coda", "D.C."]; texts .filter((n) => repetWord.includes(n.textContent || "")) .forEach((t) => { // console.log('文本123',t.textContent,'是否在可视区域内',isElementInViewport(t)) // D.C循环标记不在可视区域内,需要修复移动其位置信息 // if (t.textContent?.includes('D.C')) { // if (!isElementInViewport(t)) { // t.style.transform = `translateX(-40px)`; // } // } vfbeams.forEach((curve) => { const result = collisionDetection(t, curve); const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement; if (result.isCollision) { const shift_y = Number(t.getAttribute("y")) - (result.b1 - result.t2) - baseStep + ""; t.setAttribute("y", shift_y); // console.log('音阶间距',shift_y) if (prePath && prePath.getAttribute("stroke-width") === "0.3" && prePath.getAttribute("stroke") === "none" && (prePath.getAttribute("d")?.length || 0) > 3000) { prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`; } } }); vfvoices.forEach((curve) => { const result = collisionDetection(t, curve); const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement; if (result.isCollision) { const shift_y = Number(t.getAttribute("y")) - (result.b1 - result.t2) - baseStep + ""; t.setAttribute("y", shift_y); // console.log('音阶间距',shift_y) if (prePath && prePath.getAttribute("stroke-width") === "0.3" && prePath.getAttribute("stroke") === "none" && (prePath.getAttribute("d")?.length || 0) > 3000) { prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`; } } }); }); // 文字方框和飞线碰撞 staveSection.forEach((t) => { let shift_y = 0; [...vfcurve, ...vfties, ...vfvoices].forEach((curve) => { const result = collisionDetection(t, curve); if (result.isCollision) { shift_y = Math.min(shift_y, result.t2 - result.b1 - baseStep); } }); t.style.transform = `translateY(${shift_y}px)`; }); // 文字和小节碰撞 let vftexts = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y < stafflineCenter); for (let i = 0; i < vftexts.length; i++) { const _text = vftexts[i]; for (let j = 0; j < vftexts.length; j++) { if (_text.textContent === 'second time only') { // @ts-ignore _text.style.transform = `translateY(15px)`; } if (_text.parentNode === vftexts[j].parentNode) continue; const result = collisionDetection(_text as SVGAElement, vftexts[j] as SVGAElement); if (result.isCollision) { if (_text.textContent === vftexts[j].textContent) { vftexts[j].parentNode?.removeChild(vftexts[j]); continue; } } } } vftexts = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y < stafflineCenter); let maxY = 0; let _vftexts: SVGAElement[] = []; vftexts.forEach((vftext: any) => { const textBox = vftext.getBBox(); if (textBox.y < stafflineCenter) { maxY = Math.max(maxY, textBox.y + textBox.height); //console.log('音阶间距',textBox.y, textBox.height) _vftexts.push(vftext as SVGAElement); } }); if (maxY !== 0 && _vftexts.length > 1) { _vftexts.forEach((vftext) => { vftext.setAttribute("y", maxY + ""); //console.log('音阶间距',maxY) }); } vftexts.forEach((vftext) => { [...vfcurve, ...vfmeasures, ...vflines].forEach((vfmeasure) => { let result = collisionDetection(vftext as SVGAElement, vfmeasure); if (result.isCollision && result.b1 < result.b2 && result.t1 < result.b2 - (result.b2 - result.t2) / 2) { const shift_y = Number(vftext.getAttribute("y")) - (result.b1 - result.t2) - baseStep + ""; vftext.setAttribute("y", shift_y); //console.log('音阶间距',shift_y) } }); }); vftexts.forEach((vftext) => { vftexts.forEach((text) => { if (vftext.parentNode !== text.parentNode && !["marcato", "legato"].includes(vftext.textContent as string)) { if (["marcato", "legato"].includes(text.textContent as string)) { const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30); if (result.isCollision) { const textBBox = (vftext as SVGAElement).getBBox(); text.setAttribute("x", textBBox.x + textBBox.width + 5 + ""); text.setAttribute("y", textBBox.y + textBBox.height - 5 + ""); //console.log('音阶间距',textBBox.y + textBBox.height - 5 + "") } } else { const result = collisionDetection(vftext as SVGAElement, text as SVGAElement); if (result.isCollision) { const _y = Number(vftext.getAttribute("y")); const shift_y = result.b2 - result.t2 < 24 ? 24 : result.b2 - result.t2; text.setAttribute("y", _y - shift_y - 0.5 + ""); //console.log('音阶间距',_y - shift_y - 0.5 + "") } } } }); }); // 修改音阶和线谱的间距 const clefList = ['C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb', 'D#', 'A#', 'E#'] const btransList = ['Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb'] const jtrsnsList = ['F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'B#'] vftexts.forEach((label: any) => { const labelText = label.textContent as string if (clefList.includes(labelText)){ const _y = Number(label.getAttribute("y")) const endY = firstLinePathY ? firstLinePathY - musicalDistance : _y label.setAttribute("y", endY) } if (btransList.includes(labelText)) { label.textContent = labelText.replace('b','♭') } if (jtrsnsList.includes(labelText)) { label.textContent = labelText.replace('#','♯') } }); // numberVfTexts.forEach((label: any) => { // const labelText = label.textContent as string // if (isNumeric(labelText)) { // const _y = Number(label.getAttribute("y")) // const endY = firstLinePathY ? firstLinePathY - musicalDistance : _y // label.setAttribute("y", endY) // } // }) const vftextBottom = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y > stafflineCenter); const vflineBottom = Array.from(staffline.querySelectorAll(".vf-line")).filter((n: any) => n.getBBox().y > stafflineCenter); // 去重 for (let i = 0; i < vftextBottom.length; i++) { const _text = vftextBottom[i]; for (let j = 0; j < vftextBottom.length; j++) { if (_text.parentNode === vftextBottom[j].parentNode) continue; const result = collisionDetection(_text as SVGAElement, vftextBottom[j] as SVGAElement); if (result.isCollision) { if (_text.textContent === vftextBottom[j].textContent) { vftextBottom[j].parentNode?.removeChild(vftextBottom[j]); continue; } } } } // 1,2线谱底部文字重叠问题 vftextBottom.forEach((vftext) => { [...vfmeasures].forEach((n) => { let result = collisionDetection(vftext as SVGAElement, n); if (result.isCollision) { vftext.setAttribute("y", result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute("y"))) + ""); //console.log('音阶间距', result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute("y"))) + "") } }); }); // 如果渐弱渐强有平行的文字 vflineBottom.forEach((line) => { const texts: any[] = []; if (line.nextElementSibling?.classList.contains("vf-line")) { vftextBottom.forEach((text) => { let result = collisionDetection(line as SVGAElement, text as SVGAElement, 20, 20); if (result.isCollision) { texts.push({ text: text as SVGAElement, result, }); } }); } if (texts.length === 1) { const result = texts[0].result; const text = texts[0].text; if (result.x2 + result.w2 < result.x1) { // 左 if (Math.abs(result.y2 - result.y1) > 10) { text.setAttribute("y", result.y1 + result.h2 / 2 + ""); //console.log('音阶间距', result.y1 + result.h2 / 2 + "") } } else if (result.x2 > result.x1 + result.w1) { // 右 if (Math.abs(result.y2 - result.y1) > 10) { text.setAttribute("y", result.y1 + result.h2 / 2 + ""); //console.log('音阶间距', result.y1 + result.h2 / 2 + "") } } else { if (Math.abs(result.x2 - result.x1) < Math.abs(result.x2 + result.w2 - result.x1 - result.w1)) { // console.log(text, '有交集', '靠左') text.setAttribute("x", result.x1 - result.w2 - 5 + ""); if (Math.abs(result.y2 - result.y1) > 10) { text.setAttribute("y", result.y1 + result.h2 / 2 + ""); //console.log('音阶间距', result.y1 + result.h2 / 2 + "") } } else { // console.log(text, '有交集', '靠右') text.setAttribute("x", result.x1 + result.w1 + 5 + ""); if (Math.abs(result.y2 - result.y1) > 10) { text.setAttribute("y", result.y1 + result.h2 / 2 + ""); //console.log('音阶间距', result.y1 + result.h2 / 2 + "") } } } } else if (texts.length === 2) { const result1 = texts[0].result; const text1 = texts[0].text; const result2 = texts[1].result; const text2 = texts[1].text; text1.setAttribute("x", result1.x1 - result1.w2 - 5 + ""); if (Math.abs(result1.y2 - result1.y1) > 10) { text1.setAttribute("y", result1.y1 + result1.h2 / 2 + ""); //console.log('音阶间距', result1.y1 + result1.h2 / 2 + "") } text2.setAttribute("x", result2.x1 + result2.w1 + 5 + ""); if (Math.abs(result2.y2 - result2.y1) > 10) { text2.setAttribute("y", result2.y1 + result2.h2 / 2 + ""); //console.log('音阶间距', result2.y1 + result2.h2 / 2 + "") } } else if (texts.length === 3) { // console.log(texts) } }); vftextBottom.forEach((vftext) => { vftextBottom.forEach((text) => { if (vftext.parentNode !== text.parentNode && !["marcato", "legato", "cresc.", "Cantabile"].includes(vftext.textContent as string)) { if (["marcato", "legato", "cresc.", "Cantabile"].includes(text.textContent as string)) { const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30); if (result.isCollision) { const textBBox = (vftext as SVGAElement).getBBox(); text.setAttribute("x", textBBox.x + textBBox.width + 5 + ""); text.setAttribute("y", textBBox.y + textBBox.height - 5 + ""); //console.log('音阶间距', textBBox.y + textBBox.height - 5 + "") } } else { const result = collisionDetection(vftext as SVGAElement, text as SVGAElement); if (result.isCollision) { text.setAttribute("y", result.y1 + result.h1 + result.h2 + ""); //console.log('音阶间距', result.y1 + result.h1 + result.h2 + "") } } } }); }); // 同一行内,多个飞线碰撞 for (let index = 0; index < vfcurve.length; index++) { let nextIndex = index + 1; const cur = vfcurve[index]; let next = vfcurve[nextIndex]; let checkDone = false; while (nextIndex <= vfcurve.length - 1 && !checkDone) { let result = collisionDetection(cur, next); if (result.isCollision) { checkDone = true; next.style.transform = `translateY(-12px)`; } else { nextIndex = nextIndex + 1; next = vfcurve[nextIndex]; } } }; // 给小节添加背景色 if (!state.isCreateImg && !state.isPreView) { staves.forEach((stave: any,i: number) => { const list = [ Array.from(stave?.querySelectorAll(".vf-StaveSection") || []), Array.from(stave?.getElementsByTagName("text") || []), Array.from(stave?.querySelectorAll(".vf-Volta") || []), Array.from(stave?.querySelectorAll(".vf-clef") || []), Array.from(stave?.querySelectorAll(".vf-keysignature") || []), Array.from(stave?.querySelectorAll(".vf-Repetition") || []), ].flat(); try { if (list.length) { list.forEach((_el: any) => { if (_el.parentNode === stave) { stave?.removeChild(_el) _el?.style?.setProperty("display", "none"); } }); } } catch (error) {} const bbox = stave?.getBBox() || {}; const bgColor = 'transparent'; const botColor = 'transparent'; const rect = `` const rectBottom = `` // const filterDom = ` // // // // ` const customG = `${rect}${rectBottom}` try { if (list.length) { for(const _el of list) { if (_el?.parentElement?.classList?.contains('vf-StaveSection')) { continue; } stave?.appendChild(_el) _el?.style?.removeProperty("display"); } } } catch (error) {} stave.innerHTML = customG + stave.innerHTML; }); state.vfmeasures = state.vfmeasures.concat(vfmeasures); } dotModifiers.forEach((group: any) => { let parent = group?.parentElement; // 获取父元素 // 如果需要找更外层的祖先元素,可以一直迭代 while (parent && !parent.classList?.contains('vf-measure') && parent.tagName !== 'body' && parent) { // 假设你想找到最外层的 DIV parent = parent.parentElement; } const parentY = parent?.querySelector('.vf-custom-bg')?.getBoundingClientRect()?.y || 0; const dotY = group?.getBoundingClientRect()?.y || 0; const distanceY = parentY - dotY; const translateY = 15 - distanceY; // console.log('距离111',translateY) group.setAttribute('transform', `translate(3,${-translateY})`) // if (state.musicRenderType === 'fixedTone') { // // group.setAttribute('transform', 'translate(3,-12)') // } else { // // group.setAttribute('transform', 'translate(3,-7)') // } }); // 修复D.C、D.S等渲染位置不对的问题 const repairWord = ["D.S.", "D.C.", "Fine"]; [...vfmeasures].forEach((measure: any) => { const needRepairTexts = measure.querySelectorAll('text').length ? Array.from(measure.querySelectorAll('text'))?.filter((item: any) => repairWord.includes(item?.textContent)) : []; if (needRepairTexts.length) { // 该小节结束位置的x坐标 const measureCoordinate = measure?.querySelector('.vf-custom-bg')?.getBBox() || null const measureEndX = measureCoordinate ? measureCoordinate?.x + measureCoordinate?.width - 30 : 0; needRepairTexts.forEach((text: any) => { text?.setAttribute('x', measureEndX) }) } }); } if (!state.isCombineRender && state.isSingleLine) { transSinglePage(); } // 多行谱,拍号可能被遮挡,需要移动谱面的位置 if (!state.isSingleLine) { transMultiPosition(); } // setTimeout(() => this.resetGlobalText()); }; // 一行谱时,五线谱/简谱的谱面staffLine,居中显示 const transSinglePage = () => { if (state.isSingleLine && !state.isSimplePage) { const svgPage = document?.getElementById('osmdSvgPage1')?.getBoundingClientRect(); const staffLine = document?.querySelector('.staffline')?.getBoundingClientRect(); if (svgPage && staffLine && svgPage.height > 200) { // 需要上移的距离 // console.log('need',svgPage.height,staffLine.height) const rate = svgPage.height > 400 ? 1.2 : 2; let needTransTop = (svgPage.height - staffLine.height) / rate; // 给个安全距离 const maxTop = staffLine.top - svgPage.top - 40 needTransTop = Math.min(maxTop, needTransTop) // @ts-ignore document.getElementById('osmdSvgPage1').style.transform = `translateY(-${needTransTop}px)`; // 一行谱需要同时偏移光标的位置 const cursorDom = document.getElementById('cursorImg-0') || null; if (cursorDom) { cursorDom.style.transform = state.musicRenderType === 'staff' ? `translate(11Px, -${needTransTop}px)` : `translate(6.3Px, -${needTransTop}px)`; } // document.querySelector('.staffline').style.transform = `translateY(-${needTransTop}px)`; // const musicLine = document.querySelector('.staffline').querySelector('.vf-measure').querySelector('.vf-custom-bg').getBoundingClientRect(); // const needTransDistance = svgPage.height / 2 - (musicLine.top - svgPage.top) // console.log('svg移动距离',needTransDistance) // // @ts-ignore // document.getElementById('osmdSvgPage1').style.transform = `translateY(${needTransDistance}px)` } } if (state.isSimplePage) { const svgPage = document?.getElementById('osmdSvgPage1')?.getBoundingClientRect(); const staffLine = document?.querySelector('.staffline')?.getBoundingClientRect(); if (svgPage && staffLine) { const needY = svgPage.height - (staffLine.y+staffLine.height) - 10; // @ts-ignore document.getElementById('osmdSvgPage1').style.transform = `translateY(${needY}px)`; // 一行谱需要同时偏移光标的位置 const cursorDom = document.getElementById('cursorImg-0') || null; if (cursorDom) { cursorDom.style.transform = state.musicRenderType === 'staff' ? `translate(6Px, ${needY}px)` : `translate(6.3Px, ${needY}px)`; console.log('一行谱11111') } } } } const transMultiPosition = () => { const svgPage = document?.getElementById('osmdSvgPage1')?.getBoundingClientRect(); const staffLine = document?.querySelector('.staffline')?.getBoundingClientRect(); if (svgPage && staffLine && staffLine.y < svgPage.y) { const needY = svgPage.y - staffLine.y + 5; // @ts-ignore document.querySelector('.staffline').style.transform = `translateY(${needY}px)`; } } // 技巧文本 const resetGlobalText = () => { const svg = container.value.querySelector("svg"); if (!svg) return; const svgBBox = svg.getBBox(); let vfstavetempo: SVGAElement[] = Array.from(container.value.querySelectorAll(".vf-stavetempo")).reduce((eles: SVGAElement[], value: any) => { if (eles.find((n) => n.outerHTML === value.outerHTML)) value?.parentNode?.removeChild(value); else eles.push(value); return eles; }, []); const staffline: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline")); const vfmeasures: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-measure")); const vftexts: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-text")); const vfcurves: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-curve")); vfstavetempo.forEach((child: SVGAElement) => { let _y = 0; [...vfmeasures, ...vftexts, ...vfcurves].forEach((ele) => { const result = collisionDetection(child as SVGAElement, ele); if (result.isCollision && (result.b1 < result.b2 || result.r1 > result.l2 || result.l1 < result.r2)) { _y = Math.min(_y, result.t2 - result.b1); } }); if (_y !== 0) { child.style.transform = `translateY(${_y}px)`; } const childBBox = child.getBBox(); const rightY = (childBBox.x + childBBox.width) * 0.7 - Number(svg.getAttribute("width")); if (rightY > 0) { [...staffline, ...vfstavetempo].forEach((tempo) => { if (child != tempo) { const result = collisionDetection(child as SVGAElement, tempo, Math.abs(rightY), Math.abs(_y)); if (result.isCollision) { _y = result.t2 - result.b1; } } }); child.style.transform = `translate(-${rightY / 0.7}px,${_y}px)`; } }); if (svgBBox.y < 0) { svg.setAttribute("height", Number(svg.getAttribute("height")) - svgBBox.y + 10); } }; // 碰撞检测 const collisionDetection = (a: SVGAElement, b: SVGAElement, distance: number = 0, distance_y: number = 0) => { const abbox = a.getBBox(); const bbbox = b.getBBox(); let t1 = abbox.y - distance_y; let l1 = abbox.x - distance; let r1 = abbox.x + abbox.width + distance; let b1 = abbox.y + abbox.height + distance_y; let t2 = bbbox.y; let l2 = bbbox.x; let r2 = bbbox.x + bbbox.width; let b2 = bbbox.y + bbbox.height; if (b1 < t2 || l1 > r2 || t1 > b2 || r1 < l2) { // 表示没碰上 return { isCollision: false, t1, l1, r1, b1, t2, l2, r2, b2, x1: abbox.x, y1: abbox.y, x2: bbbox.x, y2: bbbox.y, h1: abbox.height, h2: bbbox.height, w1: abbox.width, w2: bbbox.width, }; } else { return { isCollision: true, t1, l1, r1, b1, t2, l2, r2, b2, x1: abbox.x, y1: abbox.y, x2: bbbox.x, y2: bbbox.y, h1: abbox.height, h2: bbbox.height, w1: abbox.width, w2: bbbox.width, }; } }; /** 全局曲谱配置 */ export const setGlobalMusicSheet = () => { const partIndex = state.partIndex + "" /** 延音线方向问题 start */ const stavetieList = [ {id: '12644', part_index: '25', direction: 1} ] const tieItem = stavetieList.find(({id, part_index}) => { return id == state.cbsExamSongId && part_index == partIndex }) setGlobalData('tieDirection', tieItem ? tieItem.direction : undefined) /** 延音线方向问题 end */ const graceList = [ {id: '3509', part_index: '16', direction: 1} ] const graceItem = graceList.find(({id, part_index}) => { return id == state.cbsExamSongId && part_index == partIndex }) if (graceItem){ setGlobalData('graceCustom', {direction: graceItem.direction}) } const bassDrumList = [ {id: '3030', part_index: '17', line: 4}, {id: '12704', part_index: '23', line: 3} ] const bassDrumItem = bassDrumList.find(({id, part_index}) => { return id == state.cbsExamSongId && part_index == partIndex }) if (bassDrumItem){ setGlobalData('customBassDrum', bassDrumItem.line) } /** 打击乐多声部,双声部休止符重叠 end */ /** 符杆朝向 */ const stemDirectionList = [ { id: '11654', part_index: '16', stemNotes: [ {id: 124, direction: 0}, {id: 125, direction: 0}, {id: 126, direction: 0}, {id: 127, direction: 0}, {id: 128, direction: 0} ] }, { id: '3581', part_index: '4', stemNotes: [ {id: 380, direction: 1}, ] }, { id: '3470', part_index: '0', stemNotes: [ {id: 36, direction: 1}, {id: 37, direction: 1}, ] }, { id: '3470', part_index: '11', stemNotes: [ {id: 33, direction: 1}, {id: 56, direction: 1}, ] }, { id: '12644', part_index: '22', stemNotes: [ {id: 22, direction: 1}, {id: 26, direction: 1}, {id: 135, direction: 1}, {id: 163, direction: 1}, {id: 199, direction: 1}, {id: 204, direction: 1}, {id: 206, direction: 1}, {id: 208, direction: 1}, {id: 210, direction: 1}, {id: 213, direction: 1}, ] }, { id: '12303', part_index: '18', stemNotes: [ {id: 1, direction: 1}, {id: 4, direction: 1}, {id: 6, direction: 1}, {id: 9, direction: 1}, {id: 12, direction: 1}, {id: 14, direction: 1}, ] }, { id: '12669', part_index: '24', stemNotes: [ {id: 65, direction: 1}, {id: 296, direction: 1}, {id: 298, direction: 1}, {id: 300, direction: 1}, {id: 338, direction: 1}, ] }, { id: '12420', part_index: '21', stemNotes: [ {id: 614, direction: 0}, {id: 617, direction: 0}, {id: 619, direction: 0}, {id: 621, direction: 0}, ] }, { id: '12711', part_index: '22', stemNotes: [] }, { id: '12973', part_index: '21', stemNotes: [ {id: 619, direction: 1}, {id: 622, direction: 1}, {id: 745, direction: 1}, ] }, ] const stemDirectionItem = stemDirectionList.find(({id, part_index}) => { return id == state.cbsExamSongId && part_index == partIndex }) if (stemDirectionItem) { setGlobalData('stemDirectionNote', stemDirectionItem.stemNotes) } /** vfcure */ const vfcurveList = [ { id: '12711', part_index: '4', vfcurve: [ {MeasureNumberXML: 25, index: 1, bezierEndControlPt: {y: -2}}, {MeasureNumberXML: 33, index: 1, bezierEndControlPt: {y: -2}}, ] }, { id: '12059', part_index: '0', vfcurve: [ {MeasureNumberXML: 15, bezierEndControlPt: {y: 2.8}, bezierEndPt:{y: 1.1}}, {MeasureNumberXML: 16, bezierEndControlPt: {y: -1}}, {MeasureNumberXML: 19, index: 1, bezierEndControlPt: {y: 2}}, {MeasureNumberXML: 20, bezierEndControlPt: {y: -1}}, {MeasureNumberXML: 42, index: 1, bezierEndControlPt: {y: -1.5}, bezierStartControlPt: {y: -1.5}}, {MeasureNumberXML: 46, index: 3, bezierEndControlPt: {y: -1.5}, bezierStartControlPt: {y: -1.5}}, ] }, { id: '12668', part_index: '11', vfcurve: [ {MeasureNumberXML: 8, index: 2, bezierEndControlPt: {y: -3}, bezierStartControlPt:{y: -3}, bezierEndPt:{y: -1}}, ] }, { id: '11976', part_index: '0', vfcurve: [ {MeasureNumberXML: 14, index: 4, bezierEndControlPt: {y: -3}}, {MeasureNumberXML: 14, index: 1, bezierEndPt: {y: 1.5}, bezierEndControlPt: {y: 1}}, ] }, ] const vfcurveItem = vfcurveList.find(({id, part_index}) => { return id == state.cbsExamSongId && part_index == partIndex }) if (vfcurveItem) { setGlobalData('vfcurveItem', vfcurveItem.vfcurve) } /** drum set声部 重音 */ const customArtPositionList = [ {id: '12644', part_index: '25'} ] const customArtPositionItem = customArtPositionList.find(({id, part_index}) => { return id == state.cbsExamSongId && part_index == partIndex }) if (customArtPositionItem) { setGlobalData('customArtPosition', true) } /** 全声部声部 - & 全音符 */ const customTenutoList = [ {id: '12645', part_index: '5'} ] const customTenutoItem = customTenutoList.find(({id, part_index}) => { return id == state.cbsExamSongId && part_index == partIndex }) if (customTenutoItem) { setGlobalData('customTenutoItem', true) } /** 全声部声部 > */ const customAccentList = [ {id: '12711', part_index: '22'}, {id: '12711', part_index: '25'}, ] const customAccentItem = customAccentList.find(({id, part_index}) => { return id == state.cbsExamSongId && part_index == partIndex }) if (customAccentItem || state.isEvxml) { setGlobalData('customAccentItem', true) } /** 全声部声部 + */ const customLefthandpizzicatoList = [ {id: '12711', part_index: '25'}, {id: '7755', part_index: '10'}, {id: '6226', part_index: '16'}, ] const customLefthandpizzicatoItem = customLefthandpizzicatoList.find(({id, part_index}) => { return id == state.cbsExamSongId && part_index == partIndex }) if (customLefthandpizzicatoItem) { setGlobalData('customLefthandpizzicatoItem', true) } } /** 设置自定义音符数据 */ export const setCustomNoteRealValue = () => { const detailId = state.cbsExamSongId + ""; if (["12667", "12673"].includes(detailId)){ customData.customNoteCurrentTime = true } }; /** 转换简谱的全休止符和二分休止符 */ export const transferJianNote = (measure: any, divisions: number, preBeats: number, preBeatType: number) => { const multipleXs = preBeatType / 4; const notes = measure.getElementsByTagName("note") for (const note of notes) { // 是否需要考虑带上附点 let needAddDot = true; const noteType = note.getElementsByTagName("type")?.[0]?.textContent || ''; if ((noteType === 'whole' || noteType === 'half') && note.getElementsByTagName("rest").length) { // 4/4拍,3/4拍 if (preBeatType === 4) { let maxNumber = noteType === 'half' ? 2 : preBeats / multipleXs; if (noteType === 'whole') { // 有可能是全休止符,但是该小节又不是整小节都休止,此时这个全休止符不能按照整小节休止来计算 const noteDivisions = parseInt(note.getElementsByTagName("duration")[0]?.textContent); if (noteDivisions/divisions !== preBeats) { maxNumber = 4; } else { // 满足了时值,则不需要考虑加上附点 needAddDot = false; } } // 如果音符带附点,需要判断处理下 if (note.getElementsByTagName("dot").length && needAddDot) { maxNumber = noteType === 'whole' ? maxNumber + 2 : maxNumber + 1; } if (!Number.isInteger(maxNumber)) { return; } // console.log('几个1/4音符',maxNumber) let quarterNoteNumber = 1; while (quarterNoteNumber <= maxNumber) { const newnote = document.createElement('note'); newnote.innerHTML = ` ${divisions} 1 quarter` measure.insertBefore(newnote, note); quarterNoteNumber += 1; }; measure.removeChild(note); } else if (preBeats === 3 && preBeatType === 8) { const maxNumber = noteType === 'half' ? 2 : 3; let quarterNoteNumber = 1; while (quarterNoteNumber <= maxNumber) { const newnote = document.createElement('note'); newnote.innerHTML = ` ${divisions/2} 1 eighth` measure.insertBefore(newnote, note); quarterNoteNumber += 1; }; measure.removeChild(note); } else if (preBeats === 5 && preBeatType === 8) { if (noteType === 'whole') { const newnote = document.createElement('note'); newnote.innerHTML = ` ${divisions+divisions/2} 1 quarter ` measure.insertBefore(newnote, note); const newnote2 = document.createElement('note'); newnote2.innerHTML = ` ${divisions} 1 quarter` measure.insertBefore(newnote2, note); measure.removeChild(note); } else if (noteType === 'half') { dealDotHalfNote(measure, divisions, note) } } else if (preBeats === 6 && preBeatType === 8) { if (noteType === 'whole') { const maxNumber = 2; let quarterNoteNumber = 1; while (quarterNoteNumber <= maxNumber) { const newnote = document.createElement('note'); newnote.innerHTML = ` ${divisions+divisions/2} 1 quarter ` measure.insertBefore(newnote, note); quarterNoteNumber += 1; }; measure.removeChild(note); } else if (noteType === 'half') { dealDotHalfNote(measure, divisions, note) } } else if (preBeats === 7 && preBeatType === 8) { if (noteType === 'whole') { const newnote2 = document.createElement('note'); newnote2.innerHTML = ` ${divisions+divisions/2} 1 quarter ` measure.insertBefore(newnote2, note); const maxNumber = 2; let quarterNoteNumber = 1; while (quarterNoteNumber <= maxNumber) { const newnote = document.createElement('note'); newnote.innerHTML = ` ${divisions} 1 quarter` measure.insertBefore(newnote, note); quarterNoteNumber += 1; }; measure.removeChild(note); } else if (noteType === 'half') { dealDotHalfNote(measure, divisions, note) } } else if (preBeats === 9 && preBeatType === 8) { if (noteType === 'whole') { const maxNumber = 3; let quarterNoteNumber = 1; while (quarterNoteNumber <= maxNumber) { const newnote = document.createElement('note'); newnote.innerHTML = ` ${divisions+divisions/2} 1 quarter ` measure.insertBefore(newnote, note); quarterNoteNumber += 1; }; measure.removeChild(note); } else if (noteType === 'half') { dealDotHalfNote(measure, divisions, note) } } else if (preBeats === 12 && preBeatType === 8) { if (noteType === 'whole') { const maxNumber = 4; let quarterNoteNumber = 1; while (quarterNoteNumber <= maxNumber) { const newnote = document.createElement('note'); newnote.innerHTML = ` ${divisions+divisions/2} 1 quarter ` measure.insertBefore(newnote, note); quarterNoteNumber += 1; }; measure.removeChild(note); } else if (noteType === 'half') { dealDotHalfNote(measure, divisions, note) } } } } } /** 八几排的小节,二分休止符带附点 */ const dealDotHalfNote = (measure: any, divisions: number, note: any) => { // 如果音符带附点,需要判断处理下 if (note.getElementsByTagName("dot").length) { const maxNumber = 2; let quarterNoteNumber = 1; while (quarterNoteNumber <= maxNumber) { const newnote = document.createElement('note'); newnote.innerHTML = ` ${divisions+divisions/2} 1 quarter ` measure.insertBefore(newnote, note); quarterNoteNumber += 1; }; measure.removeChild(note); } else { const newnote = document.createElement('note'); newnote.innerHTML = ` ${divisions+divisions/2} 1 quarter ` measure.insertBefore(newnote, note); const newnote2 = document.createElement('note'); newnote2.innerHTML = ` ${divisions/2} 1 eighth` measure.insertBefore(newnote2, note); measure.removeChild(note); } }