liushengqiang 1 yıl önce
ebeveyn
işleme
caddce686b

+ 0 - 2
src/components/The-error/index.tsx

@@ -1,6 +1,5 @@
 import { Button, Empty, NavBar } from "vant";
 import { defineComponent } from "vue";
-import { api_back } from "/src/helpers/communication";
 
 export default defineComponent({
 	name: "The-error",
@@ -10,7 +9,6 @@ export default defineComponent({
 				<NavBar
                     leftArrow
 					onClickLeft={() => {
-						api_back();
 					}}
 				/>
 				<Empty image="error" description="网络开小差,请稍后重试">

+ 0 - 267
src/helpers/calcSpeed.ts

@@ -1,267 +0,0 @@
-import { OpenSheetMusicDisplay, SourceMeasure } from "/osmd-extended/src";
-
-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,
-};
-
-/**
- * 计算渐变速度
- */
-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)
-	const xmlParse = new DOMParser().parseFromString(xml, "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: 1,
-		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);
-		});
-		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;
-};

+ 0 - 219
src/helpers/communication.ts

@@ -1,219 +0,0 @@
-import { storeData } from "../store";
-import {
-	CallBack,
-	IPostMessage,
-	listenerMessage,
-	postMessage,
-	promisefiyPostMessage,
-	removeListenerMessage,
-} from "../utils/native-message";
-/** 获取token */
-export const api_getToken = (): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "getToken" });
-};
-/**获取耳机的插入状态 */
-export const getEarphone = (): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "isWiredHeadsetOn" });
-};
-
-/** 获取异形屏信息 */
-export const isSpecialShapedScreen = (): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "isSpecialShapedScreen" });
-};
-
-/** 开始录音 */
-export const startSoundCheck = () => {
-	postMessage({
-		api: "startSoundCheck",
-	});
-};
-
-/** 录音返回 */
-export const sendResult = (callback: CallBack) => {
-	listenerMessage("sendResult", callback);
-};
-/** 取消监听录音返回 */
-export const removeResult = (callback: CallBack) => {
-	removeListenerMessage("sendResult", callback);
-};
-
-/** 结束录音 */
-export const endSoundCheck = () => {
-	postMessage({
-		api: "endSoundCheck",
-	});
-};
-
-/** 开始评测 */
-export const startEvaluating = (content: any): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "startEvaluating", content: content });
-};
-/** 结束评测 */
-export const endEvaluating = (content: any): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "endEvaluating", content: content });
-};
-/** 取消评测 */
-export const cancelEvaluating = () => {
-	postMessage({
-		api: "cancelEvaluating",
-	});
-};
-
-/** 评测开始录音 */
-export const api_startRecording = (): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "startRecording" });
-};
-/** 评测结束录音 */
-export const api_stopRecording = (): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "stopRecording" });
-};
-
-/** 和websocket通信 */
-export const api_proxyServiceMessage = (content: any): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "proxyServiceMessage", content });
-};
-/** 监听app真正开始录音 */
-export const api_recordStartTime = (callback: CallBack) => {
-	listenerMessage("recordStartTime", callback);
-};
-/** 卸载监听app真正开始录音 */
-export const api_remove_recordStartTime = (callback: CallBack) => {
-	removeListenerMessage("recordStartTime", callback);
-};
-
-/** 上传评测视频 */
-export const api_videoUpdate = (callback: CallBack) => {
-	postMessage(
-		{
-			api: "videoUpdate",
-		},
-		callback
-	);
-};
-
-/** 分享 */
-export const api_shareAchievements = (content: any): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "shareAchievements", content });
-};
-
-/** openwebview */
-export const api_openWebView = (content: any): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "openWebView", content });
-};
-
-/** 开启摄像头 */
-export const api_openCamera = (): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "openCamera" });
-};
-
-/** 关闭摄像头 */
-export const api_closeCamera = (): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "closeCamera" });
-};
-
-/** 安卓隐藏状态栏 */
-export const api_setStatusBarVisibility = () => {
-	// 安卓的状态栏
-	postMessage({
-		api: "setStatusBarVisibility",
-		content: {
-			isVisibility: 0,
-		},
-	});
-};
-
-/** 跟练录音切换 */
-export const api_cloudToggleFollow = (state: "start" | "end") => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({
-		api: "cloudToggleFollow",
-		content: {
-			state,
-		},
-	});
-};
-
-/** 跟练录音监听 */
-export const api_cloudFollowTime = (onFollowTime: CallBack, listen = true) => {
-	if (listen) {
-		listenerMessage("cloudFollowTime", onFollowTime);
-	} else {
-		removeListenerMessage("cloudFollowTime", onFollowTime);
-	}
-};
-
-/** 结束webview */
-export const api_back = () => {
-	postMessage({
-		api: "back",
-	});
-};
-/** 切换全屏loading */
-export const api_cloudLoading = (show = false) => {
-	postMessage({
-		api: "cloudLoading",
-		content: {
-			show,
-			type: "fullscreen",
-		},
-	});
-};
-
-/** 销毁云教练 */
-export const api_cloudDestroy = () => {
-	postMessage({
-		api: "cloudDestroy",
-	});
-};
-/** 事件埋点统计: 酷乐秀用,其它没有用 */
-export const api_setEventTracking = () => {
-	postMessage({
-		api: "setEventTracking",
-		content: {
-			type: "klx_xiaokuAI",
-		},
-	});
-};
-/** 保存图片 */
-export const api_savePicture = (content: any): Promise<IPostMessage | undefined> => {
-	if (!storeData.isApp) return Promise.resolve({} as any);
-	return promisefiyPostMessage({ api: "savePicture", content });
-};
-/** 缓存评测分数 */
-export const api_setCache = (content: any) => {
-	postMessage({
-		api: "setCache",
-		content,
-	});
-};
-
-/** app切换到后台 */
-export const api_suspendPlay = (callback: CallBack) => {
-	listenerMessage("suspendPlay", callback);
-};
-
-/** 开始录制视频 */
-export const api_startCapture = () => {
-	postMessage({
-		api: "startCapture",
-	});
-};
-
-/** 结束录制视频 */
-export const api_endCapture = () => {
-	postMessage({
-		api: "endCapture",
-	});
-};

+ 0 - 12
src/helpers/eventemitter.ts

@@ -1,12 +0,0 @@
-import eventemitter from 'eventemitter3'
-
-const _event = new eventemitter()
-
-export enum EventEnum {
-    /** 评测结果返回 */
-    sendResultScore = 'sendResultScore',
-    /** 播放器播放到后面自动结束 */
-    playEnd = 'playEnd',
-}
-
-export default _event

+ 0 - 1025
src/helpers/formateMusic.ts

