|  | @@ -1,11 +1,9 @@
 | 
	
		
			
				|  |  |  import dayjs from "dayjs";
 | 
	
		
			
				|  |  |  import duration from "dayjs/plugin/duration";
 | 
	
		
			
				|  |  |  import state from "../state";
 | 
	
		
			
				|  |  | -// import appState from "/src/state";
 | 
	
		
			
				|  |  |  import { browser } from "../utils/index";
 | 
	
		
			
				|  |  |  import { isSpecialMark, isSpeedKeyword, Fraction, SourceMeasure, isGradientWords, GRADIENT_SPEED_RESET_TAG, StringUtil, OpenSheetMusicDisplay } from "/osmd-extended/src";
 | 
	
		
			
				|  |  |  import { GradualChange, speedInfo } from "./calcSpeed";
 | 
	
		
			
				|  |  | -import { useRoute } from "vue-router";
 | 
	
		
			
				|  |  |  const browserInfo = browser();
 | 
	
		
			
				|  |  |  dayjs.extend(duration);
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -27,9 +25,6 @@ export const getFixTime = (speed: number) => {
 | 
	
		
			
				|  |  |  	return state.isOpenMetronome ? (60 / speed) * formatBeatUnit(beatUnit) * (numerator / denominator) : 0;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -const getLinkId = (): string => {
 | 
	
		
			
				|  |  | -	return location.hash.split("?")[0].split("/").pop() || "";
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  export const retain = (time: number) => {
 | 
	
		
			
				|  |  |  	return Math.ceil(time * 1000000) / 1000000;
 | 
	
	
		
			
				|  | @@ -56,19 +51,6 @@ export const getMeasureDurationDiff = (measure: any) => {
 | 
	
		
			
				|  |  |  	return SRealValue - RRealValue;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -// const speedInfo: {[key in string]: number} = {
 | 
	
		
			
				|  |  | -//   'rall.': 1.333333333,
 | 
	
		
			
				|  |  | -//   'poco rit.': 1.333333333,
 | 
	
		
			
				|  |  | -//   'rit.': 1.333333333,
 | 
	
		
			
				|  |  | -//   'molto rit.': 1.333333333,
 | 
	
		
			
				|  |  | -//   'molto rall': 1.333333333,
 | 
	
		
			
				|  |  | -//   'lentando': 1.333333333,
 | 
	
		
			
				|  |  | -//   'allargando': 1.333333333,
 | 
	
		
			
				|  |  | -//   'morendo': 1.333333333,
 | 
	
		
			
				|  |  | -//   'accel.': .8,
 | 
	
		
			
				|  |  | -//   'calando': 2,
 | 
	
		
			
				|  |  | -//   'poco accel.': .8,
 | 
	
		
			
				|  |  | -// }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /** 按照dorico的渐快渐慢生成对应的速度 */
 | 
	
		
			
				|  |  |  export const createSpeedInfo = (gradualChange: GradualChange | undefined, speed: number) => {
 | 
	
	
		
			
				|  | @@ -99,91 +81,6 @@ const tranTime = (str: string = "") => {
 | 
	
		
			
				|  |  |  	return `1970-01-01 00:${result}0`;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -export const getAllNoteElements = (osmd: any) => {
 | 
	
		
			
				|  |  | -	const list: any[] = [];
 | 
	
		
			
				|  |  | -	const listById: {
 | 
	
		
			
				|  |  | -		[key: string]: any;
 | 
	
		
			
				|  |  | -	} = {};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	for (const measure of osmd.drawer.graphicalMusicSheet.measureList) {
 | 
	
		
			
				|  |  | -		const activeMeasure = measure[0];
 | 
	
		
			
				|  |  | -		for (const tickable of activeMeasure.vfVoices["1"].tickables) {
 | 
	
		
			
				|  |  | -			list.push(tickable);
 | 
	
		
			
				|  |  | -			listById[tickable.attrs.id] = tickable;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	return {
 | 
	
		
			
				|  |  | -		list,
 | 
	
		
			
				|  |  | -		listById,
 | 
	
		
			
				|  |  | -	};
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -export const setStepIndex = (osmd: any, num: number, prev?: number) => {
 | 
	
		
			
				|  |  | -	if (num || num === 0) {
 | 
	
		
			
				|  |  | -		// console.log(prev, num)
 | 
	
		
			
				|  |  | -		if (prev && num - prev === 1) {
 | 
	
		
			
				|  |  | -			osmd.cursor.next();
 | 
	
		
			
				|  |  | -		} else if (prev && num - prev > 0) {
 | 
	
		
			
				|  |  | -			while (num - prev > 0) {
 | 
	
		
			
				|  |  | -				prev++;
 | 
	
		
			
				|  |  | -				num - prev > 0;
 | 
	
		
			
				|  |  | -				osmd.cursor.next();
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		} else {
 | 
	
		
			
				|  |  | -			let i = 0;
 | 
	
		
			
				|  |  | -			osmd.cursor.reset();
 | 
	
		
			
				|  |  | -			while (i < num) {
 | 
	
		
			
				|  |  | -				i++;
 | 
	
		
			
				|  |  | -				if (osmd.cursor.hidden !== false) {
 | 
	
		
			
				|  |  | -					osmd.cursor.show();
 | 
	
		
			
				|  |  | -				} else {
 | 
	
		
			
				|  |  | -					osmd.cursor.next();
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -export const getIndex = (times: any[], currentTime: Number) => {
 | 
	
		
			
				|  |  | -	// console.log(currentTime)
 | 
	
		
			
				|  |  | -	if (currentTime > state.times[state.times.length - 1].endtime) {
 | 
	
		
			
				|  |  | -		return state.times.length - 1;
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	let index = 0;
 | 
	
		
			
				|  |  | -	for (let i = 0; i < times.length; i++) {
 | 
	
		
			
				|  |  | -		const item = times[i];
 | 
	
		
			
				|  |  | -		const prevItem = times[i - 1];
 | 
	
		
			
				|  |  | -		if (currentTime >= item.time) {
 | 
	
		
			
				|  |  | -			if (!prevItem || item.time != prevItem.time) {
 | 
	
		
			
				|  |  | -				index = item.i;
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		} else {
 | 
	
		
			
				|  |  | -			break;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	if (state.sectionStatus && state.section.length === 2) {
 | 
	
		
			
				|  |  | -		// 限制不超过此范围
 | 
	
		
			
				|  |  | -		const startSection = state.befireSection || state.section[0];
 | 
	
		
			
				|  |  | -		index = Math.min(Math.max(index, startSection.i), state.section[1].i);
 | 
	
		
			
				|  |  | -		// console.log('endIndex', index)
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return index;
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -/**
 | 
	
		
			
				|  |  | - * 获取连音线的音符
 | 
	
		
			
				|  |  | - * @param note
 | 
	
		
			
				|  |  | - */
 | 
	
		
			
				|  |  | -export const getTone = (note: any) => {
 | 
	
		
			
				|  |  | -	for (const slur of note.noteElement.slurs) {
 | 
	
		
			
				|  |  | -		if (slur.startNote.halfTone === slur.endNote.halfTone) {
 | 
	
		
			
				|  |  | -			return slur.startNote === note.noteElement ? slur.endNote : slur.startNote;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return null;
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  export const getSlursNote = (note: any, pos?: "start" | "end") => {
 | 
	
		
			
				|  |  |  	return pos === "end" ? note.noteElement.slurs[0]?.endNote : note.noteElement.slurs[0]?.startNote;
 | 
	
		
			
				|  |  |  };
 | 
	
	
		
			
				|  | @@ -192,24 +89,6 @@ export const getNoteBySlursStart = (note: any, anyNoteHasSlurs?: boolean, pos?:
 | 
	
		
			
				|  |  |  	let activeNote = note;
 | 
	
		
			
				|  |  |  	let slursNote = getSlursNote(activeNote, pos);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	// if (!slursNote && anyNoteHasSlurs) {
 | 
	
		
			
				|  |  | -	//   for (const item of activeNote.measures) {
 | 
	
		
			
				|  |  | -	//     if (item.noteElement.slurs.length) {
 | 
	
		
			
				|  |  | -	//       slursNote = getSlursNote(item, pos)
 | 
	
		
			
				|  |  | -	//       activeNote = item
 | 
	
		
			
				|  |  | -	//     }
 | 
	
		
			
				|  |  | -	//   }
 | 
	
		
			
				|  |  | -	// }
 | 
	
		
			
				|  |  | -	// if (activeNote && slursNote !== activeNote.noteElement) {
 | 
	
		
			
				|  |  | -	//   // time = activeNote.time
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	//   for (const note of state.times) {
 | 
	
		
			
				|  |  | -	//     if (slursNote === note.noteElement) {
 | 
	
		
			
				|  |  | -	//       return note
 | 
	
		
			
				|  |  | -	//     }
 | 
	
		
			
				|  |  | -	//   }
 | 
	
		
			
				|  |  | -	// }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  	for (const item of activeNote.measures) {
 | 
	
		
			
				|  |  |  		if (item.noteElement.slurs.length) {
 | 
	
		
			
				|  |  |  			slursNote = getSlursNote(item, pos);
 | 
	
	
		
			
				|  | @@ -234,106 +113,6 @@ export const getParentNote = (note: any) => {
 | 
	
		
			
				|  |  |  	return parentNote;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/** 获取小节之间的连音线,仅同音高*/
 | 
	
		
			
				|  |  | -export const getNoteByMeasuresSlursStart = (note: any) => {
 | 
	
		
			
				|  |  | -	let activeNote = note;
 | 
	
		
			
				|  |  | -	let tieNote;
 | 
	
		
			
				|  |  | -	// console.log(note.noteElement)
 | 
	
		
			
				|  |  | -	if (note.noteElement.tie && note.noteElement.tie.StartNote) {
 | 
	
		
			
				|  |  | -		tieNote = note.noteElement.tie.StartNote;
 | 
	
		
			
				|  |  | -		// for (const s of Array.from(note.noteElement.tie) as any[]) {
 | 
	
		
			
				|  |  | -		//   const SMeasure = s.startNote.sourceMeasure
 | 
	
		
			
				|  |  | -		//   const EMeasure = s.endNote.sourceMeasure
 | 
	
		
			
				|  |  | -		//   const isNotSelf = SMeasure.MeasureNumberXML !== EMeasure.MeasureNumberXML
 | 
	
		
			
				|  |  | -		//   const isHomophony = s.startNote.noteElement.halfTone === s.endNote.noteElement.halfTone
 | 
	
		
			
				|  |  | -		//   const isNotEndNote = s.endNote !== note.noteElement
 | 
	
		
			
				|  |  | -		//   if (s && isNotSelf && isNotEndNote && isHomophony) {
 | 
	
		
			
				|  |  | -		//     tieNote = s.startNote
 | 
	
		
			
				|  |  | -		//     break
 | 
	
		
			
				|  |  | -		//   }
 | 
	
		
			
				|  |  | -		// }
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	if (activeNote && tieNote && tieNote !== activeNote.noteElement) {
 | 
	
		
			
				|  |  | -		// time = activeNote.time
 | 
	
		
			
				|  |  | -		for (const note of state.times) {
 | 
	
		
			
				|  |  | -			if (tieNote === note.noteElement) {
 | 
	
		
			
				|  |  | -				console.log(note);
 | 
	
		
			
				|  |  | -				return note;
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return activeNote;
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -export const getActtiveNoteByTimes = (evt: MouseEvent) => {
 | 
	
		
			
				|  |  | -	const el = (evt.target as HTMLDivElement)?.dataset;
 | 
	
		
			
				|  |  | -	// console.log(state)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	const data: any = {};
 | 
	
		
			
				|  |  | -	for (const time of state.times) {
 | 
	
		
			
				|  |  | -		if (time.id && !data[time.id]) {
 | 
	
		
			
				|  |  | -			data[time.id] = time;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	const activeNote = data[el.id || ""];
 | 
	
		
			
				|  |  | -	// state.timesById = data
 | 
	
		
			
				|  |  | -	return activeNote;
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -const getPrevHasSourceNote = (note: any) => {
 | 
	
		
			
				|  |  | -	const indexOf = Math.max(state.times.indexOf(note) - 1, 0);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	for (let index = indexOf; index >= 0; index--) {
 | 
	
		
			
				|  |  | -		const item = state.times[index];
 | 
	
		
			
				|  |  | -		if (item?.stave) {
 | 
	
		
			
				|  |  | -			return item;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -export const getBoundingBoxByverticalNote = (note: any) => {
 | 
	
		
			
				|  |  | -	let measures = note?.noteElement?.sourceMeasure?.verticalMeasureList;
 | 
	
		
			
				|  |  | -	measures = !measures || !measures[0] ? note?.noteElement?.isRestFlag && getPrevHasSourceNote(note)?.noteElement?.sourceMeasure?.verticalMeasureList : measures;
 | 
	
		
			
				|  |  | -	let height = 0;
 | 
	
		
			
				|  |  | -	if (measures) {
 | 
	
		
			
				|  |  | -		const firstMeasure = measures[state.partIndex];
 | 
	
		
			
				|  |  | -		for (let index = 0; index < measures.length; index++) {
 | 
	
		
			
				|  |  | -			const measure = measures[index];
 | 
	
		
			
				|  |  | -			if (measure?.stave) {
 | 
	
		
			
				|  |  | -				const { height: measureHeight } = measure?.stave;
 | 
	
		
			
				|  |  | -				if (index > 0) {
 | 
	
		
			
				|  |  | -					height += measures[index - 1]?.stave.height;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				height += measureHeight;
 | 
	
		
			
				|  |  | -				const { x, y, width, context, start_x, end_x } = firstMeasure?.stave;
 | 
	
		
			
				|  |  | -				return {
 | 
	
		
			
				|  |  | -					measureIndex: note?.noteElement?.sourceMeasure.measureListIndex || 0,
 | 
	
		
			
				|  |  | -					MeasureNumberXML: note?.noteElement?.sourceMeasure.MeasureNumberXML || 1,
 | 
	
		
			
				|  |  | -					start_x,
 | 
	
		
			
				|  |  | -					end_x,
 | 
	
		
			
				|  |  | -					height,
 | 
	
		
			
				|  |  | -					x,
 | 
	
		
			
				|  |  | -					y,
 | 
	
		
			
				|  |  | -					width,
 | 
	
		
			
				|  |  | -					context,
 | 
	
		
			
				|  |  | -					numerator: firstMeasure?.parentSourceMeasure?.ActiveTimeSignature?.numerator,
 | 
	
		
			
				|  |  | -				};
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return {
 | 
	
		
			
				|  |  | -		measureIndex: 0,
 | 
	
		
			
				|  |  | -		height,
 | 
	
		
			
				|  |  | -		start_x: 0,
 | 
	
		
			
				|  |  | -		end_x: 0,
 | 
	
		
			
				|  |  | -		x: 0,
 | 
	
		
			
				|  |  | -		y: 0,
 | 
	
		
			
				|  |  | -		width: 0,
 | 
	
		
			
				|  |  | -		context: {
 | 
	
		
			
				|  |  | -			element: null,
 | 
	
		
			
				|  |  | -		},
 | 
	
		
			
				|  |  | -	};
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  export type FractionDefault = {
 | 
	
		
			
				|  |  |  	numerator: number;
 | 
	
	
		
			
				|  | @@ -445,66 +224,6 @@ export function getTimeByBeatUnit(beatUnit: string, bpm: number, denominator: nu
 | 
	
		
			
				|  |  |  	return (denominator / formatBeatUnit(beatUnit)) * bpm;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/** 获取第一个小节的节拍速度,替换初始的速度 */
 | 
	
		
			
				|  |  | -export const getFirstBpmByMeasures = (measures: SourceMeasure[]) => {
 | 
	
		
			
				|  |  | -	for (const item of measures) {
 | 
	
		
			
				|  |  | -		if (item.TempoInBPM) {
 | 
	
		
			
				|  |  | -			return getMeasureRealBpm(item);
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return 90;
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -/**
 | 
	
		
			
				|  |  | - * 根据小节获取当前节拍下真实的速度
 | 
	
		
			
				|  |  | - * 如: 6/8拍 4分音符bpm速度为120 则计算出8分音符的速度为120 * 2
 | 
	
		
			
				|  |  | - */
 | 
	
		
			
				|  |  | -export const getMeasureRealBpm = (measure: SourceMeasure) => {
 | 
	
		
			
				|  |  | -	let bpm = measure.TempoInBPM;
 | 
	
		
			
				|  |  | -	let tempoExpression = null;
 | 
	
		
			
				|  |  | -	let activeTimeSignature: Fraction | null = null;
 | 
	
		
			
				|  |  | -	for (const expression of measure?.TempoExpressions) {
 | 
	
		
			
				|  |  | -		if (expression?.InstantaneousTempo.beatUnit) {
 | 
	
		
			
				|  |  | -			// 取最后一个有效的tempo
 | 
	
		
			
				|  |  | -			tempoExpression = expression;
 | 
	
		
			
				|  |  | -			activeTimeSignature = measure.ActiveTimeSignature;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	if (tempoExpression && activeTimeSignature) {
 | 
	
		
			
				|  |  | -		bpm = tempoExpression?.InstantaneousTempo.TempoInBpm;
 | 
	
		
			
				|  |  | -		// let multiple = 1
 | 
	
		
			
				|  |  | -		// console.log(tempoExpression.InstantaneousTempo.beatUnit)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		bpm = getTimeByBeatUnit(tempoExpression.InstantaneousTempo.beatUnit, bpm, activeTimeSignature.Denominator);
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return bpm;
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -export const getEnvHostname = () => {
 | 
	
		
			
				|  |  | -	if (location.origin.indexOf("online") > -1) {
 | 
	
		
			
				|  |  | -		return "https://mstuonline.dayaedu.com";
 | 
	
		
			
				|  |  | -	} else if (location.origin.indexOf("dev") > -1) {
 | 
	
		
			
				|  |  | -		return "http://mstudev.dayaedu.com";
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return "https://mstutest.dayaedu.com";
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -export const getTvIconUrl = () => {
 | 
	
		
			
				|  |  | -	if (location.origin.indexOf("online") > -1) {
 | 
	
		
			
				|  |  | -		return "https://mteaonline.dayaedu.com/#/guide";
 | 
	
		
			
				|  |  | -	} else if (location.origin.indexOf("dev") > -1) {
 | 
	
		
			
				|  |  | -		return "http://mteadev.dayaedu.com/#/guide";
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return "https://mteatest.dayaedu.com/#/guide";
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -export const setPrefix = (url: string): string => {
 | 
	
		
			
				|  |  | -	if (url) {
 | 
	
		
			
				|  |  | -		return "?" + url;
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return "";
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  export type CustomInfo = {
 | 
	
		
			
				|  |  |  	showSpeed: boolean;
 | 
	
		
			
				|  |  |  	parsedXML: string;
 | 
	
	
		
			
				|  | @@ -608,7 +327,7 @@ export const isRepeatWord = (text: string): boolean => {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  export const onlyVisible = (xml: string, partIndex: number): string => {
 | 
	
		
			
				|  |  |  	if (!xml) return "";
 | 
	
		
			
				|  |  | -	const detailId = getLinkId();
 | 
	
		
			
				|  |  | +	const detailId = state.examSongId;
 | 
	
		
			
				|  |  |  	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
 | 
	
		
			
				|  |  |  	const partList = xmlParse.getElementsByTagName("part-list")?.[0]?.getElementsByTagName("score-part") || [];
 | 
	
		
			
				|  |  |  	const partListNames = Array.from(partList).map((item) => item.getElementsByTagName("part-name")?.[0].textContent || "");
 | 
	
	
		
			
				|  | @@ -657,10 +376,6 @@ export const onlyVisible = (xml: string, partIndex: number): string => {
 | 
	
		
			
				|  |  |  						const index = firstMeasures.indexOf(parentMeasure);
 | 
	
		
			
				|  |  |  						const activeMeasure = part.getElementsByTagName("measure")[index];
 | 
	
		
			
				|  |  |  						setElementNoteBefore(metronomeContainer, parentMeasure, activeMeasure);
 | 
	
		
			
				|  |  | -						//   console.log(measureMetronomes, metronomesIndex, activeMeasure?.childNodes, activeMeasure?.childNodes[metronomesIndex])
 | 
	
		
			
				|  |  | -						//   activeMeasure?.insertBefore(metronomeContainer.cloneNode(true), activeMeasure?.childNodes[metronomesIndex])
 | 
	
		
			
				|  |  | -						//   // part.getElementsByTagName('measure')[index]?.appendChild(metronomeContainer.cloneNode(true))
 | 
	
		
			
				|  |  | -						//   // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
 | 
	
		
			
				|  |  |  					}
 | 
	
		
			
				|  |  |  				});
 | 
	
		
			
				|  |  |  				/** word比较特殊需要精确到note位置 */
 | 
	
	
		
			
				|  | @@ -755,29 +470,6 @@ export const onlyVisible = (xml: string, partIndex: number): string => {
 | 
	
		
			
				|  |  |  				part.parentNode?.removeChild(part);
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		});
 | 
	
		
			
				|  |  | -		// 处理装饰音问题
 | 
	
		
			
				|  |  | -		const notes = xmlParse.getElementsByTagName("note");
 | 
	
		
			
				|  |  | -		const getNextvNoteDuration = (i: number) => {
 | 
	
		
			
				|  |  | -			let nextNote = notes[i + 1];
 | 
	
		
			
				|  |  | -			// 可能存在多个装饰音问题,取下一个非装饰音时值
 | 
	
		
			
				|  |  | -			for (let index = i; index < notes.length; index++) {
 | 
	
		
			
				|  |  | -				const note = notes[index];
 | 
	
		
			
				|  |  | -				if (!note.getElementsByTagName("grace")?.length) {
 | 
	
		
			
				|  |  | -					nextNote = note;
 | 
	
		
			
				|  |  | -					break;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			const nextNoteDuration = nextNote?.getElementsByTagName("duration")[0];
 | 
	
		
			
				|  |  | -			return nextNoteDuration;
 | 
	
		
			
				|  |  | -		};
 | 
	
		
			
				|  |  | -		Array.from(notes).forEach((note, i) => {
 | 
	
		
			
				|  |  | -			const graces = note.getElementsByTagName("grace");
 | 
	
		
			
				|  |  | -			if (graces && graces.length) {
 | 
	
		
			
				|  |  | -				// if (i !== 0) {
 | 
	
		
			
				|  |  | -				// note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
 | 
	
		
			
				|  |  | -				// }
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		});
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  	// console.log(xmlParse)
 | 
	
		
			
				|  |  |  	return new XMLSerializer().serializeToString(appoggianceFormate(xmlParse));
 | 
	
	
		
			
				|  | @@ -814,39 +506,6 @@ export const appoggianceFormate = (xmlParse: Document): Document => {
 | 
	
		
			
				|  |  |  	return xmlParse;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -export const getVoicePartInfo = () => {
 | 
	
		
			
				|  |  | -	// const { MusicalInstrumentClassification, chinesePartName } = appState;
 | 
	
		
			
				|  |  | -	const { MusicalInstrumentClassification, chinesePartName } = { MusicalInstrumentClassification: {}, chinesePartName: [] as any[] };
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	let subjectId = -1;
 | 
	
		
			
				|  |  | -	const { partListNames, partIndex } = state;
 | 
	
		
			
				|  |  | -	const filterPartNames = partListNames.filter((item) => (item || "").trim() !== "");
 | 
	
		
			
				|  |  | -	if (filterPartNames.length) {
 | 
	
		
			
				|  |  | -		for (const Classification of Object.entries(MusicalInstrumentClassification)) {
 | 
	
		
			
				|  |  | -			const [key, value] = Classification as [string, string[]];
 | 
	
		
			
				|  |  | -			const activePart: any = partListNames[partIndex];
 | 
	
		
			
				|  |  | -			// console.log({activePart, value, partListNames})
 | 
	
		
			
				|  |  | -			const filterValue = value.filter((item) => item && (activePart || "").indexOf(item || "") > -1);
 | 
	
		
			
				|  |  | -			if (activePart && (filterValue.length || value.includes(activePart))) {
 | 
	
		
			
				|  |  | -				if (!isNaN(+key)) {
 | 
	
		
			
				|  |  | -					subjectId = +key;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				return {
 | 
	
		
			
				|  |  | -					realPartListNames: partListNames,
 | 
	
		
			
				|  |  | -					subjectId: subjectId,
 | 
	
		
			
				|  |  | -					partListNames: value,
 | 
	
		
			
				|  |  | -					partName: activePart,
 | 
	
		
			
				|  |  | -					chinesePartName: chinesePartName[activePart] || activePart,
 | 
	
		
			
				|  |  | -				};
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return {
 | 
	
		
			
				|  |  | -		subjectId: subjectId,
 | 
	
		
			
				|  |  | -		partListNames: [],
 | 
	
		
			
				|  |  | -	};
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  /** 根据ID获取最顶级ID */
 | 
	
		
			
				|  |  |  export const isWithinScope = (tree: any[], id: number): number => {
 | 
	
		
			
				|  |  |  	if (!tree) return 0;
 | 
	
	
		
			
				|  | @@ -867,51 +526,6 @@ export const isWithinScope = (tree: any[], id: number): number => {
 | 
	
		
			
				|  |  |  	return result;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/**
 | 
	
		
			
				|  |  | - * 特殊教材分类id
 | 
	
		
			
				|  |  | - */
 | 
	
		
			
				|  |  | -export const classids = [1, 30, 97]; // [大雅金唐, 竖笛教程, 声部训练]
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -/**
 | 
	
		
			
				|  |  | - * 指定id是否在给定的类别中
 | 
	
		
			
				|  |  | - * @param tree
 | 
	
		
			
				|  |  | - * @param id
 | 
	
		
			
				|  |  | - * @param parentInTree default: false
 | 
	
		
			
				|  |  | - */
 | 
	
		
			
				|  |  | -export const idIsInClassIds = (tree: any[], id: number, parentInTree = false, targetIds = classids): boolean => {
 | 
	
		
			
				|  |  | -	if (!tree) return false;
 | 
	
		
			
				|  |  | -	let result = false;
 | 
	
		
			
				|  |  | -	for (const item of tree) {
 | 
	
		
			
				|  |  | -		// console.log(id, item.id, (parentInTree || classids.includes(item.id)))
 | 
	
		
			
				|  |  | -		if (item.id === id && (parentInTree || targetIds.includes(item.id))) {
 | 
	
		
			
				|  |  | -			result = true;
 | 
	
		
			
				|  |  | -			break;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		if (item.sysMusicScoreCategoriesList) {
 | 
	
		
			
				|  |  | -			result = idIsInClassIds(item.sysMusicScoreCategoriesList, id, parentInTree || targetIds.includes(item.id), targetIds);
 | 
	
		
			
				|  |  | -			if (result) break;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	// console.log('result', result)
 | 
	
		
			
				|  |  | -	return result;
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -/**
 | 
	
		
			
				|  |  | - * 获取图片地址
 | 
	
		
			
				|  |  | - * @param src 图片地址
 | 
	
		
			
				|  |  | - * @returns
 | 
	
		
			
				|  |  | - */
 | 
	
		
			
				|  |  | -const getImageSize = (src: string): Promise<HTMLImageElement> => {
 | 
	
		
			
				|  |  | -	return new Promise((resolve, reject) => {
 | 
	
		
			
				|  |  | -		const img = new Image();
 | 
	
		
			
				|  |  | -		img.src = src;
 | 
	
		
			
				|  |  | -		img.onload = () => {
 | 
	
		
			
				|  |  | -			resolve(img);
 | 
	
		
			
				|  |  | -		};
 | 
	
		
			
				|  |  | -		img.onerror = (err) => reject(err);
 | 
	
		
			
				|  |  | -	});
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  class NoteList {
 | 
	
		
			
				|  |  |  	notes: any[] = [];
 | 
	
		
			
				|  |  |  	constructor(notes: any[]) {
 | 
	
	
		
			
				|  | @@ -986,7 +600,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 | 
	
		
			
				|  |  |  	const allNotes: any[] = [];
 | 
	
		
			
				|  |  |  	const allNoteId: string[] = [];
 | 
	
		
			
				|  |  |  	const allMeasures: any[] = [];
 | 
	
		
			
				|  |  | -	const { speed: baseSpeed } = state;
 | 
	
		
			
				|  |  | +	const { originSpeed: baseSpeed } = state;
 | 
	
		
			
				|  |  |  	const formatRealKey = (realKey: number, detail: any) => {
 | 
	
		
			
				|  |  |  		// 长笛的LEVEL 2-5-1条练习是泛音练习,以每小节第一个音的指法为准,高音不变变指法。
 | 
	
		
			
				|  |  |  		const olnyOneIds = ["906"];
 | 
	
	
		
			
				|  | @@ -1264,6 +878,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 | 
	
		
			
				|  |  |  			// console.log(activeInstantaneousTempo)
 | 
	
		
			
				|  |  |  			// console.log({vDenominator, NoteRealValue, denominator, numerator, wholeValue, realValue, vRealValue, measureSpeed, speed, beatSpeed})
 | 
	
		
			
				|  |  |  			// console.log(gradualLength)
 | 
	
		
			
				|  |  | +			const _noteLength = NoteRealValue
 | 
	
		
			
				|  |  |  			let noteLength = gradualLength ? gradualLength : Math.min(vRealValue, NoteRealValue) * formatBeatUnit(beatUnit) * (60 / beatSpeed);
 | 
	
		
			
				|  |  |  			const measureLength = vRealValue * vDenominator * (60 / beatSpeed);
 | 
	
		
			
				|  |  |  			// console.table({value: iterator.currentTimeStamp.realValue, vRealValue,NoteRealValue, noteLength,measureLength, MeasureNumberXML: note.sourceMeasure.MeasureNumberXML})
 | 
	
	
		
			
				|  | @@ -1271,8 +886,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 | 
	
		
			
				|  |  |  			relaMeasureLength += noteLength;
 | 
	
		
			
				|  |  |  			let relaEndtime = noteLength + relativeTime;
 | 
	
		
			
				|  |  |  			const fixedKey = note.fixedKey || 0;
 | 
	
		
			
				|  |  | -			const svgElelent = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables[si];
 | 
	
		
			
				|  |  | -			// console.log(note.sourceMeasure.MeasureNumberXML,note,svgElelent, NoteRealValue, measureLength)
 | 
	
		
			
				|  |  | +			const svgElement = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables[si];
 | 
	
		
			
				|  |  | +			// console.log(note.sourceMeasure.MeasureNumberXML,note,svgElement, NoteRealValue, measureLength)
 | 
	
		
			
				|  |  |  			if (allNotes.length && allNotes[allNotes.length - 1].relativeTime === relativeTime) {
 | 
	
		
			
				|  |  |  				continue;
 | 
	
		
			
				|  |  |  			}
 | 
	
	
		
			
				|  | @@ -1292,9 +907,12 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  			// console.log(note.tie)
 | 
	
		
			
				|  |  |  			const nodeDetail = {
 | 
	
		
			
				|  |  | +				isRestFlag: note.isRestFlag,
 | 
	
		
			
				|  |  | +				noteId: note.NoteToGraphicalNoteObjectId,
 | 
	
		
			
				|  |  |  				measureListIndex: note.sourceMeasure.measureListIndex,
 | 
	
		
			
				|  |  |  				MeasureNumberXML: note.sourceMeasure.MeasureNumberXML,
 | 
	
		
			
				|  |  | -				svgElement: svgElelent,
 | 
	
		
			
				|  |  | +				_noteLength: _noteLength,
 | 
	
		
			
				|  |  | +				svgElement: svgElement,
 | 
	
		
			
				|  |  |  				difftime,
 | 
	
		
			
				|  |  |  				octaveOffset: activeVerticalMeasureList[0]?.octaveOffset,
 | 
	
		
			
				|  |  |  				frequency: note.pitch?.frequency,
 | 
	
	
		
			
				|  | @@ -1308,7 +926,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 | 
	
		
			
				|  |  |  				tempoInBPM: note.sourceMeasure.tempoInBPM,
 | 
	
		
			
				|  |  |  				measureLength,
 | 
	
		
			
				|  |  |  				relaMeasureLength,
 | 
	
		
			
				|  |  | -				id: svgElelent?.attrs.id,
 | 
	
		
			
				|  |  | +				id: svgElement?.attrs.id,
 | 
	
		
			
				|  |  |  				note: note.halfTone + 12, // see issue #224
 | 
	
		
			
				|  |  |  				relativeTime: retain(relativeTime),
 | 
	
		
			
				|  |  |  				time: retain(relativeTime + fixtime),
 | 
	
	
		
			
				|  | @@ -1317,7 +935,6 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 | 
	
		
			
				|  |  |  				realValue,
 | 
	
		
			
				|  |  |  				halfTone: note.halfTone,
 | 
	
		
			
				|  |  |  				noteElement: note,
 | 
	
		
			
				|  |  | -				svgElelent,
 | 
	
		
			
				|  |  |  				fixedKey,
 | 
	
		
			
				|  |  |  				realKey: 0,
 | 
	
		
			
				|  |  |  				duration: 0,
 |