import { OpenSheetMusicDisplay, SourceMeasure } from "/osmd-extended/src"; import { onlyVisible } from "/src/helpers/formateMusic"; export const noteDuration = { "1/2": 2, w: 1, h: 0.5, q: 0.25, "8": 0.125, "16": 0.0625, "32": 0.03125, "64": 0.015625, "128": 0.0078125, }; export type GradualChange = { resetXmlNoteIndex: number; startXmlNoteIndex: number; endXmlNoteIndex: number; startWord: string; startMeasureListIndex: number; endMeasureListIndex: number; resetMeasureListIndex: number; }; export const speedInfo: { [key in string]: number } = { "rall.": 1.333333333, "poco rit.": 1.333333333, "rit.": 1.333333333, "molto rit.": 1.333333333, "molto rall": 1.333333333, molto: 1.333333333, lentando: 1.333333333, allargando: 1.333333333, morendo: 1.333333333, "accel.": 0.8, calando: 2, "poco accel.": 0.8, "gradually slowing": 1.333333333, slowing: 1.333333333, slow: 1.333333333, slowly: 1.333333333, faster: 1.333333333, "molto allargando": 1.333333333, stringendo: 0.8, }; /** * 计算渐变速度 */ export const calcGradual = () => {}; /** * 2022年9月14日版本 计算渐变速度,此方法不兼容之前的选择范围。 */ export const calcGradual2 = () => {}; /** * 获取指定元素下一个Note元素 * @param ele 指定元素 * @param selectors 选择器 */ const getNextNote = (ele: Element, selectors: string) => { let index = 0; const parentEle = ele.closest(selectors); let pointer = parentEle; const measure = parentEle?.closest("measure"); let siblingNote: Element | null | undefined = null; // 查找到相邻的第一个note元素 while (!siblingNote && index < (measure?.childNodes.length || 50)) { index++; if (pointer?.nextElementSibling?.tagName === "note") { siblingNote = pointer?.nextElementSibling; } pointer = pointer?.nextElementSibling!; } return siblingNote; }; export type GradualElement = { ele: Element; index: number; noteInMeasureIndex: number; textContent: string; measureIndex: number; type: "words" | "metronome"; allDuration: number; leftDuration: number; }; export type GradualNote = GradualItem[]; export type GradualItem = { start: number; measureIndex: number; noteInMeasureIndex: number; allDuration: number; leftDuration: number; type: string; closedMeasureIndex: number; }; // export type GradualItem = { // start: number // startMeasureIndex: number // startNoteInMeasureIndex: number // allDuration: number // leftDuration: number // endNoteInMeasureIndex?: number // endMeasureIndex?: number // end?: number // } /** * 按照xml进行减慢速度的计算 * @param xml 始终按照第一分谱进行减慢速度的计算 */ export const getGradualLengthByXml = (xml: string) => { const firstPartXml = onlyVisible(xml, 0, 'calc') const xmlParse = new DOMParser().parseFromString(firstPartXml, "text/xml"); const measures = Array.from(xmlParse.querySelectorAll("measure")); const notes = Array.from(xmlParse.querySelectorAll("note")); const words = Array.from(xmlParse.querySelectorAll("words")); const metronomes = Array.from(xmlParse.querySelectorAll("metronome")); const eles: GradualElement[] = []; for (const ele of [...words, ...metronomes]) { const note = getNextNote(ele, "direction"); // console.log(ele, note) if (note) { const measure = note?.closest("measure")!; const measureNotes = Array.from(measure.querySelectorAll("note")); const noteInMeasureIndex = Array.from(measure.childNodes) .filter((item) => item.nodeName === "note") .findIndex((item) => item === note); let allDuration = 0; let leftDuration = 0; for (let i = 0; i < measureNotes.length; i++) { const n = measureNotes[i]; const duration = +(n.querySelector("duration")?.textContent || "0"); allDuration += duration; if (i < noteInMeasureIndex) { leftDuration = allDuration; } } eles.push({ ele, index: notes.indexOf(note!), noteInMeasureIndex, textContent: ele.textContent!, measureIndex: measures.indexOf(measure!), type: ele.tagName as GradualElement["type"], allDuration, leftDuration, }); } } // 结尾处手动插入一个音符节点 eles.push({ ele: notes[notes.length - 1], index: notes.length, noteInMeasureIndex: 0, textContent: "", type: "metronome", allDuration: 1, leftDuration: 0, measureIndex: measures.length, }); const gradualNotes: GradualNote[] = []; eles.sort((a, b) => a.index - b.index); const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase()); for (const ele of eles) { // 是否是同时也是关闭标签 let isLastNoteAndNotClosed = false; let closed = 0; const textContent = ele.textContent?.toLocaleLowerCase().trim(); if (ele === eles[eles.length - 1]) { if (gradualNotes[gradualNotes.length - 1]?.length === 1) { isLastNoteAndNotClosed = true; } } const isKeyWork = keys.find((k) => { const ks = k.split(" "); return textContent && ks.includes(textContent) || k === textContent; }); if (ele.type === "metronome" || (ele.type === "words" && (textContent.startsWith("a tempo") || isKeyWork)) || isLastNoteAndNotClosed) { const indexOf = gradualNotes.findIndex((item) => item.length === 1); if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) { closed = -1; gradualNotes[indexOf][1] = { start: ele.index, measureIndex: ele.measureIndex, closedMeasureIndex: ele.measureIndex, noteInMeasureIndex: ele.noteInMeasureIndex, allDuration: ele.allDuration, leftDuration: ele.leftDuration, type: textContent, }; } } if (ele.type === "words" && isKeyWork) { gradualNotes.push([ { start: ele.index, measureIndex: ele.measureIndex, closedMeasureIndex: ele.measureIndex + closed, noteInMeasureIndex: ele.noteInMeasureIndex, allDuration: ele.allDuration, leftDuration: ele.leftDuration, type: textContent, }, ]); } } return gradualNotes; }; export const getGradualLength = (gradualChange: GradualChange, speed: number, osdm: OpenSheetMusicDisplay) => { const { startMeasureListIndex, endMeasureListIndex, endXmlNoteIndex, startWord } = gradualChange; const measures: SourceMeasure[] = []; for (let index = startMeasureListIndex; index <= endMeasureListIndex; index++) { const measure = osdm.Sheet.SourceMeasures[index]; measures.push(measure); } const allNoteDurations: number[] = []; for (const measure of measures) { if (allNoteDurations.length >= endXmlNoteIndex) { break; } // @ts-ignore measure.VerticalMeasureList[0]?.vfVoices["1"]?.tickables?.forEach((item) => allNoteDurations.push(noteDuration[item.duration as keyof typeof noteDuration]) ); } const minDuration = Math.min(...allNoteDurations); const parts = allNoteDurations.map((item) => item / minDuration); const allParts = parts.reduce((total, val) => val + total, 0); // const startMeasure = osdm.Sheet.SourceMeasures[startMeasureListIndex] // const endMeasure = osdm.Sheet.SourceMeasures[endMeasureListIndex] let surplusSpeed = speed / speedInfo[startWord?.toLocaleLowerCase()] || 1; const diffSpeed = speed - surplusSpeed; let useSpeed = 0; const speeds: number[] = parts.map((item) => { const s = ((diffSpeed - useSpeed) * item) / allParts; return s; }); // 120 111.9 104.4 96.9 // 8.1 7.5 7.2 6.9 // 0.6 0.3 0.3 const lingerSpeed: number[] = []; for (let index = 0; index < speeds.length; index++) { const s = speeds[index]; let beforeSpeed = 0; let afterSpeed = 0; for (let j = 0; j < index; j++) { beforeSpeed += speeds[j]; } afterSpeed += beforeSpeed; afterSpeed += s; lingerSpeed.push((afterSpeed + beforeSpeed) / 2); } // console.log(lingerSpeed, speeds[0], speeds, parts, allParts) return lingerSpeed; };