@@ -1,1025 +0,0 @@
-import dayjs from "dayjs";
-import duration from "dayjs/plugin/duration";
-import state, { customData } 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";
-const browserInfo = browser();
-dayjs.extend(duration);
-
-/**
- * 获取节拍器的时间
- * @param speed 速度
- * @param firstMeasure 曲谱第一个小节
- * @returns 节拍器的时间
- */
-export const getFixTime = (speed: number) => {
-	const duration: any = getDuration(state.osmd as unknown as OpenSheetMusicDisplay);
-	let numerator = duration.numerator || 0;
-	let denominator = duration.denominator || 4;
-	const beatUnit = duration.beatUnit || "quarter";
-	if (state.repeatedBeats) {
-		// 音频制作问题仅2拍不重复
-		numerator = numerator === 2 ? 4 : numerator;
-	} else if (numerator === 2 && denominator === 4) {
-		numerator = 4
-	}
-	// console.log('diff', speed, duration, formatBeatUnit(beatUnit), denominator, numerator, (numerator / denominator))
-	return state.isOpenMetronome ? (60 / speed) * formatBeatUnit(beatUnit) * (numerator / denominator) : 0;
-};
-
-export const retain = (time: number) => {
-	return Math.ceil(time * 1000000) / 1000000;
-};
-
-export const formatLyricsEntries = (note: any) => {
-	const voiceEntries = note.parentStaffEntry?.voiceEntries || [];
-	const lyricsEntries: string[] = [];
-
-	for (const voic of voiceEntries) {
-		if (voic.lyricsEntries?.table) {
-			const values: any[] = Object.values(voic.lyricsEntries.table);
-			for (const lyric of values) {
-				lyricsEntries.push(lyric?.value.text);
-			}
-		}
-	}
-	return lyricsEntries;
-};
-
-export const getMeasureDurationDiff = (measure: any) => {
-	const { realValue: SRealValue } = measure.activeTimeSignature;
-	const { realValue: RRealValue } = measure.duration;
-	return SRealValue - RRealValue;
-};
-
-/** 按照dorico的渐快渐慢生成对应的速度 */
-export const createSpeedInfo = (gradualChange: GradualChange | undefined, speed: number) => {
-	if (gradualChange && speedInfo[gradualChange.startWord?.toLocaleLowerCase()]) {
-		const notenum = Math.max(gradualChange.endXmlNoteIndex, 3);
-		const speeds: number[] = [];
-		const startSpeed = speed;
-		const endSpeed = speed / speedInfo[gradualChange.startWord?.toLocaleLowerCase()];
-
-		for (let index = 0; index < notenum; index++) {
-			const speed = startSpeed + ((endSpeed - startSpeed) / notenum) * (index + 1);
-			speeds.push(speed);
-		}
-		return speeds;
-	}
-	return;
-};
-
-const tranTime = (str: string = "") => {
-	let result = str;
-	const splits = str.split(":");
-	if (splits.length === 1) {
-		result = `00:${splits[0]}:00`;
-	} else if (splits.length === 2) {
-		result = `00:${splits[0]}:${splits[1]}`;
-	}
-	// console.log(`1970-01-01 00:${result}0`)
-	return `1970-01-01 00:${result}0`;
-};
-
-export const getSlursNote = (note: any, pos?: "start" | "end") => {
-	return pos === "end" ? note.noteElement.slurs[0]?.endNote : note.noteElement.slurs[0]?.startNote;
-};
-
-export const getNoteBySlursStart = (note: any, anyNoteHasSlurs?: boolean, pos?: "start" | "end") => {
-	let activeNote = note;
-	let slursNote = getSlursNote(activeNote, pos);
-
-	for (const item of activeNote.measures) {
-		if (item.noteElement.slurs.length) {
-			slursNote = getSlursNote(item, pos);
-			activeNote = item;
-		}
-	}
-	return activeNote;
-};
-
-/** 根据 noteElement 获取note */
-export const getParentNote = (note: any) => {
-	let parentNote;
-	if (note) {
-		// time = activeNote.time
-		for (const n of state.times) {
-			if (note === n.noteElement) {
-				// console.log(note)
-				return n;
-			}
-		}
-	}
-	return parentNote;
-};
-
-export type FractionDefault = {
-	numerator: number;
-	denominator: number;
-	wholeValue: number;
-};
-
-export type Duration = FractionDefault & {
-	TempoInBPM: number;
-	beatUnit: string;
-};
-
-export const getDuration = (osmd?: OpenSheetMusicDisplay): Duration => {
-	if (osmd) {
-		const { Duration, TempoInBPM, ActiveTimeSignature, TempoExpressions } = osmd.GraphicSheet.MeasureList[0][0]?.parentSourceMeasure;
-		if (Duration) {
-			let beatUnit = "quarter";
-			for (const item of TempoExpressions) {
-				beatUnit = item.InstantaneousTempo.beatUnit || "quarter";
-			}
-			const duration = formatDuration(ActiveTimeSignature, Duration) as unknown as FractionDefault;
-			return {
-				...duration,
-				TempoInBPM,
-				beatUnit,
-			};
-		}
-	}
-	const duration = new Fraction() as unknown as FractionDefault;
-	return {
-		...duration,
-		TempoInBPM: 90,
-		beatUnit: "quarter",
-	};
-};
-
-export function formatDuration(activeTimeSignature: Fraction, duration: Fraction): Fraction {
-	// 弱起第一小节duration不对
-	return activeTimeSignature;
-}
-
-export function formatBeatUnit(beatUnit: string) {
-	let multiple = 4;
-	switch (beatUnit) {
-		case "1024th":
-			// bpm = bpm;
-			multiple = 1024;
-			break;
-		case "512th":
-			// divisionsFromNote = (noteDuration / 4) * 512;
-			multiple = 512;
-			break;
-		case "256th":
-			// divisionsFromNote = (noteDuration / 4) * 256;
-			multiple = 256;
-			break;
-		case "128th":
-			// divisionsFromNote = (noteDuration / 4) * 128;
-			multiple = 128;
-			break;
-		case "64th":
-			// divisionsFromNote = (noteDuration / 4) * 64;
-			multiple = 64;
-			break;
-		case "32nd":
-			// divisionsFromNote = (noteDuration / 4) * 32;
-			multiple = 32;
-			break;
-		case "16th":
-			// divisionsFromNote = (noteDuration / 4) * 16;
-			multiple = 16;
-			break;
-		case "eighth":
-			// divisionsFromNote = (noteDuration / 4) * 8;
-			multiple = 8;
-			break;
-		case "quarter":
-			multiple = 4;
-			break;
-		case "half":
-			// divisionsFromNote = (noteDuration / 4) * 2;
-			multiple = 2;
-			break;
-		case "whole":
-			// divisionsFromNote = (noteDuration / 4);
-			multiple = 1;
-			break;
-		case "breve":
-			// divisionsFromNote = (noteDuration / 4) / 2;
-			multiple = 0.5;
-			break;
-		case "long":
-			// divisionsFromNote = (noteDuration / 4) / 4;
-			multiple = 0.25;
-			break;
-		case "maxima":
-			// divisionsFromNote = (noteDuration / 4) / 8;
-			multiple = 0.125;
-			break;
-		default:
-			break;
-	}
-
-	return multiple;
-}
-
-/** 根据音符单位,速度,几几拍计算正确的时间 */
-export function getTimeByBeatUnit(beatUnit: string, bpm: number, denominator: number) {
-	return (denominator / formatBeatUnit(beatUnit)) * bpm;
-}
-
-export type CustomInfo = {
-	showSpeed: boolean;
-	parsedXML: string;
-};
-
-/** 从xml中获取自定义信息,并删除多余的字符串 */
-export const getCustomInfo = (xml: string): CustomInfo => {
-	const data = {
-		showSpeed: true,
-		parsedXML: xml,
-	};
-	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
-	const words: any = xmlParse.getElementsByTagName("words");
-	for (const word of words) {
-		if (word && word.textContent?.trim() === "隐藏速度") {
-			data.showSpeed = false;
-			word.textContent = "";
-		}
-		if (word && word.textContent?.trim() === "@") {
-			word.textContent = "segno";
-		}
-	}
-	data.parsedXML = new XMLSerializer().serializeToString(xmlParse);
-	return data;
-};
-
-/**
- * 替换文本标签中的内容
- */
-const replaceTextConent = (beforeText: string, afterText: string, ele: Element): Element => {
-	const words: any = ele?.getElementsByTagName("words");
-	for (const word of words) {
-		if (word && word.textContent?.trim() === beforeText) {
-			word.textContent = afterText;
-		}
-	}
-	return ele;
-};
-
-/**
- * 添加第一分谱信息至当前分谱
- * @param ele 需要插入的元素
- * @param fitstParent 合奏谱第一个分谱
- * @param parent 需要添加的分谱
- */
-const setElementNoteBefore = (ele: Element, fitstParent: Element, parent?: Element | null) => {
-	let noteIndex: number = 0;
-	if (!fitstParent) {
-		return;
-	}
-	for (let index = 0; index < fitstParent.childNodes.length; index++) {
-		const element = fitstParent.childNodes[index];
-		if (element.nodeName === "note") {
-			noteIndex++;
-		}
-		if (element === ele) {
-			break;
-		}
-	}
-	if (noteIndex === 0 && parent) {
-		parent.insertBefore(ele, parent.childNodes[0]);
-		return;
-	}
-	if (parent && parent.childNodes.length > 0) {
-		let noteIndex2: number = 0;
-		const notes = Array.from(parent.childNodes).filter((child) => child.nodeName === "note");
-		const lastNote = notes[notes.length - 1];
-		if (noteIndex >= notes.length && lastNote) {
-			parent.insertBefore(ele, parent.childNodes[Array.from(parent.childNodes).indexOf(lastNote)]);
-			return;
-		}
-		for (let index = 0; index < notes.length; index++) {
-			const element = notes[index];
-			if (element.nodeName === "note") {
-				noteIndex2 = noteIndex2 + 1;
-				if (noteIndex2 === noteIndex) {
-					parent.insertBefore(ele, element);
-					break;
-				}
-			}
-		}
-	}
-	// console.log(noteIndex, parent)
-};
-
-/**
- * 检查传入文字是否为重复关键词
- * @param text 总谱xml
- * @returns 是否是重复关键词
- */
-export const isRepeatWord = (text: string): boolean => {
-	if (text) {
-		const innerText = text.toLocaleLowerCase();
-		const dsRegEx: string = "d\\s?\\.s\\.";
-		const dcRegEx: string = "d\\.\\s?c\\.";
-
-		return (
-			innerText === "@" ||
-			StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + " al fine", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + " al coda", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al fine", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al coda", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, dcRegEx) ||
-			StringUtil.StringContainsSeparatedWord(innerText, "da\\s?capo", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, dsRegEx, true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, "dal\\s?segno", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, "al\\s?coda", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, "to\\s?coda", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, "a (la )?coda", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, "fine", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, "coda", true) ||
-			StringUtil.StringContainsSeparatedWord(innerText, "segno", true)
-		);
-	}
-	return false;
-};
-
-export const onlyVisible = (xml: string, partIndex: number): string => {
-	if (!xml) return "";
-	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 || "");
-	const parts: any = xmlParse.getElementsByTagName("part");
-	// const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)
-	const firstMeasures = [...parts[0]?.getElementsByTagName("measure")];
-	const metronomes = [...parts[0]?.getElementsByTagName("metronome")];
-	const words = [...parts[0]?.getElementsByTagName("words")];
-	const codas = [...parts[0]?.getElementsByTagName("coda")];
-	const rehearsals = [...parts[0]?.getElementsByTagName("rehearsal")];
-
-	/** 第一分谱如果是约定的配置分谱则跳过 */
-	if (partListNames[0]?.toLocaleUpperCase?.() === "COMMON") {
-		partIndex++;
-		partListNames.shift();
-	}
-	const visiblePartInfo = partList[partIndex];
-	// console.log(visiblePartInfo, partIndex)
-	state.partListNames = partListNames;
-	if (visiblePartInfo) {
-		const id = visiblePartInfo.getAttribute("id");
-		Array.from(parts).forEach((part: any) => {
-			if (part && part.getAttribute("id") !== id) {
-				part.parentNode?.removeChild(part);
-				// 不等于第一行才添加避免重复添加
-			} else if (part && part.getAttribute("id") !== "P1") {
-				// 速度标记仅保留最后一个
-				const metronomeData: {
-					[key in string]: Element;
-				} = {};
-				for (let i = 0; i < metronomes.length; i++) {
-					const metronome = metronomes[i];
-					const metronomeContainer = metronome.parentElement?.parentElement?.parentElement;
-					if (metronomeContainer) {
-						const index = firstMeasures.indexOf(metronomeContainer);
-						metronomeData[index] = metronome;
-					}
-				}
-				Object.values(metronomeData).forEach((metronome) => {
-					const metronomeContainer: any = metronome.parentElement?.parentElement;
-					const parentMeasure: any = metronomeContainer?.parentElement;
-					const measureMetronomes = [...(parentMeasure?.childNodes || [])];
-					const metronomesIndex = metronomeContainer ? measureMetronomes.indexOf(metronomeContainer) : -1;
-					// console.log(parentMeasure)
-					if (parentMeasure && metronomesIndex > -1) {
-						const index = firstMeasures.indexOf(parentMeasure);
-						const activeMeasure = part.getElementsByTagName("measure")[index];
-						setElementNoteBefore(metronomeContainer, parentMeasure, activeMeasure);
-					}
-				});
-				/** word比较特殊需要精确到note位置 */
-				words.forEach((word) => {
-					let text = word.textContent || "";
-					text = ["cresc."].includes(text) ? "" : text;
-					if ((isSpecialMark(text) || isSpeedKeyword(text) || isGradientWords(text) || isRepeatWord(text) || GRADIENT_SPEED_RESET_TAG) && text) {
-						const wordContainer = word.parentElement?.parentElement;
-						const parentMeasure = wordContainer?.parentElement;
-						const measureWords = [...(parentMeasure?.childNodes || [])];
-						const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1;
-						if (wordContainer && parentMeasure && wordIndex > -1) {
-							const index = firstMeasures.indexOf(parentMeasure);
-							const activeMeasure = part.getElementsByTagName("measure")[index];
-							// 找当前小节是否包含word标签
-							const _words = Array.from(activeMeasure?.getElementsByTagName("words") || []);
-							// 遍历word标签,检查是否和第一小节重复,如果有重复则不平移word
-							const total = _words.reduce((total: any, _word: any) => {
-								if (_word.textContent?.includes(text)) {
-									total++;
-								}
-								return total;
-							}, 0);
-							if (total === 0) {
-								if (["12280"].includes(detailId)) {
-									activeMeasure?.insertBefore(wordContainer.cloneNode(true), activeMeasure?.childNodes[wordIndex]);
-								} else {
-									setElementNoteBefore(wordContainer, parentMeasure, activeMeasure);
-								}
-							}
-						}
-					}
-				});
-				/** word比较特殊需要精确到note位置 */
-				codas.forEach((coda) => {
-					const wordContainer = coda.parentElement?.parentElement;
-					const parentMeasure = wordContainer?.parentElement;
-					const measureWords = [...(parentMeasure?.childNodes || [])];
-					const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1;
-					if (wordContainer && parentMeasure && wordIndex > -1) {
-						const index = firstMeasures.indexOf(parentMeasure);
-						const activeMeasure = part.getElementsByTagName("measure")[index];
-						if (["12280"].includes(detailId)) {
-							activeMeasure?.insertBefore(wordContainer.cloneNode(true), activeMeasure?.childNodes[wordIndex]);
-						} else {
-							setElementNoteBefore(wordContainer, parentMeasure, activeMeasure);
-						}
-					}
-				});
-				rehearsals.forEach((rehearsal) => {
-					const container = rehearsal.parentElement?.parentElement;
-					const parentMeasure = container?.parentElement;
-					// console.log(rehearsal)
-					if (parentMeasure) {
-						const index = firstMeasures.indexOf(parentMeasure);
-						part.getElementsByTagName("measure")[index]?.appendChild(container.cloneNode(true));
-						// console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
-					}
-				});
-			} else {
-				words.forEach((word) => {
-					const text = word.textContent || "";
-					if (isSpeedKeyword(text) && text) {
-						const wordContainer = word.parentElement?.parentElement?.parentElement;
-						if (wordContainer && wordContainer.firstElementChild && wordContainer.firstElementChild !== word) {
-							const wordParent = word.parentElement?.parentElement;
-							const fisrt = wordContainer.firstElementChild;
-							wordContainer.insertBefore(wordParent, fisrt);
-						}
-					}
-				});
-			}
-
-			// 最后一个小节的结束线元素不在最后 调整
-			if (part && part.getAttribute("id") === id) {
-				const barlines = part.getElementsByTagName("barline");
-				const lastParent = barlines[barlines.length - 1]?.parentElement;
-				if (lastParent?.lastElementChild?.tagName !== "barline") {
-					const children = lastParent?.children || [];
-					for (let el of children) {
-						if (el.tagName === "barline") {
-							// 将结束线元素放到最后
-							lastParent?.appendChild(el);
-							break;
-						}
-					}
-				}
-			}
-		});
-		Array.from(partList).forEach((part) => {
-			if (part && part.getAttribute("id") !== id) {
-				part.parentNode?.removeChild(part);
-			}
-		});
-	}
-	// console.log(xmlParse)
-	return new XMLSerializer().serializeToString(appoggianceFormate(xmlParse));
-};
-
-// 倚音后连音线
-export const appoggianceFormate = (xmlParse: Document): Document => {
-	if (!xmlParse) return xmlParse;
-	const graces: any = xmlParse.querySelectorAll("grace");
-	if (!graces.length) return xmlParse;
-	const getNextElement = (el: HTMLElement): HTMLElement => {
-		if (el.querySelector("grace")) {
-			return getNextElement(el?.nextElementSibling as HTMLElement);
-		}
-		return el;
-	};
-	for (let grace of graces) {
-		const notations = grace.parentElement?.querySelector("notations");
-		if (notations && notations.querySelectorAll("slur").length > 1) {
-			let nextEle: Element = getNextElement(grace.parentElement?.nextElementSibling as HTMLElement);
-			if (nextEle && nextEle.querySelectorAll("slur").length > 0) {
-				const slurNumber = Array.from(nextEle.querySelector("notations")?.children || []).map((el: Element) => {
-					return el.getAttribute("number");
-				});
-				const slurs = notations.querySelectorAll("slur");
-				for (let nota of slurs) {
-					if (!slurNumber.includes(nota.getAttribute("number"))) {
-						nextEle.querySelector("notations")?.appendChild(nota);
-					}
-				}
-			}
-		}
-	}
-	return xmlParse;
-};
-
-/** 根据ID获取最顶级ID */
-export const isWithinScope = (tree: any[], id: number): number => {
-	if (!tree) return 0;
-	let result = 0;
-	for (const item of tree) {
-		if (item.id === id) {
-			result = item.id;
-			break;
-		}
-		if (item.sysMusicScoreCategoriesList) {
-			result = isWithinScope(item.sysMusicScoreCategoriesList, id);
-			if (result > 0) {
-				result = item.id;
-			}
-			if (result) break;
-		}
-	}
-	return result;
-};
-
-class NoteList {
-	notes: any[] = [];
-	constructor(notes: any[]) {
-		this.notes = notes;
-	}
-
-	public last() {
-		return this.notes[this.notes.length - 1];
-	}
-
-	public list() {
-		return this.notes;
-	}
-}
-
-export const getNotesByid = (id: string): NoteList => {
-	const notes = new NoteList(state.times.filter((item) => item.id === id));
-	return notes;
-};
-
-/** 格式化当前曲谱缩放比例 */
-export const formatZoom = (num = 1) => {
-	return num * state.zoom;
-};
-
-/** 格式化曲谱
- * 1.全休止符的小节,没有音符默认加个全休止符
- */
-export const formatXML = (xml: string): string => {
-	if (!xml) return "";
-	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
-	const measures = Array.from(xmlParse.getElementsByTagName("measure"));
-	// let speed = -1
-	let beats = -1;
-	let beatType = -1;
-	// 小节中如果没有节点默认为休止符
-	for (const measure of measures) {
-		if (beats === -1 && measure.getElementsByTagName("beats").length) {
-			beats = parseInt(measure.getElementsByTagName("beats")[0].textContent || "4");
-		}
-		if (beatType === -1 && measure.getElementsByTagName("beat-type").length) {
-			beatType = parseInt(measure.getElementsByTagName("beat-type")[0].textContent || "4");
-		}
-		// if (speed === -1 && measure.getElementsByTagName('per-minute').length) {
-		//   speed = parseInt(measure.getElementsByTagName('per-minute')[0].textContent || this.firstLib?.speed)
-		// }
-		const divisions = parseInt(measure.getElementsByTagName("divisions")[0]?.textContent || "256");
-		if (measure.getElementsByTagName("note").length === 0) {
-			const forwardTimeElement = measure.getElementsByTagName("forward")[0]?.getElementsByTagName("duration")[0];
-			if (forwardTimeElement) {
-				forwardTimeElement.textContent = "0";
-			}
-			measure.innerHTML =
-				measure.innerHTML +
-				`
-        <note>
-          <rest measure="yes"/>
-          <duration>${divisions * beats}</duration>
-          <voice>1</voice>
-          <type>whole</type>
-        </note>`;
-		}
-	}
-	return new XMLSerializer().serializeToString(xmlParse);
-};
-
-/** 获取所有音符的时值,以及格式化音符 */
-export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
-	const customNoteRealValue = customData.customNoteRealValue;
-	const customNoteCurrentTime = customData.customNoteCurrentTime;
-	const detailId = state.examSongId + "";
-	const partIndex = state.partIndex + "";
-	let fixtime = browserInfo.huawei ? 0.08 : 0; //getFixTime()
-	const allNotes: any[] = [];
-	const allNoteId: string[] = [];
-	const allMeasures: any[] = [];
-	const { originSpeed: baseSpeed } = state;
-	const formatRealKey = (realKey: number, detail: any) => {
-		// 不是管乐迷, 不处理
-		if (state.appName !== "GYM") return realKey;
-		// 长笛的LEVEL 2-5-1条练习是泛音练习,以每小节第一个音的指法为准,高音不变变指法。
-		const olnyOneIds = ["906"];
-		if (olnyOneIds.includes(detailId)) {
-			return detail.measures[0]?.realKey || realKey;
-		}
-		// 圆号的LEVEL 2-5条练习是泛音练习,最后四小节指法以连音线第一个小节为准
-		const olnyOneIds2 = ["782", "784"];
-		if (olnyOneIds2.includes(detailId)) {
-			const measureNumbers = [14, 16, 30, 32];
-			if (measureNumbers.includes(detail.firstVerticalMeasure?.measureNumber)) {
-				return allNotes[allNotes.length - 1]?.realKey || realKey;
-			}
-		}
-		// 2-6 第三小节指法按照第一个音符显示
-		const filterIds = ["900", "901", "640", "641", "739", "740", "800", "801", "773", "774", "869", "872", "714", "715"];
-		if (filterIds.includes(detailId)) {
-			if (detail.firstVerticalMeasure?.measureNumber === 3 || detail.firstVerticalMeasure?.measureNumber === 9) {
-				return detail.measures[0]?.realKey || realKey;
-			}
-		}
-		return realKey;
-	};
-	if (!osmd.cursor) return [];
-	const iterator: any = osmd.cursor.Iterator;
-	// console.log("🚀 ~ iterator:", iterator)
-	console.time("音符跑完时间");
-
-	let i = 0;
-	let si = 0;
-	let measures: any[] = [];
-	let stepSpeeds: number[] = [];
-	/** 弱起时间 */
-	let difftime = 0;
-	let usetime = 0;
-	let relaMeasureLength = 0;
-	let beatUnit = "quarter";
-	let gradualSpeed;
-	let gradualChange: GradualChange | undefined;
-	let gradualChangeIndex = 0;
-	let totalMultipleRestMeasures = 0;
-	let multipleRestMeasures = 0;
-	const _notes = [] as any[];
-	if (state.gradualTimes) {
-		console.log("后台设置的渐慢小节时间", state.gradual, state.gradualTimes);
-	}
-
-	let currentTimeStamp = iterator.currentTimeStamp.RealValue;
-	const currentTimes = [] as any[];
-	let isSetNextNoteReal = false;
-	let differFrom = 0;
-	while (!iterator.EndReached) {
-		// console.log({ ...iterator });
-		const voiceEntries = iterator.CurrentVoiceEntries?.[0] ? [iterator.CurrentVoiceEntries?.[0]] : [];
-		let currentVoiceEntries: any[] = [];
-		// 单声部多声轨
-		if (state.multitrack > 0) {
-			currentVoiceEntries = [...iterator.CurrentVoiceEntries];
-		} else {
-			currentVoiceEntries = [...iterator.CurrentVoiceEntries].filter((n) => {
-				return n && n?.ParentVoice?.VoiceId != 1;
-			});
-		}
-		let currentTime = 0;
-		let isDouble = false;
-		let isMutileSubject = false;
-
-		if (currentVoiceEntries.length && !isSetNextNoteReal) {
-			isDouble = true;
-			let voiceNotes = [...iterator.CurrentVoiceEntries].reduce((notes, n) => {
-				notes.push(...n.Notes);
-				return notes;
-			}, [] as any);
-			voiceNotes = voiceNotes.sort((a: any, b: any) => a?.length?.realValue - b?.length?.realValue);
-			currentTime = voiceNotes?.[0]?.length?.realValue || 0;
-
-			if (state.multitrack > 0 && currentVoiceEntries.length === 2) {
-				const min = voiceNotes[0]?.length?.realValue || 0;
-				const max = voiceNotes[voiceNotes.length - 1]?.length?.realValue || 0;
-				differFrom = max - min;
-				isSetNextNoteReal = differFrom === 0 ? false : true;
-			}
-		}
-		// 多声部上下音符没对齐,光标多走一拍
-		if (_notes[_notes.length - 1]?.isDouble && !currentVoiceEntries.length) {
-			isMutileSubject = true;
-		}
-		if (state.multitrack > 0 && !isDouble && isSetNextNoteReal) {
-			isDouble = true;
-			currentTime = differFrom;
-			isSetNextNoteReal = false;
-			differFrom = 0;
-		}
-		currentTimes.push(iterator.currentTimeStamp.realValue - currentTimeStamp);
-		currentTimeStamp = iterator.currentTimeStamp.realValue;
-		for (const v of voiceEntries) {
-			let note = v.notes[0];
-			if (note.IsGraceNote) {
-				// 如果是装饰音, 取不是装饰音的时值
-				const voice = note.parentStaffEntry.voiceEntries.find((_v: any) => !_v.isGrace);
-				note = voice.notes[0];
-			}
-			note.fixedKey = note.ParentVoiceEntry.ParentVoice.Parent.SubInstruments[0].fixedKey || 0;
-			// 有倚音
-			if (note?.voiceEntry?.isGrace) {
-				isDouble = true;
-				let ns = [...iterator.currentVoiceEntries].reduce((notes, n) => {
-					notes.push(...n.notes);
-					return notes;
-				}, []);
-				ns = ns.sort((a: any, b: any) => b?.length?.realValue - a?.length?.realValue);
-				currentTime = currentTime != 0 ? Math.min(ns?.[0]?.length?.realValue, currentTime) : ns?.[0]?.length?.realValue;
-			}
-			if (state.multitrack > 0 && currentTime > note.length.realValue) {
-				currentTime = note.length.realValue;
-			}
-			_notes.push({
-				note,
-				iterator: { ...iterator },
-				currentTime,
-				isDouble,
-				isMutileSubject,
-			});
-		}
-
-		iterator.moveToNextVisibleVoiceEntry(false);
-	}
-	for (let { note, iterator, currentTime, isDouble, isMutileSubject } of _notes) {
-		if (note) {
-			if (si === 0) {
-				allMeasures.push(note.sourceMeasure);
-			}
-			if (si === 0 && state.isSpecialBookCategory) {
-				for (const expression of (note.sourceMeasure as SourceMeasure)?.TempoExpressions) {
-					if (expression?.InstantaneousTempo?.beatUnit) {
-						// 取最后一个有效的tempo
-						beatUnit = expression.InstantaneousTempo.beatUnit;
-					}
-				}
-			}
-			let measureSpeed = note.sourceMeasure.tempoInBPM;
-			const { metronomeNoteIndex } = iterator.currentMeasure;
-			if (metronomeNoteIndex !== 0 && metronomeNoteIndex > si) {
-				measureSpeed = allNotes[allNotes.length - 1]?.speed || 100;
-			}
-
-			const activeVerticalMeasureList = [note.sourceMeasure.verticalMeasureList?.[0]] || [];
-			const { realValue } = iterator.currentTimeStamp;
-			const { RealValue: vRealValue, Denominator: vDenominator } = formatDuration(
-				iterator.currentMeasure.activeTimeSignature,
-				iterator.currentMeasure.duration
-			);
-			let { wholeValue, numerator, denominator, realValue: NoteRealValue } = note.length;
-			if (customNoteRealValue[i]) {
-				// console.log(NoteRealValue, customNoteRealValue[i])
-				NoteRealValue = customNoteRealValue[i];
-			}
-			if (isDouble && currentTime > 0) {
-				if (currentTime != NoteRealValue) {
-					console.log(`小节 ${note.sourceMeasure.MeasureNumberXML} 替换: noteLength: ${NoteRealValue}, 最小: ${currentTime}`);
-					NoteRealValue = currentTime;
-				}
-			}
-			// note.sourceMeasure.MeasureNumberXML === 8 && console.error(`小节 ${note.sourceMeasure.MeasureNumberXML}`, NoteRealValue)
-			// 管乐迷,按自定义按读取到的音符时值
-			if (customNoteCurrentTime) {
-				if (isMutileSubject && currentTimes[i + 1] > 0 && NoteRealValue > currentTimes[i + 1]) {
-					console.log(NoteRealValue, currentTimes[i + 1])
-					NoteRealValue = currentTimes[i + 1];
-				}
-			}
-
-			let relativeTime = usetime;
-			// 速度不能为0 此处的速度应该是按照设置的速度而不是校准后的速度,否则mp3速度不对
-			let beatSpeed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1;
-			// 如果有节拍器,需要将节拍器的时间算出来
-			if (i === 0) {
-				fixtime += getFixTime(beatSpeed);
-				state.fixtime = fixtime;
-				console.log("fixtime:", fixtime, '速度:', beatSpeed, "state.isSpecialBookCategory:", state.isSpecialBookCategory, 'state.isOpenMetronome:', state.isOpenMetronome);
-			}
-			// console.log(getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator))
-			let gradualLength = 0;
-			let speed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1;
-			gradualChange = iterator.currentMeasure.speedInfo || gradualChange;
-			gradualSpeed = osmd.Sheet.SoundTempos?.get(note.sourceMeasure.measureListIndex) || gradualSpeed;
-			if (!gradualSpeed || gradualSpeed.length < 2) {
-				gradualSpeed = createSpeedInfo(gradualChange, speed);
-			}
-			// console.log({...iterator.currentMeasure},gradualChange, gradualSpeed)
-			const measureListIndex = iterator.currentMeasure.measureListIndex;
-			// 计算相差时间按照比例分配到所有音符上
-			if (state.gradualTimes && Object.keys(state.gradualTimes).length > 0) {
-				const withInRangeNote = state.gradual.find((item, index) => {
-					const nextItem: any = state.gradual[index + 1];
-					return (
-						item[0].measureIndex <= measureListIndex &&
-						item[1]?.measureIndex! >= measureListIndex &&
-						(!nextItem || nextItem?.[0].measureIndex !== measureListIndex)
-					);
-				});
-				const [first, last] = withInRangeNote || [];
-				if (first && last) {
-					// 小节数量
-					const continuous = last.measureIndex - first.measureIndex;
-					// 开始小节内
-					const inTheFirstMeasure = first.closedMeasureIndex == measureListIndex && si >= first.noteInMeasureIndex;
-					// 结束小节内
-					const inTheLastMeasure = last.closedMeasureIndex === measureListIndex && si < last.noteInMeasureIndex;
-					// 范围内小节
-					const inFiestOrLastMeasure = first.closedMeasureIndex !== measureListIndex && last.closedMeasureIndex !== measureListIndex;
-					if (inTheFirstMeasure || inTheLastMeasure || inFiestOrLastMeasure) {
-						const startTime = state.gradualTimes[first.measureIndex];
-						const endTime = state.gradualTimes[last.measureIndex];
-						if (startTime && endTime) {
-							const times = continuous - first.leftDuration / first.allDuration + last.leftDuration / last.allDuration;
-							const diff = dayjs(tranTime(endTime)).diff(dayjs(tranTime(startTime)), "millisecond");
-							gradualLength = ((NoteRealValue / vRealValue / times) * diff) / 1000;
-						}
-					}
-				}
-			} else if (state.appName === "GYM" && gradualChange && gradualSpeed && (gradualChange.startXmlNoteIndex === si || gradualChangeIndex > 0)) {
-				const startSpeed = gradualSpeed[0] - (gradualSpeed[1] - gradualSpeed[0]);
-				const { resetXmlNoteIndex, endXmlNoteIndex } = gradualChange;
-				const noteDiff = endXmlNoteIndex;
-				let stepSpeed = (gradualSpeed[gradualSpeed.length - 1] - startSpeed) / noteDiff;
-				stepSpeed = note.DotsXml ? stepSpeed / 1.5 : stepSpeed;
-				if (gradualChangeIndex < noteDiff) {
-					const tempSpeed = Math.ceil(speed + stepSpeed * gradualChangeIndex);
-					let tmpSpeed = getTimeByBeatUnit(beatUnit, tempSpeed, iterator.currentMeasure.activeTimeSignature.Denominator);
-					const maxLength = (wholeValue + numerator / denominator) * vDenominator * (60 / tmpSpeed);
-					// speed += stepSpeeds.reduce((a, b) => a + b, 0)
-					speed += Math.ceil(stepSpeed * (gradualChangeIndex + 1));
-					tmpSpeed = getTimeByBeatUnit(beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator);
-					const minLength = (wholeValue + numerator / denominator) * vDenominator * (60 / tmpSpeed);
-					gradualLength = (maxLength + minLength) / 2;
-				} else if (resetXmlNoteIndex > gradualChangeIndex) {
-					speed = allNotes[i - 1]?.speed;
-				}
-				beatSpeed =
-					(state.isSpecialBookCategory ? getTimeByBeatUnit(beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator) : baseSpeed) || 1;
-				const isEnd = !(gradualChangeIndex < noteDiff) && !(resetXmlNoteIndex > gradualChangeIndex);
-				gradualChangeIndex++;
-				if (isEnd) {
-					gradualChangeIndex = 0;
-					gradualChange = undefined;
-					gradualSpeed = undefined;
-					stepSpeeds = [];
-				}
-			}
-			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})
-			// console.log(i, Math.min(vRealValue, NoteRealValue),noteLength,gradualLength, formatBeatUnit(beatUnit),beatSpeed, NoteRealValue * formatBeatUnit(beatUnit) * (60 / beatSpeed) )
-			usetime += noteLength;
-			relaMeasureLength += noteLength;
-			let relaEndtime = noteLength + relativeTime;
-			const fixedKey = note.fixedKey || 0;
-			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;
-			}
-			// console.log(iterator.currentMeasure)
-			// 如果是弱起就补齐缺省的时长
-			if (i === 0) {
-				let _firstMeasureRealValue = 0;
-				const staffEntries = note.sourceMeasure.verticalMeasureList?.[0]?.staffEntries || [];
-				//计算第一个小节里面的音符时值是否等于整个小节的时值
-				staffEntries.forEach((_a: any) => {
-					if (_a?.sourceStaffEntry?.voiceEntries?.[0]?.notes?.[0]?.length?.realValue) {
-						_firstMeasureRealValue += _a.sourceStaffEntry.voiceEntries[0].notes[0].length.realValue;
-					}
-				});
-				if (_firstMeasureRealValue < vRealValue) {
-					// console.log(_firstMeasureRealValue, vRealValue)
-					// 如果是弱起,将整个小节的时值减去音符的时值,就是缺省的时值
-					difftime = measureLength - noteLength;
-				}
-				if (difftime > 0) {
-					fixtime += difftime;
-				}
-				// 管乐迷 diff获取不准确时, 弱起补齐
-				if (["2589", "2561", "2560", "2559", "2558", "2556", "2555", "2554"].includes(detailId)) {
-					// difftime = iterator.currentTimeStamp.realValue * formatBeatUnit(beatUnit) * (60 / beatSpeed);
-					// fixtime += difftime;
-				}
-			}
-			let stave = activeVerticalMeasureList[0]?.stave;
-
-			if (note.sourceMeasure.multipleRestMeasures) {
-				totalMultipleRestMeasures = note.sourceMeasure.multipleRestMeasures;
-				multipleRestMeasures = 0;
-			}
-			if (multipleRestMeasures < totalMultipleRestMeasures) {
-				multipleRestMeasures++;
-			} else {
-				multipleRestMeasures = 0;
-				totalMultipleRestMeasures = 0;
-			}
-
-			// console.log(note.tie)
-			const nodeDetail = {
-				isStaccato: note.voiceEntry.isStaccato(),
-				isRestFlag: note.isRestFlag,
-				noteId: note.NoteToGraphicalNoteObjectId,
-				measureListIndex: note.sourceMeasure.measureListIndex,
-				MeasureNumberXML: note.sourceMeasure.MeasureNumberXML,
-				_noteLength: _noteLength,
-				svgElement: svgElement,
-				frequency: note?.pitch?.frequency || -1,
-				nextFrequency: note?.pitch?.nextFrequency || -1,
-				prevFrequency: note?.pitch?.prevFrequency || -1,
-				difftime,
-				octaveOffset: activeVerticalMeasureList[0]?.octaveOffset,
-				speed,
-				beatSpeed,
-				i,
-				si,
-				stepSpeeds,
-				measureOpenIndex: allMeasures.length - 1,
-				measures,
-				tempoInBPM: note.sourceMeasure.tempoInBPM,
-				measureLength,
-				relaMeasureLength,
-				id: svgElement?.attrs.id,
-				note: note.halfTone + 12, // see issue #224
-				relativeTime: retain(relativeTime),
-				time: retain(relativeTime + fixtime),
-				endtime: retain(relaEndtime + fixtime),
-				relaEndtime: retain(relaEndtime),
-				realValue,
-				halfTone: note.halfTone,
-				noteElement: note,
-				fixedKey,
-				realKey: 0,
-				duration: 0,
-				formatLyricsEntries: formatLyricsEntries(note),
-				stave,
-				firstVerticalMeasure: activeVerticalMeasureList[0],
-				noteLength: 1,
-				osdmContext: osmd,
-				speedbeatUnit: beatUnit,
-				multipleRestMeasures: multipleRestMeasures,
-			};
-			nodeDetail.realKey = formatRealKey(note.halfTone - fixedKey * 12, nodeDetail);
-			nodeDetail.duration = nodeDetail.endtime - nodeDetail.time;
-			let tickables = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables || [];
-			if ([121].includes(state.subjectId)) {
-				tickables = note.sourceMeasure.verticalSourceStaffEntryContainers;
-			}
-			// console.log(note.sourceMeasure.MeasureNumberXML, note.sourceMeasure.verticalSourceStaffEntryContainers.length)
-			nodeDetail.noteLength = tickables.length || 1;
-			allNotes.push(nodeDetail);
-			allNoteId.push(nodeDetail.id);
-			measures.push(nodeDetail);
-			if (si < tickables.length - 1) {
-				si++;
-			} else {
-				si = 0;
-				relaMeasureLength = 0;
-				measures = [];
-			}
-		}
-		i++;
-	}
-	// 按照时间轴排序
-	const sortArray = allNotes.sort((a, b) => a.relativeTime - b.relativeTime).map((item, index) => ({ ...item, i: index }));
-	console.timeEnd("音符跑完时间");
-	try {
-		osmd.cursor.reset();
-	} catch (error) {}
-	state.activeMeasureIndex = sortArray[0].MeasureNumberXML;
-	return sortArray;
-};
-
-/** 获取小节之间的连音线,仅同音高*/
-export const getNoteByMeasuresSlursStart = (note: any) => {
-	let activeNote = note;
-	let tieNote;
-	if (note.noteElement.tie && note.noteElement.tie.StartNote) {
-		tieNote = note.noteElement.tie.StartNote;
-	}
-	if (activeNote && tieNote && tieNote !== activeNote.noteElement) {
-		for (const note of state.times) {
-			if (tieNote === note.noteElement) {
-				return note;
-			}
-		}
-	}
-	return activeNote;
-};

