|
@@ -2,6 +2,7 @@ import dayjs from "dayjs";
|
|
import duration from "dayjs/plugin/duration";
|
|
import duration from "dayjs/plugin/duration";
|
|
import state, { customData } from "/src/state";
|
|
import state, { customData } from "/src/state";
|
|
import { browser } from "../utils/index";
|
|
import { browser } from "../utils/index";
|
|
|
|
+import { transferJianNote } from "/src/helpers/customMusicScore"
|
|
import {
|
|
import {
|
|
isSpecialMark,
|
|
isSpecialMark,
|
|
isSpeedKeyword,
|
|
isSpeedKeyword,
|
|
@@ -616,21 +617,66 @@ export const formatZoom = (num = 1) => {
|
|
return num * state.zoom;
|
|
return num * state.zoom;
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/** 妙极客多分轨的曲子,可能没有part-name标签,需要手动加上该标签 */
|
|
|
|
+export const xmlAddPartName = (xml: string) => {
|
|
|
|
+ if (!xml) return "";
|
|
|
|
+ const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
|
|
|
|
+ const scoreParts = Array.from(xmlParse.getElementsByTagName("score-part"));
|
|
|
|
+ for (const scorePart of scoreParts) {
|
|
|
|
+ if (scorePart.getElementsByTagName("part-name").length === 0) {
|
|
|
|
+ state.evxmlAddPartName = true;
|
|
|
|
+ const name = scorePart.getAttribute("id") || "";
|
|
|
|
+ const newPartName = `<part-name>${name}</part-name>`
|
|
|
|
+ // scorePart.prepend(newPartName);
|
|
|
|
+ scorePart.innerHTML = newPartName + scorePart.innerHTML;
|
|
|
|
+ }
|
|
|
|
+ if (scorePart.getElementsByTagName("part-name").length && !scorePart.getElementsByTagName("part-name")?.[0]?.textContent?.trim() ) {
|
|
|
|
+ scorePart.getElementsByTagName("part-name")[0].textContent = scorePart.getAttribute("id") || "";
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return new XMLSerializer().serializeToString(xmlParse);
|
|
|
|
+}
|
|
|
|
+
|
|
/** 格式化曲谱
|
|
/** 格式化曲谱
|
|
* 1.全休止符的小节,没有音符默认加个全休止符
|
|
* 1.全休止符的小节,没有音符默认加个全休止符
|
|
*/
|
|
*/
|
|
export const formatXML = (xml: string, xmlUrl?: string): string => {
|
|
export const formatXML = (xml: string, xmlUrl?: string): string => {
|
|
if (!xml) return "";
|
|
if (!xml) return "";
|
|
-
|
|
|
|
const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
|
|
const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
|
|
|
|
|
|
|
|
+ // 声调
|
|
|
|
+ const fifths = xmlParse.getElementsByTagName("fifths");
|
|
|
|
+ if (fifths && fifths.length) {
|
|
|
|
+ // 是否是C调
|
|
|
|
+ state.isCTone = fifths[0].textContent === '0'
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const endings = Array.from(xmlParse.getElementsByTagName("ending"));
|
|
|
|
+ for (const ending of endings) {
|
|
|
|
+ // if (ending.getAttribute('type') === 'stop') {
|
|
|
|
+ // // @ts-ignore
|
|
|
|
+ // ending.parentNode?.removeChild(ending.parentNode?.getElementsByTagName('bar-style')[0])
|
|
|
|
+ // }
|
|
|
|
+ // @ts-ignore
|
|
|
|
+ // ending.parentNode.parentNode?.removeChild(ending.parentNode)
|
|
|
|
+ }
|
|
|
|
+
|
|
const measures = Array.from(xmlParse.getElementsByTagName("measure"));
|
|
const measures = Array.from(xmlParse.getElementsByTagName("measure"));
|
|
const minutes: any = xmlParse.getElementsByTagName("per-minute");
|
|
const minutes: any = xmlParse.getElementsByTagName("per-minute");
|
|
let speeds: any = []
|
|
let speeds: any = []
|
|
for (const minute of minutes) {
|
|
for (const minute of minutes) {
|
|
- if (minute.textContent && !!Number(minute.textContent)) {
|
|
|
|
- speeds.push(Number(minute.textContent))
|
|
|
|
|
|
+ let measureSpeed = minute.textContent ? Number(minute.textContent) : 0;
|
|
|
|
+ // 速度带附点,需要转换成不带附点的速度值
|
|
|
|
+ const hasSpeedDot = Array.from(minute?.parentElement?.children || []).some((item: any) => item?.tagName === 'beat-unit-dot')
|
|
|
|
+ measureSpeed = hasSpeedDot ? measureSpeed + measureSpeed/2 : measureSpeed;
|
|
|
|
+ if (minute.textContent && measureSpeed) {
|
|
|
|
+ speeds.push(Number(measureSpeed))
|
|
}
|
|
}
|
|
|
|
+ // if (hasSpeedDot && measureSpeed) {
|
|
|
|
+ // minute.textContent = measureSpeed
|
|
|
|
+ // const dotDom = minute?.parentElement.querySelector('beat-unit-dot')
|
|
|
|
+ // minute?.parentElement?.removeChild(dotDom)
|
|
|
|
+ // }
|
|
}
|
|
}
|
|
speeds = [...new Set(speeds)]
|
|
speeds = [...new Set(speeds)]
|
|
const hasVaryingSpeed = speeds.length > 1 ? true : false
|
|
const hasVaryingSpeed = speeds.length > 1 ? true : false
|
|
@@ -669,6 +715,9 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
|
|
let speed = -1
|
|
let speed = -1
|
|
let beats = -1;
|
|
let beats = -1;
|
|
let beatType = -1;
|
|
let beatType = -1;
|
|
|
|
+ // 前面小节的拍子
|
|
|
|
+ let preBeats: number = 4;
|
|
|
|
+ let preBeatType: number = 4;
|
|
// 小节中如果没有节点默认为休止符
|
|
// 小节中如果没有节点默认为休止符
|
|
for (const measure of measures) {
|
|
for (const measure of measures) {
|
|
if (beats === -1 && measure.getElementsByTagName("beats").length) {
|
|
if (beats === -1 && measure.getElementsByTagName("beats").length) {
|
|
@@ -680,6 +729,11 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
|
|
if (speed === -1 && measure.getElementsByTagName('per-minute').length) {
|
|
if (speed === -1 && measure.getElementsByTagName('per-minute').length) {
|
|
speed = Number(measure.getElementsByTagName('per-minute')[0]?.textContent)
|
|
speed = Number(measure.getElementsByTagName('per-minute')[0]?.textContent)
|
|
}
|
|
}
|
|
|
|
+ // 当前小节的拍数
|
|
|
|
+ const currentBeats = measure.getElementsByTagName("beats").length ? measure.getElementsByTagName("beats")[0]?.textContent : preBeats;
|
|
|
|
+ const currentBeatType = measure.getElementsByTagName("beat-type").length ? measure.getElementsByTagName("beat-type")[0]?.textContent : preBeatType;
|
|
|
|
+ preBeats = Number(currentBeats);
|
|
|
|
+ preBeatType = Number(currentBeatType);
|
|
const divisions = parseInt(measure.getElementsByTagName("divisions")[0]?.textContent || "256");
|
|
const divisions = parseInt(measure.getElementsByTagName("divisions")[0]?.textContent || "256");
|
|
// 如果note节点里面有space节点,并且没有duration节点,代表这是一个空白节点,需要删除
|
|
// 如果note节点里面有space节点,并且没有duration节点,代表这是一个空白节点,需要删除
|
|
if (measure.getElementsByTagName("note").length && state.isEvxml) {
|
|
if (measure.getElementsByTagName("note").length && state.isEvxml) {
|
|
@@ -726,11 +780,14 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
|
|
<voice>1</voice>
|
|
<voice>1</voice>
|
|
<type>whole</type>
|
|
<type>whole</type>
|
|
</note>`;
|
|
</note>`;
|
|
|
|
+ } else if (state.musicRenderType !== 'staff') {
|
|
|
|
+ transferJianNote(measure, divisions, preBeats, preBeatType)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return new XMLSerializer().serializeToString(xmlParse);
|
|
return new XMLSerializer().serializeToString(xmlParse);
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+
|
|
/** 获取所有音符的时值,以及格式化音符 */
|
|
/** 获取所有音符的时值,以及格式化音符 */
|
|
export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
const customNoteRealValue = customData.customNoteRealValue;
|
|
const customNoteRealValue = customData.customNoteRealValue;
|
|
@@ -789,11 +846,12 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
let multipleRestMeasures = 0;
|
|
let multipleRestMeasures = 0;
|
|
let staveNoteIndex = 0;
|
|
let staveNoteIndex = 0;
|
|
let staveIndex = 0;
|
|
let staveIndex = 0;
|
|
- let xmlNoteTime = 0 // xml上面的音符时间
|
|
|
|
let xmlMp3BeatFixTime = 0 // xml上节拍器的时间
|
|
let xmlMp3BeatFixTime = 0 // xml上节拍器的时间
|
|
|
|
|
|
let preNoteEndTime = 0; // 上一个音符的结束时间
|
|
let preNoteEndTime = 0; // 上一个音符的结束时间
|
|
|
|
|
|
|
|
+ let preNoteMeasureNumber = 0; // 上一个小节的number值
|
|
|
|
+
|
|
const _notes = [] as any[];
|
|
const _notes = [] as any[];
|
|
if (state.gradualTimes) {
|
|
if (state.gradualTimes) {
|
|
console.log("后台设置的渐慢小节时间", state.gradual, state.gradualTimes);
|
|
console.log("后台设置的渐慢小节时间", state.gradual, state.gradualTimes);
|
|
@@ -805,9 +863,16 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
let differFrom = 0;
|
|
let differFrom = 0;
|
|
// let testIdx = 0;
|
|
// let testIdx = 0;
|
|
let repeatIdx = 0; // 循环的次数
|
|
let repeatIdx = 0; // 循环的次数
|
|
|
|
+ const firstTrackName = state.canSelectTracks[0] || "";
|
|
while (!iterator.EndReached) {
|
|
while (!iterator.EndReached) {
|
|
// console.log({ ...iterator });
|
|
// console.log({ ...iterator });
|
|
/** 多声轨合并显示,当前音符的时值取所有声轨中的最小值 */
|
|
/** 多声轨合并显示,当前音符的时值取所有声轨中的最小值 */
|
|
|
|
+ if (state.isCombineRender) {
|
|
|
|
+ iterator.currentVoiceEntries = iterator.currentVoiceEntries.filter((item: any) => {
|
|
|
|
+ const trackName = state.isEvxml && state.evxmlAddPartName ? item.parentVoice.parent.IdString || '' : item.parentVoice.parent.Name || '';
|
|
|
|
+ return trackName === firstTrackName
|
|
|
|
+ });
|
|
|
|
+ }
|
|
let minIndex = 0, elRealValue = 0
|
|
let minIndex = 0, elRealValue = 0
|
|
for (let index = 0; index < iterator.currentVoiceEntries.length; index++) {
|
|
for (let index = 0; index < iterator.currentVoiceEntries.length; index++) {
|
|
const element = iterator.currentVoiceEntries[index];
|
|
const element = iterator.currentVoiceEntries[index];
|
|
@@ -822,8 +887,10 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
}
|
|
}
|
|
elRealValue = element.notes[0].length.realValue
|
|
elRealValue = element.notes[0].length.realValue
|
|
}
|
|
}
|
|
|
|
+ if (minIndex !== 0 && state.isCombineRender && iterator.currentVoiceEntries[minIndex]) {
|
|
|
|
+ iterator.currentVoiceEntries[minIndex].Notes[0].NoteToGraphicalNoteObjectId = iterator.currentVoiceEntries?.[0].Notes[0].NoteToGraphicalNoteObjectId;
|
|
|
|
+ }
|
|
const voiceEntries = iterator.currentVoiceEntries?.[minIndex] ? [iterator.currentVoiceEntries?.[minIndex]] : [];
|
|
const voiceEntries = iterator.currentVoiceEntries?.[minIndex] ? [iterator.currentVoiceEntries?.[minIndex]] : [];
|
|
-
|
|
|
|
let currentVoiceEntries: any[] = [];
|
|
let currentVoiceEntries: any[] = [];
|
|
// 多分轨,当前小节最大音符数量
|
|
// 多分轨,当前小节最大音符数量
|
|
let maxNoteNum = 0;
|
|
let maxNoteNum = 0;
|
|
@@ -895,7 +962,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
if (state.multitrack > 0 && currentTime > note.length.realValue) {
|
|
if (state.multitrack > 0 && currentTime > note.length.realValue) {
|
|
currentTime = note.length.realValue;
|
|
currentTime = note.length.realValue;
|
|
}
|
|
}
|
|
- note.maxNoteNum = maxNoteNum
|
|
|
|
|
|
+ note.maxNoteNum = maxNoteNum;
|
|
|
|
+ note.trackIndex = minIndex;
|
|
_notes.push({
|
|
_notes.push({
|
|
note,
|
|
note,
|
|
iterator: { ...iterator },
|
|
iterator: { ...iterator },
|
|
@@ -976,6 +1044,10 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
}
|
|
}
|
|
|
|
|
|
activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[0]] || [];
|
|
activeVerticalMeasureList = [note.sourceMeasure?.verticalMeasureList?.[0]] || [];
|
|
|
|
+ // 某些情况下,合并显示的妙极客曲子,note.sourceMeasure?.verticalMeasureList可能为空数组
|
|
|
|
+ if (state.isCombineRender && state.isEvxml && note.sourceMeasure?.verticalMeasureList.length === 0) {
|
|
|
|
+ activeVerticalMeasureList = osmd.GraphicSheet.MeasureList.find((item: any) => item[0]?.MeasureNumber === note.sourceMeasure.MeasureNumberXML) || [];
|
|
|
|
+ }
|
|
let currenrtVfVoices = activeVerticalMeasureList[0]?.vfVoices['1'] ? activeVerticalMeasureList[0]?.vfVoices['1'] : activeVerticalMeasureList[0]?.vfVoices['2'] ? activeVerticalMeasureList[0]?.vfVoices['2'] : null;
|
|
let currenrtVfVoices = activeVerticalMeasureList[0]?.vfVoices['1'] ? activeVerticalMeasureList[0]?.vfVoices['1'] : activeVerticalMeasureList[0]?.vfVoices['2'] ? activeVerticalMeasureList[0]?.vfVoices['2'] : null;
|
|
/**
|
|
/**
|
|
* TODO:多分轨合并的小节,音符可能没有id,此时就去其它分轨找
|
|
* TODO:多分轨合并的小节,音符可能没有id,此时就去其它分轨找
|
|
@@ -1113,7 +1185,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
// 当前音符的持续时长,当前音符的RealValue值*拍数*(60/后台设置的基准速度)
|
|
// 当前音符的持续时长,当前音符的RealValue值*拍数*(60/后台设置的基准速度)
|
|
let noteLength = gradualLength ? gradualLength : Math.min(vRealValue, NoteRealValue) * formatBeatUnit(beatUnit) * (60 / beatSpeed);
|
|
let noteLength = gradualLength ? gradualLength : Math.min(vRealValue, NoteRealValue) * formatBeatUnit(beatUnit) * (60 / beatSpeed);
|
|
// 小节时长
|
|
// 小节时长
|
|
- const measureLength = vRealValue * vDenominator * (60 / beatSpeed);
|
|
|
|
|
|
+ const measureLength = vRealValue * 4 * (60 / beatSpeed);
|
|
// console.table({value: iterator.currentTimeStamp.realValue, vRealValue,NoteRealValue, noteLength,measureLength, MeasureNumberXML: note.sourceMeasure.MeasureNumberXML})
|
|
// console.table({value: iterator.currentTimeStamp.realValue, vRealValue,NoteRealValue, noteLength,measureLength, MeasureNumberXML: note.sourceMeasure.MeasureNumberXML})
|
|
// console.log(i, Math.min(vRealValue, NoteRealValue),noteLength,gradualLength, formatBeatUnit(beatUnit),beatSpeed, NoteRealValue * formatBeatUnit(beatUnit) * (60 / beatSpeed) )
|
|
// console.log(i, Math.min(vRealValue, NoteRealValue),noteLength,gradualLength, formatBeatUnit(beatUnit),beatSpeed, NoteRealValue * formatBeatUnit(beatUnit) * (60 / beatSpeed) )
|
|
/**
|
|
/**
|
|
@@ -1194,10 +1266,18 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
multipleRestMeasures = 0;
|
|
multipleRestMeasures = 0;
|
|
}
|
|
}
|
|
if (multipleRestMeasures < totalMultipleRestMeasures) {
|
|
if (multipleRestMeasures < totalMultipleRestMeasures) {
|
|
- multipleRestMeasures++;
|
|
|
|
|
|
+ if (note?.sourceMeasure?.MeasureNumberXML !== preNoteMeasureNumber) {
|
|
|
|
+ multipleRestMeasures++;
|
|
|
|
+ } else {
|
|
|
|
+ multipleRestMeasures = allNotes.length ? allNotes.last().multipleRestMeasures : 0;
|
|
|
|
+ }
|
|
} else {
|
|
} else {
|
|
- multipleRestMeasures = 0;
|
|
|
|
- totalMultipleRestMeasures = 0;
|
|
|
|
|
|
+ if (note?.sourceMeasure?.MeasureNumberXML !== preNoteMeasureNumber) {
|
|
|
|
+ multipleRestMeasures = 0;
|
|
|
|
+ totalMultipleRestMeasures = 0;
|
|
|
|
+ } else {
|
|
|
|
+ multipleRestMeasures = allNotes.length ? allNotes.last().multipleRestMeasures : 0;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
// console.log(note.tie)
|
|
// console.log(note.tie)
|
|
@@ -1205,10 +1285,47 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
// console.log('频率',note?.pitch?.frequency,i)
|
|
// console.log('频率',note?.pitch?.frequency,i)
|
|
/**
|
|
/**
|
|
* evxml的曲子,如果曲谱xml中带有times信息,则音符时值优先取times中的值
|
|
* evxml的曲子,如果曲谱xml中带有times信息,则音符时值优先取times中的值
|
|
|
|
+ * 曲子:1795013295024062466(春暖花开),如果音符有times信息,休止符没有times信息,此种规则是认为休止符不参与时值计算的,需要过滤掉该休止符
|
|
|
|
+ * TODO:需要考虑唱名怎么处理,唱名是xml有多少个音符,就需要唱多少个,不能剔除
|
|
*/
|
|
*/
|
|
|
|
+ if (state.isEvxml && note.isRestFlag && note?.noteTimeInfo?.length === 0 && state.xmlHasTimes ) {
|
|
|
|
+ const idx = _notes.findIndex(item=>item.note === note);
|
|
|
|
+ let nextNoteTimes = _notes[idx+1]?.note?.noteTimeInfo?.[0]?.begin*1000
|
|
|
|
+ let preNoteTImes = _notes[idx-1]?.note?.noteTimeInfo?.[0]?.end*1000
|
|
|
|
+ // 当下一个音符也没有时间的时候,再往下一个找
|
|
|
|
+ if(!nextNoteTimes && nextNoteTimes!==0){
|
|
|
|
+ let nextIndex = idx + 2
|
|
|
|
+ while(!nextNoteTimes && nextIndex<_notes.length){
|
|
|
|
+ nextNoteTimes = _notes[nextIndex]?.note?.noteTimeInfo?.[0]?.begin*1000
|
|
|
|
+ nextIndex ++
|
|
|
|
+ }
|
|
|
|
+ // 当最后音符就是没有打时间的休止小节,可能nextNoteTimes时间找不到,目前没有处理
|
|
|
|
+ }
|
|
|
|
+ if(!preNoteTImes && preNoteTImes!==0){
|
|
|
|
+ let preIndex = idx - 2
|
|
|
|
+ while(!preNoteTImes && preIndex>-1){
|
|
|
|
+ preNoteTImes = _notes[preIndex]?.note?.noteTimeInfo?.[0]?.end*1000
|
|
|
|
+ preIndex --
|
|
|
|
+ }
|
|
|
|
+ // 当没有找到preNoteTImes的时候 赋值为0 (当第一个音符就是没有打时间的休止小节会出现这种情况)
|
|
|
|
+ preNoteTImes || (preNoteTImes = 0)
|
|
|
|
+ }
|
|
|
|
+ const allowRange = Math.abs(nextNoteTimes - preNoteTImes)< 10;
|
|
|
|
+ if (allowRange) {
|
|
|
|
+ note.maxNoteNum = note.maxNoteNum - 1;
|
|
|
|
+ // 唱名时间补齐,当删除这个音符的时候,上个音符的持续时间要加上这个音符的时间
|
|
|
|
+ allNotes[allNotes.length - 1].noteLengthTime += noteLength
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
let evNoteStartTime = 0, evNoteEndTime = 0;
|
|
let evNoteStartTime = 0, evNoteEndTime = 0;
|
|
- if (state.isEvxml && note?.noteTimeInfo?.length) {
|
|
|
|
- const idx = noteIds.filter((item: any) => item === svgElement?.attrs.id)?.length || 0
|
|
|
|
|
|
+ if (state.isEvxml && note?.noteTimeInfo?.length ) {
|
|
|
|
+ let idx = noteIds.filter((item: any) => item === svgElement?.attrs.id)?.length || 0;
|
|
|
|
+ // 如果是合并的小节的休止符
|
|
|
|
+ if (note.isRestFlag && !svgElement && note?.NoteToGraphicalNoteObjectId) {
|
|
|
|
+ const customRestId = `rest-${note?.sourceMeasure?.MeasureNumberXML}-${note?.NoteToGraphicalNoteObjectId}`;
|
|
|
|
+ idx = noteIds.filter((item: any) => item === customRestId)?.length || 0;
|
|
|
|
+ }
|
|
evNoteStartTime = note?.noteTimeInfo[idx]?.begin
|
|
evNoteStartTime = note?.noteTimeInfo[idx]?.begin
|
|
evNoteEndTime = note?.noteTimeInfo[idx]?.end
|
|
evNoteEndTime = note?.noteTimeInfo[idx]?.end
|
|
if (evNoteStartTime) {
|
|
if (evNoteStartTime) {
|
|
@@ -1218,6 +1335,10 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
// usetime = evNoteStartTime - fixtime
|
|
// usetime = evNoteStartTime - fixtime
|
|
}
|
|
}
|
|
svgElement?.attrs.id && noteIds.push(svgElement?.attrs.id)
|
|
svgElement?.attrs.id && noteIds.push(svgElement?.attrs.id)
|
|
|
|
+ // 如果是合并的休止小节,是没有渲染音符的,所以没有svgElement对象,也就没有id,此时需要添加自定义的一个id进度,便于多遍循环时,找到对应的noteTimeInfo里面的时间信息
|
|
|
|
+ if (note.isRestFlag && !svgElement && note?.NoteToGraphicalNoteObjectId) {
|
|
|
|
+ noteIds.push(`rest-${note?.sourceMeasure?.MeasureNumberXML}-${note?.NoteToGraphicalNoteObjectId}`)
|
|
|
|
+ }
|
|
|
|
|
|
// 如果该音符包含倚音,添加标记
|
|
// 如果该音符包含倚音,添加标记
|
|
let hasGraceNote = false;
|
|
let hasGraceNote = false;
|
|
@@ -1226,6 +1347,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
}
|
|
}
|
|
const filterRepeatIdx = allNotes.filter((item: any) => item.noteId === note.NoteToGraphicalNoteObjectId).length
|
|
const filterRepeatIdx = allNotes.filter((item: any) => item.noteId === note.NoteToGraphicalNoteObjectId).length
|
|
const nodeDetail = {
|
|
const nodeDetail = {
|
|
|
|
+ trackIndex: note.trackIndex, // 当前的音符属于第几条分轨
|
|
isStaccato: note.voiceEntry.isStaccato(),
|
|
isStaccato: note.voiceEntry.isStaccato(),
|
|
isRestFlag: note.isRestFlag,
|
|
isRestFlag: note.isRestFlag,
|
|
noteId: note.NoteToGraphicalNoteObjectId,
|
|
noteId: note.NoteToGraphicalNoteObjectId,
|
|
@@ -1274,14 +1396,15 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
maxNoteNum: note.maxNoteNum, // 当前小节音符最多的分轨的音符数量
|
|
maxNoteNum: note.maxNoteNum, // 当前小节音符最多的分轨的音符数量
|
|
// repeatIdx: iterator.repeatIdx || 0, // 标记是第几遍循环,从0开始
|
|
// repeatIdx: iterator.repeatIdx || 0, // 标记是第几遍循环,从0开始
|
|
repeatIdx: filterRepeatIdx,
|
|
repeatIdx: filterRepeatIdx,
|
|
- xmlNoteTime: retain(xmlNoteTime), // xml上音符开始时间 唱名用
|
|
|
|
- xmlNoteEndTime: retain(xmlNoteTime + noteLength), //xml上音符结束时间 唱名用
|
|
|
|
|
|
+ noteLengthTime: noteLength, //当前音符时长
|
|
|
|
+ xmlNoteTime: 0, // xml上音符开始时间 唱名用
|
|
|
|
+ xmlNoteEndTime: 0, //xml上音符结束时间 唱名用
|
|
xmlMp3BeatFixTime, //xml上节拍器的时间
|
|
xmlMp3BeatFixTime, //xml上节拍器的时间
|
|
notBeatFixtime: state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime, // 不含节拍器的fixtime值 唱名用
|
|
notBeatFixtime: state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime, // 不含节拍器的fixtime值 唱名用
|
|
notBeatTime: state.isEvxml && evNoteStartTime ? retain(evNoteStartTime) : retain(relativeTime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)), // 不含节拍器的 音符开始时间
|
|
notBeatTime: state.isEvxml && evNoteStartTime ? retain(evNoteStartTime) : retain(relativeTime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)), // 不含节拍器的 音符开始时间
|
|
notBeatEndTime: state.isEvxml && evNoteEndTime ? retain(evNoteEndTime) : retain(relaEndtime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)) // 不含节拍器的 音符结束时间
|
|
notBeatEndTime: state.isEvxml && evNoteEndTime ? retain(evNoteEndTime) : retain(relaEndtime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)) // 不含节拍器的 音符结束时间
|
|
};
|
|
};
|
|
- xmlNoteTime += noteLength
|
|
|
|
|
|
+ // console.log(i,'当前的小节',nodeDetail.MeasureNumberXML,totalMultipleRestMeasures,multipleRestMeasures)
|
|
// 如果是妙极客的曲子,并且第二遍循环播放需要等待时间,并且是第二遍循环的第一个小节的第一个音符
|
|
// 如果是妙极客的曲子,并且第二遍循环播放需要等待时间,并且是第二遍循环的第一个小节的第一个音符
|
|
// if (state.isEvxml && state.secondEvXmlBeginTime && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
|
|
// if (state.isEvxml && state.secondEvXmlBeginTime && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
|
|
// nodeDetail.time = nodeDetail.time + state.secondEvXmlBeginTime;
|
|
// nodeDetail.time = nodeDetail.time + state.secondEvXmlBeginTime;
|
|
@@ -1289,7 +1412,9 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
// usetime = usetime + state.secondEvXmlBeginTime;
|
|
// usetime = usetime + state.secondEvXmlBeginTime;
|
|
// relativeTime = relativeTime + state.secondEvXmlBeginTime;
|
|
// relativeTime = relativeTime + state.secondEvXmlBeginTime;
|
|
// }
|
|
// }
|
|
- if (state.isEvxml && nodeDetail.repeatIdx && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
|
|
|
|
|
|
+ // if (state.isEvxml && nodeDetail.repeatIdx && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
|
|
|
|
+ const firstRepeatNodeId = allNotes.find((item: any) => item.MeasureNumberXML === state.timegapRepeatMeasureIndex)?.noteId || 0;
|
|
|
|
+ if (state.isEvxml && nodeDetail.repeatIdx && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === state.timegapRepeatMeasureIndex && nodeDetail.noteId === firstRepeatNodeId) {
|
|
const currentWaitTime = state.evXmlBeginArr[nodeDetail.repeatIdx] || 0;
|
|
const currentWaitTime = state.evXmlBeginArr[nodeDetail.repeatIdx] || 0;
|
|
nodeDetail.time = nodeDetail.time + currentWaitTime;
|
|
nodeDetail.time = nodeDetail.time + currentWaitTime;
|
|
nodeDetail.endtime = nodeDetail.endtime + currentWaitTime;
|
|
nodeDetail.endtime = nodeDetail.endtime + currentWaitTime;
|
|
@@ -1306,11 +1431,22 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
// console.log('👀看看endtime', nodeDetail.duration, relaEndtime, fixtime, i)
|
|
// console.log('👀看看endtime', nodeDetail.duration, relaEndtime, fixtime, i)
|
|
// console.log('音符时间',nodeDetail.i,nodeDetail.time,nodeDetail.endtime)
|
|
// console.log('音符时间',nodeDetail.i,nodeDetail.time,nodeDetail.endtime)
|
|
tickables = tickables.filter((tickable: any) => tickable.attrs?.type !== "GhostNote")
|
|
tickables = tickables.filter((tickable: any) => tickable.attrs?.type !== "GhostNote")
|
|
- const maxNum = (state.isCombineRender && note.maxNoteNum) ? note.maxNoteNum : tickables.length;
|
|
|
|
|
|
+ let maxNum = (state.isCombineRender && note.maxNoteNum) ? note.maxNoteNum : tickables.length;
|
|
|
|
+ // 妙极客的曲子,一个休止小节内可能有多个休止符,此时maxNum是0,需要针对这种情况作处理
|
|
|
|
+ if (note.isRestFlag && maxNum === 0) {
|
|
|
|
+ maxNum = note.maxNoteNum;
|
|
|
|
+ }
|
|
nodeDetail.noteLength = maxNum || 1;
|
|
nodeDetail.noteLength = maxNum || 1;
|
|
allNotes.push(nodeDetail);
|
|
allNotes.push(nodeDetail);
|
|
allNoteId.push(nodeDetail.id);
|
|
allNoteId.push(nodeDetail.id);
|
|
- measures.push(nodeDetail);
|
|
|
|
|
|
+
|
|
|
|
+ if ( measures.some((item: any) => item.MeasureNumberXML !== nodeDetail.MeasureNumberXML) ) {
|
|
|
|
+ measures = [];
|
|
|
|
+ measures.push(nodeDetail);
|
|
|
|
+ nodeDetail.measures = measures;
|
|
|
|
+ } else {
|
|
|
|
+ measures.push(nodeDetail);
|
|
|
|
+ }
|
|
/**
|
|
/**
|
|
* bug: #9877
|
|
* bug: #9877
|
|
* 多分轨合并展示的曲子,不同分轨,同一小节音符的数量可能不能,不能只通过tickables的长度判断该小节的音符数量
|
|
* 多分轨合并展示的曲子,不同分轨,同一小节音符的数量可能不能,不能只通过tickables的长度判断该小节的音符数量
|
|
@@ -1324,12 +1460,21 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
|
|
}
|
|
}
|
|
preNoteEndTime = nodeDetail.endtime;
|
|
preNoteEndTime = nodeDetail.endtime;
|
|
}
|
|
}
|
|
|
|
+ preNoteMeasureNumber = note?.sourceMeasure?.MeasureNumberXML;
|
|
i++;
|
|
i++;
|
|
}
|
|
}
|
|
// 按照时间轴排序
|
|
// 按照时间轴排序
|
|
const sortArray = allNotes.sort((a, b) => a.relativeTime - b.relativeTime).map((item, index) => Object.assign(item,{i:index}));
|
|
const sortArray = allNotes.sort((a, b) => a.relativeTime - b.relativeTime).map((item, index) => Object.assign(item,{i:index}));
|
|
// const sortArray = allNotes.sort((a, b) => a.time - b.time).map((item, index) => ({ ...item, i: index }));
|
|
// const sortArray = allNotes.sort((a, b) => a.time - b.time).map((item, index) => ({ ...item, i: index }));
|
|
// const sortArray = allNotes.map((item, index) => ({ ...item, i: index }));
|
|
// const sortArray = allNotes.map((item, index) => ({ ...item, i: index }));
|
|
|
|
+ // 给 xmlNoteTime 和 xmlNoteEndTime 赋值
|
|
|
|
+ let xmlNoteTime = 0
|
|
|
|
+ sortArray.map(item => {
|
|
|
|
+ const noteLengthTime = item.noteLengthTime
|
|
|
|
+ item.xmlNoteTime = retain(xmlNoteTime)
|
|
|
|
+ item.xmlNoteEndTime = retain(xmlNoteTime + noteLengthTime)
|
|
|
|
+ xmlNoteTime += noteLengthTime
|
|
|
|
+ })
|
|
console.timeEnd("音符跑完时间");
|
|
console.timeEnd("音符跑完时间");
|
|
try {
|
|
try {
|
|
osmd.cursor.reset();
|
|
osmd.cursor.reset();
|
|
@@ -1435,16 +1580,19 @@ const customizationXml = (xmlParse: any) => {
|
|
const measures: any[] = Array.from(xmlParse.getElementsByTagName("measure"));
|
|
const measures: any[] = Array.from(xmlParse.getElementsByTagName("measure"));
|
|
const notes: any[] = Array.from(xmlParse.getElementsByTagName("note"));
|
|
const notes: any[] = Array.from(xmlParse.getElementsByTagName("note"));
|
|
|
|
|
|
- // 获取音符最多的歌词数,用于自定义循环播放次数
|
|
|
|
- let maxLyricNum = 0;
|
|
|
|
|
|
+ // 获取音符最多的歌词数,time最多的次数,取两者的最大值,用于自定义循环播放次数
|
|
|
|
+ let maxLyricNum = 0, maxTimeNum = 0;
|
|
if (notes && notes.length) {
|
|
if (notes && notes.length) {
|
|
for (const note of notes) {
|
|
for (const note of notes) {
|
|
if (maxLyricNum < note.getElementsByTagName("lyric").length) {
|
|
if (maxLyricNum < note.getElementsByTagName("lyric").length) {
|
|
maxLyricNum = note.getElementsByTagName("lyric").length
|
|
maxLyricNum = note.getElementsByTagName("lyric").length
|
|
}
|
|
}
|
|
|
|
+ if (maxTimeNum < note.getElementsByTagName("time").length) {
|
|
|
|
+ maxTimeNum = note.getElementsByTagName("time").length
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- state.maxLyricNum = maxLyricNum;
|
|
|
|
|
|
+ state.maxLyricNum = Math.max(maxLyricNum, maxTimeNum);
|
|
// state.osmd.EngravingRules.DYCustomRepeatCount = maxLyricNum;
|
|
// state.osmd.EngravingRules.DYCustomRepeatCount = maxLyricNum;
|
|
;(window as any).DYCustomRepeatCount = state.maxLyricNum;
|
|
;(window as any).DYCustomRepeatCount = state.maxLyricNum;
|
|
console.log('歌词次数',maxLyricNum)
|
|
console.log('歌词次数',maxLyricNum)
|
|
@@ -1489,20 +1637,26 @@ const customizationXml = (xmlParse: any) => {
|
|
* 妙极客xml,多遍歌词循环的曲目,如果没有repeat标签,需要加上repeat标签
|
|
* 妙极客xml,多遍歌词循环的曲目,如果没有repeat标签,需要加上repeat标签
|
|
* */
|
|
* */
|
|
if (maxLyricNum > 1) {
|
|
if (maxLyricNum > 1) {
|
|
- const hasRepeat = xmlParse.querySelectorAll('repeat').length > 0
|
|
|
|
|
|
+ const hasRepeat = xmlParse.querySelectorAll('repeat').length > 0;
|
|
if (!hasRepeat) {
|
|
if (!hasRepeat) {
|
|
- const lastMeasure = measures.last();
|
|
|
|
- if (lastMeasure.getElementsByTagName('barline').length) {
|
|
|
|
- const barlineDom = lastMeasure.getElementsByTagName('barline')[0]
|
|
|
|
- barlineDom.innerHTML = barlineDom.innerHTML + `<repeat direction="backward" />`;
|
|
|
|
- } else {
|
|
|
|
- lastMeasure.innerHTML = lastMeasure.innerHTML + `
|
|
|
|
- <barline location="right">
|
|
|
|
- <bar-style>light-heavy</bar-style>
|
|
|
|
- <repeat direction="backward" />
|
|
|
|
- </barline>`
|
|
|
|
|
|
+ const parts = xmlParse.querySelectorAll('score-partwise>part')
|
|
|
|
+ if (parts.length) {
|
|
|
|
+ for (const part of parts) {
|
|
|
|
+ const currentMeasures = part.querySelectorAll('measure').length ? Array.from(part.querySelectorAll('measure')) : [];
|
|
|
|
+ const lastMeasure: any = currentMeasures.last();
|
|
|
|
+ if (lastMeasure?.getElementsByTagName('barline').length) {
|
|
|
|
+ const barlineDom = lastMeasure?.getElementsByTagName('barline')[0]
|
|
|
|
+ barlineDom.innerHTML = barlineDom.innerHTML + `<repeat direction="backward" />`;
|
|
|
|
+ } else {
|
|
|
|
+ lastMeasure.innerHTML = lastMeasure.innerHTML + `
|
|
|
|
+ <barline location="right">
|
|
|
|
+ <bar-style>light-heavy</bar-style>
|
|
|
|
+ <repeat direction="backward" />
|
|
|
|
+ </barline>`
|
|
|
|
+ }
|
|
|
|
+ // console.log(lastMeasure)
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- // console.log(lastMeasure)
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1515,16 +1669,26 @@ const analyzeEvxml = (xmlParse: any, xmlUrl?: string) => {
|
|
const xmlNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('num');
|
|
const xmlNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('num');
|
|
const denNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('den');
|
|
const denNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('den');
|
|
const timeGaps: any = xmlParse.getElementsByTagName("timegap")?.length ? Array.from(xmlParse.getElementsByTagName("timegap")?.[0]?.getElementsByTagName("values")?.[0]?.getElementsByTagName("item")) : [];
|
|
const timeGaps: any = xmlParse.getElementsByTagName("timegap")?.length ? Array.from(xmlParse.getElementsByTagName("timegap")?.[0]?.getElementsByTagName("values")?.[0]?.getElementsByTagName("item")) : [];
|
|
|
|
+ state.xmlHasTimes = !!xmlParse.getElementsByTagName("times")?.length
|
|
// 第一个音符的起始时间
|
|
// 第一个音符的起始时间
|
|
const firstMeasure = xmlParse.getElementsByTagName("measure")[0];
|
|
const firstMeasure = xmlParse.getElementsByTagName("measure")[0];
|
|
if (firstMeasure) {
|
|
if (firstMeasure) {
|
|
const firstNoteBeginTime = firstMeasure.getElementsByTagName("times")[0]?.getElementsByTagName("time")[0]?.getAttribute('begin');
|
|
const firstNoteBeginTime = firstMeasure.getElementsByTagName("times")[0]?.getElementsByTagName("time")[0]?.getAttribute('begin');
|
|
state.evXmlBeginTime = firstNoteBeginTime ? firstNoteBeginTime / 1000 : xmlNum ? 60 / state.originSpeed * xmlNum * 4/denNum : 0;
|
|
state.evXmlBeginTime = firstNoteBeginTime ? firstNoteBeginTime / 1000 : xmlNum ? 60 / state.originSpeed * xmlNum * 4/denNum : 0;
|
|
state.secondEvXmlBeginTime = firstNoteBeginTime ? 0 : xmlNum2 ? 60 / state.originSpeed * xmlNum2 * 4/denNum2 : 0;
|
|
state.secondEvXmlBeginTime = firstNoteBeginTime ? 0 : xmlNum2 ? 60 / state.originSpeed * xmlNum2 * 4/denNum2 : 0;
|
|
- const hasTimeGap = xmlParse.getElementsByTagName("timegap").length > 0;
|
|
|
|
|
|
+ const hasTimeGap = state.xmlHasTimeGap = xmlParse.getElementsByTagName("timegap").length > 0;
|
|
const hasTimes = xmlParse.getElementsByTagName("times").length > 0;
|
|
const hasTimes = xmlParse.getElementsByTagName("times").length > 0;
|
|
-
|
|
|
|
if (timeGaps && timeGaps.length && !firstNoteBeginTime) {
|
|
if (timeGaps && timeGaps.length && !firstNoteBeginTime) {
|
|
|
|
+ // 有timegap的曲子,需要找到是从哪一小节开始循环的,默认是从第一节开始循环
|
|
|
|
+ const startRepeat = Array.from(xmlParse.getElementsByTagName("repeat") || []).filter((item: any) => item?.getAttribute('direction') === 'forward')
|
|
|
|
+ const firstRepeat: any = startRepeat?.length ? startRepeat[0] : null;
|
|
|
|
+ if (firstRepeat) {
|
|
|
|
+ let parentElement = firstRepeat?.parentNode
|
|
|
|
+ while (parentElement && parentElement.tagName !== 'measure') {
|
|
|
|
+ parentElement = parentElement.parentNode
|
|
|
|
+ }
|
|
|
|
+ state.timegapRepeatMeasureIndex = parentElement?.getAttribute('number') ? Number(parentElement?.getAttribute('number')) : 1;
|
|
|
|
+ }
|
|
for (const timeGap of timeGaps) {
|
|
for (const timeGap of timeGaps) {
|
|
const num: any = timeGap?.getAttribute('num'), den: any = timeGap?.getAttribute('den');
|
|
const num: any = timeGap?.getAttribute('num'), den: any = timeGap?.getAttribute('den');
|
|
const startTime = num ? 60 / state.originSpeed * num * 4/den : 0;
|
|
const startTime = num ? 60 / state.originSpeed * num * 4/den : 0;
|