Ver Fonte

Merge branch 'feature-mjk' into ktyq-online-1.8.7

TIANYONG há 9 meses atrás
pai
commit
68da0166ea

+ 1 - 1
src/helpers/customMusicScore.ts

@@ -849,7 +849,7 @@ export const setGlobalMusicSheet = () => {
 	const customAccentItem = customAccentList.find(({id, part_index}) => {
 	  return id == state.cbsExamSongId && part_index == partIndex
 	})
-	if (customAccentItem) {
+	if (customAccentItem || state.isEvxml) {
 	  setGlobalData('customAccentItem', true)
 	}
 	/** 全声部声部 +  */

+ 188 - 10
src/helpers/formateMusic.ts

@@ -17,6 +17,11 @@ const browserInfo = browser();
 dayjs.extend(duration);
 
 /**
+ * 需要隐藏的中文速度文本
+ */
+const hideSpeedWords: string[] = ["中速"];
+
+/**
  * 获取节拍器的时间
  * @param speed 速度
  * @param firstMeasure 曲谱第一个小节
@@ -378,6 +383,7 @@ export const onlyVisible = (xml: string, partIndex: number): string => {
 	// console.log(visiblePartInfo, partIndex)
 	// 根据后台已选择的分轨筛选出能切换的声轨
 	state.partListNames = partListNames;
+	// console.log('分轨名称',state.partListNames)
 	if (visiblePartInfo) {
 		const id = visiblePartInfo.getAttribute("id");
 		Array.from(parts).forEach((part: any) => {
@@ -615,6 +621,7 @@ export const formatZoom = (num = 1) => {
  */
 export const formatXML = (xml: string, xmlUrl?: string): string => {
 	if (!xml) return "";
+	
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
 	const measures = Array.from(xmlParse.getElementsByTagName("measure"));
 	const repeats: any = Array.from(xmlParse.querySelectorAll('repeat'));
@@ -624,6 +631,7 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 	// 解析处理evxml
 	if (state.isEvxml) {
 		analyzeEvxml(xmlParse, xmlUrl);
+		customizationXml(xmlParse);
 	}
 	// const words: any = xmlParse.getElementsByTagName("words");
 	// for (const word of words) {
@@ -655,11 +663,30 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 				// if (note.getElementsByTagName("space").length && !note.getElementsByTagName("duration").length) {
 				// 	measure.removeChild(note);
 				// }
-				if (!note.getElementsByTagName("duration").length || (note.getElementsByTagName("duration").length && note.getElementsByTagName("duration")[0]?.textContent == 0)) {
-					measure.removeChild(note);
+				// 非倚音音符
+				if (!note.getElementsByTagName("grace").length) {
+					if (!note.getElementsByTagName("duration").length || (note.getElementsByTagName("duration").length && note.getElementsByTagName("duration")[0]?.textContent == 0)) {
+						measure.removeChild(note);
+					}
 				}
 			});
 		}
+		// 如果有特殊中文速度文本,需要删除
+		const reg = new RegExp("[\\u4E00-\\u9FFF]+", "g");
+		if (measure.getElementsByTagName("words").length && state.isEvxml) {
+			const wordList = Array.from(measure.getElementsByTagName("words")) || [];
+			wordList.forEach((word: any) => {
+				// TODO:删除妙极客曲子无意义的words
+				// wordArr?.push(word?.textContent)
+				if (word?.textContent && reg.test(word?.textContent) && word?.parentNode?.parentNode) {
+					measure.removeChild(word.parentNode.parentNode);
+					// deleteWordArr?.push(word?.textContent)
+				}
+				// if(hideSpeedWords.includes(word?.textContent) && word?.parentNode?.parentNode) {
+				// 	measure.removeChild(word.parentNode.parentNode);
+				// }
+			})
+		}
 		if (measure.getElementsByTagName("note").length === 0) {
 			const forwardTimeElement = measure.getElementsByTagName("forward")[0]?.getElementsByTagName("duration")[0];
 			if (forwardTimeElement) {
@@ -745,6 +772,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	let staveNoteIndex = 0;
 	let staveIndex = 0;
 
+	let preNoteEndTime = 0; // 上一个音符的结束时间
+
 	const _notes = [] as any[];
 	if (state.gradualTimes) {
 		console.log("后台设置的渐慢小节时间", state.gradual, state.gradualTimes);
@@ -754,6 +783,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	const currentTimes = [] as any[];
 	let isSetNextNoteReal = false;
 	let differFrom = 0;
+	// let testIdx = 0;
+	let repeatIdx = 0; // 循环的次数
 	while (!iterator.EndReached) {
 		// console.log({ ...iterator });
 		const voiceEntries = iterator.CurrentVoiceEntries?.[0] ? [iterator.CurrentVoiceEntries?.[0]] : [];
@@ -837,10 +868,18 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 		}
 
 		iterator.moveToNextVisibleVoiceEntry(false);
+		// 从头开始循环,repeatIdx标记+1
+		if (iterator.backJumpOccurred) {
+			repeatIdx += 1;
+		}
+		iterator.repeatIdx = repeatIdx;
+		// console.log('小节',testIdx,iterator.repeatIdx,iterator.EndReached,iterator.currentMeasureIndex,iterator.backJumpOccurred,iterator.forwardJumpOccurred)
+		// testIdx += 1;
 	}
 	// 是否是变速的曲子
 	const hasVaryingSpeed = _notes.some((item: any) => item.measuresTempoInBPM !== _notes[0].measuresTempoInBPM)
-	console.log('变速曲子',hasVaryingSpeed)
+	console.log('变速曲子',hasVaryingSpeed, _notes)
+	let noteIds: any = [];
 	// let voicesBBox: any = null;
 	for (let { note, iterator, currentTime, isDouble, isMutileSubject } of _notes) {
 		if (note) {
@@ -920,6 +959,12 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			}
 
 			let relativeTime = usetime;
+
+			// 妙极客的曲子,修复有的音符有times,有的音符没有times导致的,累计时长错误问题
+			if (state.isEvxml && relativeTime < preNoteEndTime - fixtime) {
+				relativeTime = preNoteEndTime - fixtime
+			}
+
 			let beatSpeed = 0;
 			// 速度不能为0 此处的速度应该是按照设置的速度而不是校准后的速度,否则mp3速度不对
 			if (measureSpeed !== baseSpeed && !hasVaryingSpeed) {
@@ -1094,6 +1139,21 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			// console.log(note.tie)
 			// console.log('👀看看endtime', duration, relaEndtime, fixtime, i)
 			// console.log('频率',note?.pitch?.frequency,i)
+			/**
+			 * evxml的曲子,如果曲谱xml中带有times信息,则音符时值优先取times中的值
+			 */
+			let evNoteStartTime = 0, evNoteEndTime = 0;
+			if (state.isEvxml && note?.noteTimeInfo?.length) {
+				const idx = noteIds.filter((item: any) => item === svgElement?.attrs.id)?.length || 0
+				evNoteStartTime = note?.noteTimeInfo[idx]?.begin
+				evNoteEndTime = note?.noteTimeInfo[idx]?.end
+				if (evNoteStartTime) {
+					relativeTime = evNoteStartTime - fixtime
+					// usetime = evNoteStartTime - fixtime
+				}
+				// usetime = evNoteStartTime - fixtime
+			}
+			svgElement?.attrs.id && noteIds.push(svgElement?.attrs.id)
 
 			const nodeDetail = {
 				isStaccato: note.voiceEntry.isStaccato(),
@@ -1122,8 +1182,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				note: note.halfTone + 12, // see issue #224
 				fixtime, // 弱起补充的时间
 				relativeTime: retain(relativeTime),
-				time: retain(relativeTime + fixtime), // 开始播放的时间
-				endtime: retain(relaEndtime + fixtime), // 播放完成的时间
+				time: state.isEvxml && evNoteStartTime ? retain(evNoteStartTime) : retain(relativeTime + fixtime), // 开始播放的时间
+				endtime: state.isEvxml && evNoteEndTime ? retain(evNoteEndTime) : retain(relaEndtime + fixtime), // 播放完成的时间
 				relaEndtime: retain(relaEndtime),
 				realValue,
 				halfTone: note.halfTone,
@@ -1141,7 +1201,22 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				totalMultipleRestMeasures, // 当前小节总的合并小节数
 				measureSpeed,  // 小节速度
 				maxNoteNum: note.maxNoteNum, // 当前小节音符最多的分轨的音符数量
+				repeatIdx: iterator.repeatIdx, // 标记是第几遍循环,从0开始
 			};
+			// 如果是妙极客的曲子,并且第二遍循环播放需要等待时间,并且是第二遍循环的第一个小节的第一个音符
+			// if (state.isEvxml && state.secondEvXmlBeginTime && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
+			// 	nodeDetail.time = nodeDetail.time + state.secondEvXmlBeginTime;
+			// 	nodeDetail.endtime = nodeDetail.endtime + state.secondEvXmlBeginTime;
+			// 	usetime = usetime + state.secondEvXmlBeginTime;
+			// 	relativeTime = relativeTime + state.secondEvXmlBeginTime;
+			// }
+			if (state.isEvxml && nodeDetail.repeatIdx && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
+				const currentWaitTime = state.evXmlBeginArr[nodeDetail.repeatIdx] || 0;
+				nodeDetail.time = nodeDetail.time + currentWaitTime;
+				nodeDetail.endtime = nodeDetail.endtime + currentWaitTime;
+				usetime = usetime + currentWaitTime;
+				relativeTime = relativeTime + currentWaitTime;
+			}			
 			nodeDetail.realKey = formatRealKey(note.halfTone - fixedKey * 12, nodeDetail);
 			nodeDetail.duration = nodeDetail.endtime - nodeDetail.time;
 			let tickables = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables || [];
@@ -1150,6 +1225,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			}
 			// console.log(note.sourceMeasure.MeasureNumberXML, note.sourceMeasure.verticalSourceStaffEntryContainers.length)
 			// console.log('👀看看endtime', nodeDetail.duration, relaEndtime, fixtime, i)
+			// console.log('音符时间',nodeDetail.i,nodeDetail.time,nodeDetail.endtime)
 			tickables = tickables.filter((tickable: any) => tickable.attrs?.type !== "GhostNote")
 			const maxNum = (state.isCombineRender && note.maxNoteNum) ? note.maxNoteNum : tickables.length;
 			nodeDetail.noteLength = maxNum || 1;
@@ -1167,11 +1243,14 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				relaMeasureLength = 0;
 				measures = [];
 			}
+			preNoteEndTime = nodeDetail.endtime;
 		}
 		i++;
 	}
 	// 按照时间轴排序
 	const sortArray = allNotes.sort((a, b) => a.relativeTime - b.relativeTime).map((item, index) => ({ ...item, i: index }));
+	// const sortArray = allNotes.sort((a, b) => a.time - b.time).map((item, index) => ({ ...item, i: index }));
+	// const sortArray = allNotes.map((item, index) => ({ ...item, i: index }));
 	console.timeEnd("音符跑完时间");
 	try {
 		osmd.cursor.reset();
@@ -1258,19 +1337,118 @@ export const verifyCanRepeat = (startNum: number, endNum: number) => {
 	}
 }
 
+// 处理妙极客xml谱面
+const customizationXml = (xmlParse: any) => {
+	const credits: any = Array.from(xmlParse.querySelectorAll('credit'));
+	const creators: any = Array.from(xmlParse.querySelectorAll('creator'));
+	const graces: any = Array.from(xmlParse.querySelectorAll('grace'));
+	const measures: any[] = Array.from(xmlParse.getElementsByTagName("measure"));
+	const notes: any[] = Array.from(xmlParse.getElementsByTagName("note"));
+
+	// 获取音符最多的歌词数,用于自定义循环播放次数
+	let maxLyricNum = 0;
+	if (notes && notes.length) {
+		for (const note of notes) {
+			if (maxLyricNum < note.getElementsByTagName("lyric").length) {
+				maxLyricNum = note.getElementsByTagName("lyric").length
+			}
+		}
+	}
+	state.maxLyricNum = maxLyricNum;
+	// state.osmd.EngravingRules.DYCustomRepeatCount = maxLyricNum;
+	;(window as any).DYCustomRepeatCount = state.maxLyricNum;
+	console.log('歌词次数',maxLyricNum)
+
+	if (credits && credits.length) {
+		for (const credit of credits) {
+			if (credit.getElementsByTagName("credit-type")?.[0]?.textContent === 'lyricist') {
+				const creditWord = credit.getElementsByTagName("credit-words")
+				creditWord?.[0].setAttribute('justify', 'right')
+			}
+		}
+	}
+	if (creators && creators.length) {
+		for (const creator of creators) {
+			if (creator.getAttribute('type') === 'lyricist') {
+				// creator.textContent = '测试一下1';
+			}
+			
+		}
+	}
+	// 妙极客xml的倚音(grace)标签需要加上slash=yes属性
+	if (graces && graces.length) {
+		for (const grace of graces) {
+			grace?.setAttribute('slash','yes');
+			// console.log(grace,'倚音')
+		}
+	}
+	// 妙极客xml部分小节没有音符,只有Segno,该小节不需要渲染,表示的是反复标记
+	for (const measure of measures) {
+		const hasNote = measure.getElementsByTagName("note").length;
+		const hasSegno = measure.getElementsByTagName("segno").length;
+		const sounds = Array.from(measure.getElementsByTagName("sound"));
+		const hasSoundSegno = sounds.some((item: any) => item.getAttribute('segno') === 'segno' );
+		if (!hasNote && hasSegno && hasSoundSegno) {
+			const parent = measure.parentNode;
+			parent.removeChild(measure);
+		}
+	}
+
+	/**
+	 * bug: #10289,曲目:1782672015612725196、1788040971888537602
+	 * 妙极客xml,多遍歌词循环的曲目,如果没有repeat标签,需要加上repeat标签
+	 * */
+	if (maxLyricNum > 1) {
+		const hasRepeat = xmlParse.querySelectorAll('repeat').length > 0
+		if (!hasRepeat) {
+			const lastMeasure = measures.last();
+			if (lastMeasure.getElementsByTagName('barline').length) {
+				const barlineDom = lastMeasure.getElementsByTagName('barline')[0]
+				barlineDom.innerHTML = barlineDom.innerHTML + `<repeat direction="backward" />`;
+			} else {
+				lastMeasure.innerHTML = lastMeasure.innerHTML + `
+				<barline location="right">
+					<bar-style>light-heavy</bar-style>
+					<repeat direction="backward" />
+				</barline>`
+			}
+			// console.log(lastMeasure)
+		}
+	}
+}
+
 // 计算evxml的起始播放时间
 const analyzeEvxml = (xmlParse: any, xmlUrl?: string) => {
 	// xml拍号数
 	const xmlNum = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[0]?.getAttribute('num');
+	const denNum = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[0]?.getAttribute('den');
+	const xmlNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('num');
+	const denNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('den');
+	const timeGaps: any = xmlParse.getElementsByTagName("timegap")?.length ? Array.from(xmlParse.getElementsByTagName("timegap")?.[0]?.getElementsByTagName("values")?.[0]?.getElementsByTagName("item")) : [];
 	// 第一个音符的起始时间
-	const firstNoteBeginTime = xmlParse.getElementsByTagName("times")[0]?.getElementsByTagName("time")[0]?.getAttribute('begin');
-	state.evXmlBeginTime = firstNoteBeginTime ? firstNoteBeginTime / 1000 : xmlNum ? 60 / state.originSpeed * xmlNum : 0;
-	const hasTimeGap = xmlParse.getElementsByTagName("timegap").length > 0;
-	const hasTimes = xmlParse.getElementsByTagName("times").length > 0;
+	const firstMeasure = xmlParse.getElementsByTagName("measure")[0];
+	if (firstMeasure) {
+		const firstNoteBeginTime = firstMeasure.getElementsByTagName("times")[0]?.getElementsByTagName("time")[0]?.getAttribute('begin');
+		state.evXmlBeginTime = firstNoteBeginTime ? firstNoteBeginTime / 1000 : xmlNum ? 60 / state.originSpeed * xmlNum * 4/denNum : 0;
+		state.secondEvXmlBeginTime = firstNoteBeginTime ? 0 : xmlNum2 ? 60 / state.originSpeed * xmlNum2 * 4/denNum2 : 0;
+		const hasTimeGap = xmlParse.getElementsByTagName("timegap").length > 0;
+		const hasTimes = xmlParse.getElementsByTagName("times").length > 0;
+
+		if (timeGaps && timeGaps.length && !firstNoteBeginTime) {
+			for (const timeGap of timeGaps) {
+				const num: any = timeGap?.getAttribute('num'), den: any = timeGap?.getAttribute('den');
+				const startTime = num ? 60 / state.originSpeed * num * 4/den : 0;
+				state.evXmlBeginArr.push(startTime)
+			}
+		}
+
+		console.log('🚀 ~ evxml解析','有timegap:',hasTimeGap,'有times:',hasTimes,'timegap集合',state.evXmlBeginArr,'第一个timegap',state.evXmlBeginTime)
+	}
+
 	// if (!hasTimeGap && !hasTimes) {
 	// 	state.noTimes.push(xmlUrl)
 	// }
-	console.log('🚀 ~ evxml解析','有timegap:',hasTimeGap,'有times:',hasTimes)
+	
 }
 
 /**

+ 1 - 0
src/page-instrument/custom-plugins/guide-page/student-top.tsx

@@ -182,6 +182,7 @@ export default defineComponent({
 
   const guideInfo = ref({} as any)
   const getAllGuidance = async()=>{
+    console.log('学生引导123')
     try{
 			if (state.guideInfo) {
 				guideInfo.value = state.guideInfo

+ 9 - 3
src/page-instrument/evaluat-model/index.tsx

@@ -168,8 +168,8 @@ export default defineComponent({
         const item = selectTimes[index];
         const note = getNoteByMeasuresSlursStart(item);
         // #8701 bug: 评测模式,是以曲谱本身的速度进行评测,所以rate取1,不需要转换
-        // const rate = state.speed / state.originSpeed;
-        const rate = 1;
+        const rate = state.speed / state.originSpeed;
+        // const rate = 1;
         const difftime = item.difftime;
         const start = difftime + (item.sourceRelativeTime || item.relativeTime) - starTime;
         const end = difftime + (item.sourceRelaEndtime || item.relaEndtime) - starTime;
@@ -226,7 +226,9 @@ export default defineComponent({
     /** 连接websocket */
     const handleConnect = async () => {
       const behaviorId = localStorage.getItem("behaviorId") || localStorage.getItem("BEHAVIORID") || undefined;
-      const rate = state.speed / state.originSpeed;
+      let rate = state.speed / state.originSpeed;
+      rate = parseFloat(rate.toFixed(2));
+      console.log('速度比例',rate,'速度',state.speed)
       calculateInfo = formatTimes()
       const content = {
         musicXmlInfos: calculateInfo.datas,
@@ -245,6 +247,7 @@ export default defineComponent({
         // beatLength: Math.round((state.fixtime * 1000) / rate),
         beatLength: actualBeatLength,
         evaluationCriteria: state.evaluationStandard,
+        speedRate: rate, // 播放倍率
       };
       await connectWebsocket(content);
       // state.playSource = "music";
@@ -258,12 +261,15 @@ export default defineComponent({
           resetPlaybackToStart()
           return;
         } else if (evaluatingData.resultData?.recordIdStr || evaluatingData.resultData?.recordId) {
+          let rate = state.speed / state.originSpeed;
+          rate = parseFloat(rate.toFixed(2));
           // 上传云端
           // evaluatModel.evaluatUpdateAudio = true;
           api_openAdjustRecording({
             recordId: evaluatingData.resultData?.recordIdStr || evaluatingData.resultData?.recordId,
             title: state.examSongName || "曲谱演奏",
             coverImg: state.coverImg,
+            speedRate: rate, // 播放倍率
           });
           return;
         }

+ 1 - 1
src/page-instrument/header-top/index.tsx

@@ -523,7 +523,7 @@ export default defineComponent({
                       headData.speedShow = !headData.speedShow;
                     }}
                   >
-                    <Badge class={styles.badge} content={state.playState === "play" ? state.playIngSpeed : state.speed}>
+                    <Badge class={styles.badge} content={state.playState === "play" ? Math.floor(state.playIngSpeed) : Math.floor(state.speed)}>
                       <img class={styles.iconBtn} src={headImg("icon_speed.svg")} />
                     </Badge>
                     <span>速度</span>

+ 7 - 1
src/page-instrument/header-top/speed/index.tsx

@@ -23,13 +23,19 @@ export default defineComponent({
 
 		/** 重置速度 */
 		const resetSpeed = () => {
-			speed.value = state.originSpeed;
+			speed.value = Math.floor(state.originSpeed);
 		};
 
 		watch(
 			() => speed.value,
 			() => {
 				handleSetSpeed(speed.value);
+				// if ( Math.abs(Number(speed.value) - Number(state.speed)) >= 1 ) {
+				// 	speed.value = Math.floor(speed.value)
+				// 	handleSetSpeed(speed.value);
+				// } else {
+				// 	//speed.value = state.speed;
+				// }
 			}
 		);
 

+ 1 - 1
src/page-instrument/view-detail/index.tsx

@@ -168,6 +168,7 @@ export default defineComponent({
       setCustomGradual();
 			setCustomNoteRealValue();
       state.times = formateTimes(osmd);
+      // 一行谱
       if (state.isSingleLine) {
         // 音符添加位置信息bbox
         addNoteBBox(state.times);
@@ -377,7 +378,6 @@ export default defineComponent({
       detailData.fingerPreView = false;
       detailData.fingerPreViewGuide = false;
     };
-    console.log(1111222,state.zoom)
     return () => (
       <div
         class={[styles.detail, state.setting.eyeProtection && "eyeProtection", (state.platform === IPlatform.PC && state.zoom > 0.8) && styles.PC, state.isPreView && styles.preViewDetail, state.isSingleLine && styles.singleLineDetail]}

+ 31 - 3
src/state.ts

@@ -378,6 +378,10 @@ const state = reactive({
   fixtime: 0,
   /** evxml等待播放的时间 */
   evXmlBeginTime: 0,
+  /** 第二遍循环evxml等待播放的时间 */
+  secondEvXmlBeginTime: 0,
+  /** evxml等待播放的时间集合,多遍反复播放,会有多个timegap(前奏)时间 */
+  evXmlBeginArr: [] as any,
   /** 指法信息 */
   fingeringInfo: {} as IFingering,
   /** 滚动容器的ID */
@@ -475,6 +479,8 @@ const state = reactive({
   paymentType: null,
   /** 播放模式,默认练习模式 */
   defaultModeType: 1,
+  /** 音符最多歌词次数 */
+  maxLyricNum: 0,
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -509,7 +515,7 @@ const setStep = () => {
 };
 /** 开始播放 */
 export const onPlay = () => {
-  console.log("开始播放");
+  console.log("开始播放",'音频总时长:',getAudioDuration());
   state.playEnd = false;
   // offset_duration = browserInfo.xiaomi ? 0.2 : 0.08;
   offset_duration = 0.2;
@@ -555,7 +561,7 @@ const handlePlaying = () => {
   state.playProgress = (currentTime / duration) * 100;
   let item = getNote(currentTime);
   // console.log(11111,currentTime,duration,state.playSource, item)
-  // console.log(item.i,item.noteId,item.measureSpeed)
+  // console.log(item?.i,item?.noteId,item?.measureSpeed,'播放')
   // 练习模式下,实时刷新小节速度
   if (item && state.modeType === "practise" && state.playState === "play" && item.measureSpeed && item.measureSpeed !== state.playIngSpeed) {
     const ratio = state.speed / state.originSpeed
@@ -563,6 +569,7 @@ const handlePlaying = () => {
   } else if (state.modeType === "practise" && state.playState === "play" && item && !item.measureSpeed) {
     state.playIngSpeed = state.speed
   }
+  state.playIngSpeed = state.playIngSpeed || state.speed;
   if (item) {
     // 选段状态下
     if (state.sectionStatus && state.section.length === 2) {
@@ -906,8 +913,22 @@ export const getNote = (currentTime: number) => {
   }
   let _item = null as any;
   for (let i = state.activeNoteIndex; i < len; i++) {
-    const item = times[i];
+    let item = times[i];
     const prevItem = times[i - 1];
+    // if (state.isEvxml) {
+    //   let diffArr: any[] = [];
+    //   times.forEach((note: any, noteIdx: number) => {
+    //     if (currentTime >= note.time && currentTime <= times[noteIdx+1].time) {
+    //       let diffTime = times[noteIdx+1].time - currentTime;
+    //       diffArr.push({
+    //         diffTime,
+    //         idx: noteIdx
+    //       })
+    //     }
+    //   })
+    //   diffArr.sort((a, b) => a.diffTime - b.diffTime);
+    //   item = diffArr.length ? times[diffArr[0].idx] : item;
+    // }
     if (currentTime >= item.time) {
       if (!prevItem || item.time != prevItem.time) {
         _item = item;
@@ -1347,6 +1368,11 @@ export const followBeatPaly = () => {
 
 // 音符添加bbox
 export const addNoteBBox = (list: any[]) => {
+  const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || {
+		x: 0,
+		y: 0,
+	};
+	const parentLeft = musicContainer.x || 0;
   let voicesBBox: any = null;
   for (let i = 0; i < list.length; i++) {
     const note = list[i];
@@ -1358,7 +1384,9 @@ export const addNoteBBox = (list: any[]) => {
     if (svgElement?.attrs.id) {
       // @ts-ignore
       bbox = document.getElementById(`vf-${svgElement?.attrs?.id}`)?.getBBox();
+      const noteBbox = document.getElementById(`vf-${svgElement?.attrs?.id}`)?.getBoundingClientRect?.() || { x: 0, width: 0 };
       bbox = {
+        left: noteBbox.x - parentLeft - noteBbox.width / 4, // 用于简谱模式,跳动音符时,设置光标的位置(五线谱:osmd自动设置光标位置,简谱:需要手动设置光标位置)
         x: bbox?.x * state.zoom,
         y: bbox?.y * state.zoom,
         width: bbox?.width * state.zoom,

+ 3 - 0
src/view/evaluating/index.tsx

@@ -383,9 +383,12 @@ export const handleStartBegin = async (preTimes?: number) => {
 	// 	accompanimentState: state.setting.enableAccompaniment ? 1 : 0,
 	// 	firstNoteTime: preTimes || 0,
 	// });
+	let rate = state.speed / state.originSpeed;
+	rate = parseFloat(rate.toFixed(2));
 	await api_startRecordingCb({
 		accompanimentState: state.setting.enableAccompaniment ? 1 : 0,
 		firstNoteTime: preTimes || 0,
+		speedRate: rate, // 播放倍率
 	}, () => {
 		if (state.isAppPlay) {
 			setTimeout(() => {

+ 5 - 3
src/view/music-score/index.tsx

@@ -101,7 +101,8 @@ export default defineComponent({
 				autoResize: false,
 				followCursor: false,
 				drawPartNames: props.showPartNames, // 是否渲染声轨名称
-				drawComposer: false, // 渲染作者
+				// drawLyricist: false, // 渲染作曲家
+				// drawComposer: false, // 渲染作词家
 				defaultColorMusic: props.musicColor, // 颜色
 				renderSingleHorizontalStaffline: state.isSingleLine ? true : false,
 				autoGenerateMultipleRestMeasuresFromRestMeasures: state.isSingleLine ? false : true, // 连续休止小节是否合并显示
@@ -109,7 +110,6 @@ export default defineComponent({
 				// pageFormat: 'A4_P',
 				// autoBeam: true,
 				// drawMetronomeMarks: false,
-				// drawLyricist: false,
 				// ...this.opotions,
 				
 			});
@@ -119,6 +119,7 @@ export default defineComponent({
 			osmd.EngravingRules.PageTopMargin = state.platform === IPlatform.PC ? 9 : 10; // 老师端顶部间距
 			osmd.EngravingRules.PageTopMarginNarrow = 3;
 			osmd.EngravingRules.PageLeftMargin = 2;
+			osmd.EngravingRules.BreathMarkDistance = 0.1; // 呼吸标记距离音符的位置,百分比
 			// 老师端上课页面,左右两边有功能按钮,所以左右边距需要加大
 			// if (state.isAttendClass && state.platform === IPlatform.PC) {
 			// 	osmd.EngravingRules.PageLeftMargin = 7;
@@ -134,10 +135,11 @@ export default defineComponent({
 				};
 			}
 			osmd.EngravingRules.DYMusicScoreId = state.examSongId || ''
+			osmd.EngravingRules.DYCustomRepeatCount = state.maxLyricNum || 0;
 			await osmd.load(musicData.score);
 			osmd.zoom = state.zoom;
 			osmd.render();
-			// console.log("🚀 ~ osmd:", osmd)
+			console.log("🚀 ~ osmd:", osmd)
 			emit("rendered", osmd);
 			resetFormate();
 			resetGivenFormate();

+ 6 - 3
src/view/music-score/testCheck.tsx

@@ -37,6 +37,8 @@ export const resetRenderMusicScore = (type?: string) => {
 	location.search = "?" + newSearch;
 };
 
+let wordList: never[] = [], deleteWordList: never[] = [];
+
 export default defineComponent({
 	name: "music-score",
 	emits: ["rendered"],
@@ -69,14 +71,15 @@ export default defineComponent({
                 const item = list[i];
                 try {
                     await getXML(item.evxml_file_url);
-                    await init(i);
+                    // await init(i);
                 } catch (error) {
                     errorNum += 1;
                     errorList.push(item.evxml_file_url);
                     console.log('🚀 ~ evxml解析报错:',`第${i}个xml`,error,'总错误数:',errorNum)
                 }
             }
-            console.log('🚀 ~ evxml循环完成','没有times和timegap的:',state.noTimes,'解析报错的xml:',errorList)
+            // console.log('🚀 ~ evxml循环完成','没有times和timegap的:',state.noTimes,'解析报错的xml:',errorList)
+			console.log('关键词',wordList, deleteWordList)
         }
 		/** 设置 曲谱模式,五线谱还是简谱 */
 		const setRenderType = () => {
@@ -88,7 +91,7 @@ export default defineComponent({
 		const getXML = async (evxml: any) => {
 			const res = await fetch(evxml).then((response) => response?.text());
             if (res) {
-                const xml = formatXML(res, evxml);
+                const xml = formatXML(res, evxml, wordList, deleteWordList);
                 musicData.score = state.isCombineRender ? xml : onlyVisible(xml, state.partIndex);
                 if (state.gradualTimes) {
                     state.gradual = getGradualLengthByXml(xml);

+ 2 - 2
vite.config.ts

@@ -76,9 +76,9 @@ export default defineConfig({
         // target: "https://kt.colexiu.com",
         // target: "https://test.lexiaoya.cn",
         // target: "https://kt.colexiu.com",
-        // target: "https://test.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
+        target: "https://test.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
         // target: "https://dev.resource.colexiu.com",
-        target: "https://test.kt.colexiu.com",
+        // target: "https://test.kt.colexiu.com",
         // target: "https://mec.colexiu.com",
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/instrument/, ""),