+ 17 - 161
src/helpers/metronome.ts

@@ -4,15 +4,11 @@
  * time: 2022.11.14
  */
 import { reactive, watch } from "vue";
-import { tickUrl as tick, tockUrl as tock } from "/src/constant/audios";
-import { browser } from "/src/utils/index";
-import state from "/src/state";
 import { Howl } from "howler";
 import tockAndTick from "/src/constant/tockAndTick.json";
 type IOptions = {
 	speed: number;
 };
-const browserInfo = browser();
 
 export const metronomeData = reactive({
 	disable: true,
@@ -27,32 +23,6 @@ export const metronomeData = reactive({
 	activeMetro: {} as any,
 });
 
-// 切换隐藏光标
-const toggleLine = () => {
-	if (!metronomeData.lineShow) return;
-	const img: HTMLElement = document.querySelector("#cursorImg-0")!;
-	if (img) {
-		if (state.times[state.activeNoteIndex].multipleRestMeasures) {
-			img.classList.remove("lineHide");
-		} else {
-			img.classList.add("lineHide");
-		}
-	}
-};
-watch(
-	() => metronomeData.lineShow,
-	() => {
-		const img: HTMLElement = document.querySelector("#cursorImg-0")!;
-		if (img) {
-			if (metronomeData.lineShow) {
-				img.classList.add("lineHide");
-			} else {
-				img.classList.remove("lineHide");
-			}
-		}
-	}
-);
-
 class Metronome {
 	playType = "tick";
 	source = null as any; // 创建音频源头
@@ -76,9 +46,6 @@ class Metronome {
 
 	// 播放
 	sound = (currentTime: number) => {
-		if (!state.sectionStatus){
-			currentTime = setCurrentTime(currentTime);
-		}
 		let index = -1;
 		let activeMetro = -1;
 		for (let i = 0; i < metronomeData.metroList.length; i++) {
@@ -101,14 +68,13 @@ class Metronome {
 			metronomeData.isClick = false;
 			return;
 		}
-		toggleLine()
 		metronomeData.isClick = false;
 	};
 	// 播放
 	playAudio = () => {
 		if (!metronomeData.initPlayerState) return;
 		this.source = metronomeData.activeMetro?.index === 0 ? this.source1 : this.source2;
-		this.source.volume(metronomeData.disable || state.playState === 'paused' ? 0 : 0.4);
+		this.source.volume(metronomeData.disable ? 0 : 1);
 		this.source.play();
 	};
 
@@ -141,70 +107,38 @@ class Metronome {
 	calculation(times: any[]) {
 		// console.log("🚀 ~ times", times);
 		// 1.统计有多少小节
-		const measures: any[] = [];
+		const measures: any = {};
 		let xmlNumber = -1;
 		for (let i = 0; i < times.length; i++) {
 			const note = times[i];
-			const measureNumberXML = note?.noteElement?.sourceMeasure?.MeasureNumberXML || -1;
+			const measureNumberXML = note?.timeNote?.measureNumber;
 			// console.log("🚀 ~ note?.noteElement?.sourceMeasure", note?.noteElement?.sourceMeasure)
 			// console.log("🚀 ~ measureNumberXML", measureNumberXML, note)
 			// console.log("🚀 ~ measureNumberXML", note)
-			const measureListIndex = note?.noteElement?.sourceMeasure?.measureListIndex;
 			if (measureNumberXML > -1) {
 				if (measureNumberXML != xmlNumber) {
 					const m = {
 						measureNumberXML: measureNumberXML,
-						measureNumberIndex: measureListIndex,
-						numerator: note?.noteElement?.sourceMeasure?.ActiveTimeSignature?.numerator || 0,
-						start: note.measures[0].time,
-						end: note.measures[note.measures.length - 1].endtime,
-						time: note.measures[note.measures.length - 1].endtime - note.measures[0].time,
-						stave_x: note?.noteElement?.sourceMeasure?.verticalMeasureList?.[0]?.stave?.x || 0,
-						end_x: note?.stave?.end_x || 0 || 0,
+						numerator: note?.measure?.numerator || 0,
+						time: note?.timeNote?.millisecondsPerMeasure,
 						stepList: [] as number[],
-						svgs: [] as any[],
 						isRestFlag: note.isRestFlag,
+						notes: [] as any[]
 					};
-					// 2.统计小节的拍数
-					// 3.统计小节的时长, 开始时间,结束时间
-					// console.log(measureNumberXML,note.measures, times.filter((n: any) => n?.noteElement?.sourceMeasure?.measureListIndex == measureListIndex))
-					if ([121].includes(state.subjectId)) {
-						const _measures = times.filter((n: any) => n?.noteElement?.sourceMeasure?.measureListIndex == measureListIndex);
-						note.measures = _measures;
-						m.start = note.measures[0].time;
-						m.end = note.measures[note.measures.length - 1].endtime;
-						m.time = note.measures[note.measures.length - 1].endtime - note.measures[0].time;
-						try {
-							const tickables = note.noteElement.sourceMeasure.verticalMeasureList.reduce((arr: any[], value: any) => {
-								arr.push(...value.vfVoices["1"].tickables);
-								return arr;
-							}, []);
-							const xList: any[] = [];
-							m.svgs = tickables
-								.map((n: any) => {
-									const x = n.getBoundingBox().x;
-									if (!xList.includes(x) && n.duration !== "w") {
-										xList.push(x);
-										n._start_x = x;
-										return n;
-									}
-								})
-								.filter(Boolean)
-								.sort((a: any, b: any) => a._start_x - b._start_x);
-							// console.log(measureNumberXML, m.svgs)
-						} catch (error) {
-							console.log(error);
-						}
-						m.stepList = calculateMutilpleMetroStep(note.measures, m);
-					} else {
-						m.stepList = calculateMetroStep(note.measures, m);
-					}
-					measures.push(m);
+					measures[measureNumberXML] = m;
 					xmlNumber = measureNumberXML;
+				} else {
+					measures[measureNumberXML].notes.push(note);
 				}
 			}
 		}
-		// console.log(measures, measures.length);
+		console.log(measures, measures.length);
+		return
+
+					// 2.统计小节的拍数
+					// 3.统计小节的时长, 开始时间,结束时间
+					// console.log(measureNumberXML,note.measures, times.filter((n: any) => n?.noteElement?.sourceMeasure?.measureListIndex == measureListIndex))
+					// m.stepList = calculateMetroStep(note.measures, m);
 
 		let metroList: number[] = [];
 		const metroMeasure: any[] = [];
@@ -214,7 +148,7 @@ class Metronome {
 				const measure = measures[i];
 				const noteStep = measure.time / measure.numerator;
 				// console.log("🚀 ~ measure.measureNumberXML",measure.measureNumberXML, noteStep)
-				const WIDTH = [121].includes(state.subjectId) ? 95 : 100;
+				const WIDTH =  100;
 				const widthStep = WIDTH / (measure.numerator + 1);
 				metroMeasure[i] = [] as number[];
 				// console.log('stepList', [...measure.stepList], measure.measureNumberXML)
@@ -357,83 +291,5 @@ function calculateMetroStep(arr: any[], m: any): number[] {
 	// console.log("stepList", [...stepList], m.measureNumberXML);
 	return stepList;
 }
-// 计算单声部多声轨的拍子的时值
-function calculateMutilpleMetroStep(arr: any[], m: any): number[] {
-	// console.log("🚀 ~ m:", [...m.svgs])
-	const step = m.time / m.numerator;
-	const measure_bbox = arr[0]?.svgElement?.attrs?.el?.parentElement?.parentElement?.getBoundingClientRect?.() || { x: 0 };
-	if (arr.length === 1) {
-		const staveNote = m.svgs[0];
-		// 大于一拍
-		let bbox = staveNote?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
-		if (staveNote && !staveNote.isRest()) {
-			return [bbox.x - measure_bbox.x];
-		}
-		return [];
-	}
-	// console.log("🚀 ~ arr", arr, step, m.measureNumberXML);
-	let total = 0;
-	let notes: any[] = [];
-	let stepList: number[] = [];
-	for (let i = 0; i < arr.length; i++) {
-		const item = arr[i];
-		item._index = i;
-		const noteTime = item.endtime - item.time;
-		total += noteTime;
-		let svgEle = m.svgs[i]?.attrs?.el;
-		// 大于一拍
-		let bbox = svgEle?.getBoundingClientRect?.() || { x: 0 };
-		// console.log(m.measureNumberXML, svgEle, i)
-		if (noteTime > step) {
-			total -= step;
-			// console.log('超过一拍了', notes, m.measureNumberXML)
-			let x = bbox.x - measure_bbox.x;
-			if (notes.length > 0) {
-				svgEle = m.svgs[notes[0]._index]?.attrs?.el;
-				bbox = svgEle?.getBoundingClientRect?.() || { x: 0 };
-				x = bbox.x - measure_bbox.x;
-			}
-			stepList.push(x);
-			notes = [];
-		} else {
-			notes.push(item);
-		}
-		// console.log(notes)
-		if (Math.abs(total - step) < 0.001) {
-			let x = bbox.x - measure_bbox.x;
-			if (notes.length > 0) {
-				svgEle = m.svgs[notes[0]._index]?.attrs?.el;
-				bbox = svgEle?.getBoundingClientRect?.() || { x: 0 };
-				x = bbox.x - measure_bbox.x;
-			}
-			// console.log("一拍",svgEle,notes,m.svgs, m.measureNumberXML);
-			stepList.push(x);
-			total = 0;
-			notes = [];
-		}
-	}
-	stepList = stepList.reduce((list: any[], n: number) => {
-		if (list.includes(n)) {
-			list.push(undefined as any);
-		} else {
-			list.push(n);
-		}
-		return list;
-	}, []); //Array.from(new Set(stepList))
-	// console.log('stepList', stepList, m.measureNumberXML)
-	return stepList;
-}
-
-// 延迟兼容处理
-function setCurrentTime(time: number) {
-	if (browserInfo.huawei || browserInfo.xiaomi) {
-		time += 0.125;
-	} else if (browserInfo.android) {
-		time += 0.11;
-	} else if (browserInfo.ios) {
-		time += 0.01;
-	}
-	return time;
-}
 
 export default Metronome;

+ 21 - 27
src/pc/home/runtime.ts

@@ -147,7 +147,6 @@ export const createNote = (options: Partial<INote>): INote => {
 		accidental: options.accidental || "",
 		content: options.content || "",
 		noteType: options.noteType || "",
-		meter: options.meter || "",
 		clef: options.clef || "",
 		play: options.play || [],
 		key: options.key || "",
@@ -176,6 +175,7 @@ export const createMeasure = (): IMeasure => {
 		measureNumber: 0,
 		celf: "",
 		key: "",
+		meter: "",
 	};
 };
 
@@ -204,49 +204,43 @@ export const renderMeasures = (abc: IAbc, option?: IRenderMeasuresOption) => {
 	const measures = abc.measures;
 	for (let i = 0; i < measures.length; i++) {
 		const measure = measures[i];
-		text += measure.repeat;
+		text += measure.repeat ?? '';// 重复
+		text += measure.meter ?? '';// 拍号
 		for (let j = 0; j < measure.notes.length; j++) {
 			const note = measure.notes[j];
-			const playStr = note.play?.join("") || "";
+			const playStr = note.play?.join("") ?? "";
 
-			text += note.clef; // 谱号
-			text += note.key; // 调号
-			text += note.speed; // 速度
-			text += note.meter; // 拍号
-			text += note.slus; // 3连音
-			if (note.tie.includes("(")) {
+			text += note.clef ?? ''; // 谱号
+			text += note.key ?? ''; // 调号
+			text += note.speed ?? ''; // 速度
+			text += note.slus ?? ''; // 3连音
+			if (note.tie?.includes("(")) {
 				// 连音线 前
-				text += note.tie;
+				text += note.tie ?? '';
 			}
 			if (!option?.hiddenIndex){
 				text += `"<${i + "." + j}"`; // 音符 id
 			}
-			text += playStr; // 演奏技法
-			text += note.dynamics; // 力度符号
-			text += note.accidental; // 临时升降记号
-			text += note.content; // 音符
+			text += playStr ?? ''; // 演奏技法
+			text += note.dynamics ?? ''; // 力度符号
+			text += note.accidental ?? ''; // 临时升降记号
+			text += note.content ?? ''; // 音符
 			// 音符时值
-			text += note.noteType;
-			text += note.dot;
-			
-			// if(note.dot){
-			// 	text += note.dot; // 附点
-			// } else {
-			// 	text += note.noteType; // 音符类型
-			// }
+			text += note.noteType ?? '';
+			text += note.dot ?? ''; // 点
 
-			text += note.tieline; // 延音
-			if (note.tie.includes(")")) {
+			text += note.tieline ?? ''; // 延音
+			if (note.tie?.includes(")")) {
 				// 连音线 后
-				text += note.tie;
+				text += note.tie ?? '';
 			}
-			text += note.segno; // 分割
+			text += note.segno ?? ''; // 分割
 		}
 		let _i = i + 1;
 		if (!option?.hiddenIndex) {
 			text += `"<${_i}"`
 		}
-		text += `${ measure.barline}`;
+		text += measure.barline ?? '';
 		if (wrap % 4 === 0) {
 			text += "\n";
 		}

+ 0 - 516
src/state.ts

@@ -1,516 +0,0 @@
-import { closeToast, showToast } from "vant";
-import { reactive } from "vue";
-import { OpenSheetMusicDisplay } from "../osmd-extended/src";
-import { metronomeData } from "./helpers/metronome";
-import { GradualNote, GradualTimes, GradualVersion } from "./type";
-import { handleEndEvaluat, handleStartEvaluat } from "./view/evaluating";
-import { IFingering } from "src/view/fingering/fingering-config";
-import { handleStartTick } from "./view/tick";
-import { audioListStart, getAudioCurrentTime, getAudioDuration, setAudioCurrentTime, setAudioPlaybackRate } from "./view/audio-list";
-import { toggleFollow } from "./view/follow-practice";
-import { browser, setStorageSpeed } from "./utils";
-
-/** 入门 | 进阶 | 大师 */
-export type IDifficulty = "BEGINNER" | "ADVANCED" | "PERFORMER";
-/** 渲染类型: 五线谱,简谱 */
-export enum EnumMusicRenderType {
-	/** 五线谱 */
-	staff = "staff",
-	/** 简谱 */
-	firstTone = "firstTone",
-	/** 固定音高 */
-	fixedTone = "fixedTone"
-}
-export const musicscoresettingKey = "musicscoresetting";
-/** 有声音的是那个音源 */
-export type IPlayState = "music" | "background"
-/** 播放状态 */
-export type IAudioState = "play" | "paused"
-
-/** 来源 */
-export enum IPlatform {
-	APP = "APP",
-	PC = "PC"
-}
-
-const state = reactive({
-	/** 来源 : PC , app */
-	platform: '' as IPlatform,
-	appName: "" as "GYM" | "COLEXIU",
-	musicRenderType: EnumMusicRenderType.staff as EnumMusicRenderType,
-	/**曲谱是否渲染完成 */
-	musicRendered: false,
-	/** 当前曲谱数据ID, 和曲谱ID不一致 */
-	detailId: "",
-	/** 曲谱资源URL */
-	xmlUrl: "",
-	/** 声部ID */
-	subjectId: 0 as number,
-	/** 分类ID */
-	categoriesId: 0,
-	/** 分类名称 */
-	categoriesName: "",
-	/** 是否支持评测 */
-	enableEvaluation: true,
-	/** 是否支持转谱 */
-	enableNotation: false,
-	/** 曲谱ID */
-	examSongId: "",
-	/** 曲谱名称 */
-	examSongName: "",
-	/** 扩展字段 */
-	extConfigJson: {} as any,
-	/** 扩展样式字段 */
-	extStyleConfigJson: {} as any,
-	/** 是否开启节拍器 */
-	isOpenMetronome: false,
-	/** 是否显示指法 */
-	isShowFingering: false,
-	/** 原音 */
-	music: "",
-	/** 伴奏 */
-	accompany: "",
-	/** midiURL */
-	midiUrl: "",
-	/** 父分ID */
-	parentCategoriesId: 0,
-	/** 资源类型: mp3 | midi */
-	playMode: "MP3" as "MP3" | "MIDI",
-	/** 设置的速度 */
-	speed: 0,
-	/** 曲谱音频正常的速度 */
-	originSpeed: 0,
-	/** 分轨名称 */
-	track: "",
-	/** 当前显示声部索引 */
-	partIndex: 0,
-	/** 是否需要节拍器 */
-	needTick: false,
-	/** 曲谱实例 */
-	osmd: null as unknown as OpenSheetMusicDisplay,
-	/**是否是特殊乐谱类型, 主要针对管乐迷  */
-	isSpecialBookCategory: false,
-	/** 播放状态 */
-	playState: "paused" as IAudioState,
-	/** 播放结束状态 */
-	playEnd: false,
-	/** 播放那个: 原音,伴奏 */
-	playSource: "music" as IPlayState,
-	/** 播放进度 */
-	playProgress: 0,
-	/** 激活的note index */
-	activeNoteIndex: 0,
-	/** 激活的小节 */
-	activeMeasureIndex: 0,
-	/** 选段状态 */
-	sectionStatus: false,
-	/** 选段数据 */
-	section: [] as any[],
-	/** 选段背景 */
-	sectionBoundingBoxs: [] as any[],
-	/** 开启选段预备 */
-	isOpenPrepare: false,
-	/** 选段预备 */
-	sectionFirst: null as any,
-	/** 音符数据 */
-	times: [] as any[],
-	/** 播放模式 */
-	modeType: "practise" as "practise" | "follow" | "evaluating",
-	/** 设置 */
-	setting: {
-		/** 效音提醒 */
-		soundEffect: true,
-		/** 护眼模式 */
-		eyeProtection: false,
-		/** 摄像头 */
-		camera: false,
-		/** 摄像头透明度 */
-		cameraOpacity: 70,
-		/** 循环播放 */
-		repeatAutoPlay: true,
-		/** 显示指法 */
-		displayFingering: true,
-		/** 显示光标 */
-		displayCursor: true,
-		/** 频率 */
-		frequency: 442,
-		/** 评测难度 */
-		evaluationDifficulty: "ADVANCED" as IDifficulty,
-		/** 保存到相册 */
-		saveToAlbum: false,
-		/** 开启伴奏 */
-		enableAccompaniment: true,
-		/** 反应时间 */
-		reactionTimeMs: 0,
-	},
-	/** 节拍器的时间 */
-	fixtime: 0,
-	/** 指法信息 */
-	fingeringInfo: {} as IFingering,
-	/** 滚动容器的ID */
-	scrollContainer: "musicAndSelection",
-	/** 是否是打击乐 */
-	isPercussion: false,
-	/** 是否重复节拍器的时间 */
-	repeatedBeats: 0,
-	/**当前曲谱中所有声部名字 */
-	partListNames: [] as string[],
-	/** 渐变速度信息 */
-	gradual: [] as GradualNote[],
-	/** 渐变速度版本 */
-	gradualVersion: GradualVersion.BASE as GradualVersion,
-	/** 渐变时间信息 */
-	gradualTimes: null as GradualTimes,
-	/** 单声部多声轨 */
-	multitrack: 0,
-	/** 缩放 */
-	zoom: 0.8,
-	/** 渲染曲谱比例 */
-	musicZoom: 1,
-	/** 练习,评测是否是选段模式 */
-	isSelectMeasureMode: false,
-	/** 是否是评分显示 */
-	isReport: false
-});
-const browserInfo = browser()
-let offset_duration = 0
-/** 自定义数据 */
-export const customData = reactive({
-	/** 自定义音符时值 */
-	customNoteRealValue: [] as any,
-	/** 自定义音符按读取到的时值 */
-	customNoteCurrentTime: false
-})
-/** 在渲染前后计算光标应该走到的音符 */
-const setStep = () => {
-	if (state.playState !== "play") {
-		console.log("暂停播放");
-		return;
-	}
-	let startTime = Date.now();
-	requestAnimationFrame(() => {
-		const endTime = Date.now();
-		// 渲染时间大于16.6,就会让页面卡顿, 如果渲染时间大与16.6就下一个渲染帧去计算
-		if (endTime - startTime < 16.7) {
-			handlePlaying();
-			setStep();
-		} else {
-			setTimeout(() => {
-				handlePlaying();
-				setStep();
-			}, 16.7)
-		}
-		
-	});
-};
-/** 开始播放 */
-export const onPlay = () => {
-	console.log("开始播放");
-	state.playEnd = false;
-	offset_duration = browserInfo.xiaomi ? 0.2 : 0.08
-	setStep();
-};
-
-/** 播放模式结束自动重播 */
-const autoResetPlay = () => {
-	if (state.modeType !== "practise") return;
-	skipNotePlay(0, true);
-	// 没有开启自动重播, 不是练习模式
-	if (!state.setting.repeatAutoPlay) return;
-	scrollViewNote();
-	setTimeout(() => {
-		togglePlay("play");
-	}, 1000);
-};
-
-/** 播放完成事件 */
-export const onEnded = () => {
-	console.log("音频播放结束");
-	// 修改状态为结束
-	state.playEnd = true;
-	state.playState = "paused";
-	// 结束播放
-	audioListStart(state.playState);
-	// 调用结束评测
-	handleEndEvaluat(true);
-	// 调用自动重复播放
-	autoResetPlay();
-};
-
-/**
- * 播放一直触发的事件
- */
-const handlePlaying = () => {
-	const currentTime = getAudioCurrentTime();
-	const duration = getAudioDuration();
-	state.playProgress = (currentTime / duration) * 100;
-	let item = getNote(currentTime);
-
-	if (item) {
-		// 选段状态下
-		if (state.sectionStatus && state.section.length === 2) {
-			// 如果开启了预备拍
-			const selectStartItem = state.sectionFirst ? state.sectionFirst : state.section[0];
-			const selectEndItem = state.section[1];
-				
-			if (Math.abs(selectEndItem.endtime - currentTime) < offset_duration) {
-				console.log("选段播放结束");
-				// 如果为选段评测模式
-				if (state.modeType === "evaluating" && state.isSelectMeasureMode) {
-					onEnded();
-					return;
-				}
-				item = selectStartItem;
-				setAudioCurrentTime(selectStartItem.time, selectStartItem.i);
-			}
-		}
-		gotoNext(item);
-	}
-
-	metronomeData.metro?.sound(currentTime);
-};
-/** 跳转到指定音符开始播放 */
-export const skipNotePlay = (itemIndex: number, isStart = false) => {
-	const item = state.times[itemIndex];
-	let itemTime = item.time;
-	if (isStart) {
-		itemTime = 0;
-	}
-	// console.log("🚀 ~ itemTime:", itemTime);
-	if (item) {
-		setAudioCurrentTime(itemTime, itemIndex);
-		gotoNext(item);
-		metronomeData.metro?.sound(itemTime);
-	}
-};
-
-/**
- * 切换曲谱播放状态
- * @param playState 可选: 默认 undefined, 需要切换的状态 play:播放, paused: 暂停
- */
-export const togglePlay = async (playState?: "play" | "paused") => {
-	state.playState = playState ? playState : state.playState === "paused" ? "play" : "paused";
-	// 设置为开始播放时, 如果需要节拍,先播放节拍器
-	if (state.playState === "play" && state.needTick) {
-		const tickend = await handleStartTick();
-		// console.log("🚀 ~ tickend:", tickend)
-		// 节拍器返回false, 取消播放
-		if (!tickend) {
-			state.playState = "paused";
-			return false;
-		}
-	}
-	// 如果选段没有结束, 直接开始播放,清空选段状态
-	if (state.playState == "play") {
-		if (state.sectionStatus && state.section.length < 2) {
-			clearSelection();
-		}
-	}
-	audioListStart(state.playState);
-	return true;
-};
-/** 结束播放 */
-export const handleStopPlay = () => {
-	state.playState = "paused";
-	audioListStart(state.playState);
-};
-
-/** 重置播放为开始 */
-export const resetPlaybackToStart = () => {
-	// 如果为选段状态
-	if (state.sectionStatus && state.section.length === 2) {
-		state.section = formateSelectMearure(state.section);
-		return;
-	}
-	skipNotePlay(0, true);
-};
-
-/** 跳转到指定音符 */
-export const gotoCustomNote = (index: number) => {
-	try {
-		state.osmd.cursor.reset();
-	} catch (error) {}
-	for (let i = 0; i < index; i++) {
-		state.osmd.cursor.next();
-	}
-};
-/** 跳转到下一个音符 */
-export const gotoNext = (note: any) => {
-	const num = note.i;
-	// console.log('next', state.activeNoteIndex, num)
-	if (state.activeNoteIndex === note.i) return;
-	const osmd = state.osmd;
-	let prev = state.activeNoteIndex;
-	state.activeNoteIndex = num;
-	state.activeMeasureIndex = note.MeasureNumberXML;
-
-	if (prev && num - prev === 1) {
-		osmd.cursor.next();
-	} else if (prev && num - prev > 0) {
-		while (num - prev > 0) {
-			prev++;
-			osmd.cursor.next();
-		}
-	} else {
-		gotoCustomNote(num);
-	}
-
-	scrollViewNote();
-};
-/** 获取指定音符 */
-export const getNote = (currentTime: number) => {
-	const times = state.times;
-	const len = state.times.length;
-	/** 播放超过了最后一个音符的时间,直接结束, 2秒误差 */
-	if (currentTime > times[len - 1].endtime + 2) {
-		onEnded();
-		return;
-	}
-	let _item = null as any;
-	for (let i = state.activeNoteIndex; i < len; i++) {
-		const item = times[i];
-		const prevItem = times[i - 1];
-		if (currentTime >= item.time) {
-			if (!prevItem || item.time != prevItem.time) {
-				_item = item;
-			}
-		} else {
-			break;
-		}
-	}
-	// console.log("activeNoteIndex", currentTime, state.activeNoteIndex, _item.i);
-	return _item;
-};
-
-/** 重播 */
-export const handleResetPlay = () => {
-	resetPlaybackToStart();
-	// 如果是暂停, 直接播放
-	togglePlay('play')
-};
-/** 设置速度 */
-export const handleSetSpeed = (speed: number) => {
-	setStorageSpeed(state.examSongId, speed)
-	state.speed = speed;
-};
-/** 清除选段状态 */
-export const clearSelection = () => {
-	state.sectionStatus = false;
-	state.section = [];
-	closeToast();
-};
-
-/** 开启选段 */
-export const handleChangeSection = () => {
-	// 如果开启了选段,再次点击取消选段
-	if (state.sectionStatus) {
-		togglePlay('paused')
-		skipNotePlay(0, true);
-		clearSelection();
-		return;
-	}
-	state.sectionStatus = true;
-	// 开启
-	if (state.sectionStatus) {
-		togglePlay("paused");
-	}
-	showToast({
-		message: "请选择开始小节",
-		duration: 0,
-		position: "top",
-		className: "selectionToast",
-	})
-};
-
-/** 效验并格式化选段小节 */
-const formateSelectMearure = (_list: any[]): any[] => {
-	if (!_list.length) return [];
-	const list = _list.sort((a, b) => a.time - b.time);
-	const startXml = list[0]?.measureOpenIndex;
-	const endXml = list.last()?.measureOpenIndex;
-	const selectStartMeasure = state.times.filter((n: any) => startXml === n.measureOpenIndex) || [];
-	const selectEndMeasure = state.times.filter((n: any) => endXml === n.measureOpenIndex) || [];
-	// 没有找到选段小节
-	if (!selectStartMeasure.length || !selectEndMeasure.length) {
-		clearSelection();
-		return [];
-	}
-	list[0] = selectStartMeasure[0];
-	list[1] = selectEndMeasure.last();
-	let startItemINdex = list[0].i;
-	// 开启预备拍
-	if (state.isOpenPrepare) {
-		const startXmlIndex = list[0].MeasureNumberXML;
-		state.sectionFirst = state.times.find((n: any) => startXmlIndex - n.MeasureNumberXML === 1);
-		startItemINdex = state.sectionFirst ? state.sectionFirst.i : startItemINdex;
-	}
-	skipNotePlay(startItemINdex, startItemINdex === 0);
-	return list;
-};
-
-/** 选择选段 */
-export const handleSelection = (item: any) => {
-	if (!state.sectionStatus || state.section.length > 1) return;
-	if (state.section.length !== 2 && item) {
-		state.section.push(item);
-		if (state.section.length === 2) {
-			state.section = formateSelectMearure(state.section);
-			closeToast();
-		}
-	}
-	if (state.section.length === 1) {
-		showToast({
-			message: "请选择结束小节",
-			duration: 0,
-			position: "top",
-			className: "selectionToast",
-		})
-	}
-};
-
-/** 直接设置选段 */
-export const hanldeDirectSelection = (list: any[]) => {
-	if (!Array.isArray(list) || list.length !== 2) return;
-	state.sectionStatus = true;
-	state.section = formateSelectMearure(list);
-};
-let offsetTop = 0;
-/**
- * 窗口内滚动到音符的区域
- * @param isScroll 可选: 强制滚动到顶部, 默认: false
- * @returns void
- */
-export const scrollViewNote = () => {
-	const cursorElement = document.getElementById("cursorImg-0")!;
-	const musicAndSelection = document.getElementById(state.scrollContainer)!;
-	if (!cursorElement || !musicAndSelection || offsetTop === cursorElement.offsetTop) return;
-	offsetTop = cursorElement.offsetTop;
-	if (offsetTop > 50) {
-		musicAndSelection.scrollTo({
-			top: (offsetTop - 50) * state.musicZoom ,
-			behavior: "smooth",
-		});
-	} else {
-		musicAndSelection.scrollTo({
-			top: 0,
-			behavior: "smooth",
-		});
-	}
-};
-
-/** 检测是否是节奏练习 */
-export const isRhythmicExercises = () => {
-	return state.examSongName.indexOf("节奏练习") > -1;
-};
-
-/** 重置状态 */
-export const handleRessetState = () => {
-	if (state.modeType === "evaluating") {
-		handleStartEvaluat();
-	} else if (state.modeType === "practise") {
-		togglePlay("paused");
-	} else if (state.modeType === "follow") {
-		toggleFollow(false);
-	}
-};
-export default state;