Browse Source

Merge branch 'feature-tianyong' into online

TIANYONG 1 year ago
parent
commit
b2c04aa621

+ 66 - 1
src/helpers/formateMusic.ts

@@ -591,6 +591,9 @@ export const formatXML = (xml: string): string => {
 	if (!xml) return "";
 	if (!xml) return "";
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
 	const measures = Array.from(xmlParse.getElementsByTagName("measure"));
 	const measures = Array.from(xmlParse.getElementsByTagName("measure"));
+	const repeats: any = Array.from(xmlParse.querySelectorAll('repeat'))
+	// 处理重复小节信息
+	parseXmlToRepeat(repeats)
 	// const words: any = xmlParse.getElementsByTagName("words");
 	// const words: any = xmlParse.getElementsByTagName("words");
 	// for (const word of words) {
 	// for (const word of words) {
 	// 	if (word && word.textContent?.trim() === "筒音作5") {
 	// 	if (word && word.textContent?.trim() === "筒音作5") {
@@ -976,6 +979,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				relaMeasureLength,
 				relaMeasureLength,
 				id: svgElement?.attrs.id,
 				id: svgElement?.attrs.id,
 				note: note.halfTone + 12, // see issue #224
 				note: note.halfTone + 12, // see issue #224
+				fixtime,
 				relativeTime: retain(relativeTime),
 				relativeTime: retain(relativeTime),
 				time: retain(relativeTime + fixtime),
 				time: retain(relativeTime + fixtime),
 				endtime: retain(relaEndtime + fixtime),
 				endtime: retain(relaEndtime + fixtime),
@@ -1033,11 +1037,72 @@ export const getNoteByMeasuresSlursStart = (note: any) => {
 		tieNote = note.noteElement.tie.StartNote;
 		tieNote = note.noteElement.tie.StartNote;
 	}
 	}
 	if (activeNote && tieNote && tieNote !== activeNote.noteElement) {
 	if (activeNote && tieNote && tieNote !== activeNote.noteElement) {
+		const arr: any = []
 		for (const note of state.times) {
 		for (const note of state.times) {
 			if (tieNote === note.noteElement) {
 			if (tieNote === note.noteElement) {
-				return note;
+				arr.push(note)
 			}
 			}
 		}
 		}
+		if (arr.length) {
+			return arr.find((n: any) => n.i === (note.i - 1))
+		}
 	}
 	}
 	return activeNote;
 	return activeNote;
 };
 };
+
+
+// 通过xml信息获取重播的小节信息
+const parseXmlToRepeat = (repeats: any) => {
+	if (!repeats.length) return
+	let repeatInfo: any = []
+	// 重复开始小节,结束小节
+	let start = 0, end = 0
+	for (let i = 0; i < repeats.length; i++) {
+		const element = repeats[i];
+		const direction = element.getAttribute('direction')
+		let parentElement = element.parentNode
+		while (parentElement && parentElement.tagName !== 'measure') {
+			parentElement = parentElement.parentNode
+		}
+		let notesNumber = parentElement.getAttribute('number') // 第多少小节,从1开始
+		notesNumber = notesNumber ? Number(notesNumber) : 0
+		if (direction === 'forward') {
+			start = notesNumber
+		} else if (direction === 'backward') {
+			end = notesNumber
+			repeatInfo.push({
+				start,
+				end
+			})
+		}
+	}
+	state.repeatInfo = repeatInfo
+	console.log('重播',repeatInfo)
+}
+
+// 校验当前选段是否满足重播条件
+export const verifyCanRepeat = (startNum: number, endNum: number) => {
+	let repeatIdx = -1
+	if (state.repeatInfo.length) {
+		for (let i = state.repeatInfo.length - 1; i >= 0; i--) {
+			const { start, end } = state.repeatInfo[i];
+			if (startNum <= start && endNum >= end) {
+				repeatIdx = i
+				return {
+					repeatIdx,
+					canRepeat: true
+				}
+				break;
+			}
+		}
+		return {
+			repeatIdx,
+			canRepeat: false
+		}
+	} else {
+		return {
+			repeatIdx,
+			canRepeat: false
+		}
+	}
+}

+ 11 - 5
src/page-instrument/custom-plugins/work-ealuating/index.tsx

@@ -2,7 +2,7 @@ import { defineComponent, onMounted, reactive, watch } from "vue";
 import { useRoute } from "vue-router";
 import { useRoute } from "vue-router";
 // import { verifyMembershipServices } from "../vip-verify";
 // import { verifyMembershipServices } from "../vip-verify";
 import { api_lessonTrainingSubmitTraining } from "../../api";
 import { api_lessonTrainingSubmitTraining } from "../../api";
-import state, { IDifficulty, handleSetSpeed } from "/src/state";
+import state, { IDifficulty, handleSetSpeed, hanldeDirectSelection, setSection } from "/src/state";
 import { getQuery } from "/src/utils/queryString";
 import { getQuery } from "/src/utils/queryString";
 import { evaluatingData } from "/src/view/evaluating";
 import { evaluatingData } from "/src/view/evaluating";
 
 
@@ -19,6 +19,9 @@ export default defineComponent({
 		const evaluatingWorkData = reactive({
 		const evaluatingWorkData = reactive({
 			difficulty: "" as IDifficulty,
 			difficulty: "" as IDifficulty,
 			evaluatingRecord: props.workeData?.id,
 			evaluatingRecord: props.workeData?.id,
+			start: "" as any,
+			end: "" as any,
+			evaluateSpeed: 0,
 		});
 		});
 		/** 隐藏评测功能 */
 		/** 隐藏评测功能 */
 		const handleHide = () => {
 		const handleHide = () => {
@@ -42,10 +45,13 @@ export default defineComponent({
 			if (["BEGINNER", "ADVANCED", "PERFORMER"].includes(trainingContent.evaluateDifficult)) {
 			if (["BEGINNER", "ADVANCED", "PERFORMER"].includes(trainingContent.evaluateDifficult)) {
 				evaluatingWorkData.difficulty = trainingContent.evaluateDifficult;
 				evaluatingWorkData.difficulty = trainingContent.evaluateDifficult;
 				state.setting.evaluationDifficulty = trainingContent.evaluateDifficult;
 				state.setting.evaluationDifficulty = trainingContent.evaluateDifficult;
-				//设置速度
-				if (trainingContent.evaluateSpeed) {
-					handleSetSpeed(trainingContent.evaluateSpeed);
-				}
+
+				evaluatingWorkData.evaluateSpeed = trainingContent.evaluateSpeed;
+				evaluatingWorkData.start = Number(trainingContent.practiceChapterBegin);
+				evaluatingWorkData.end = Number(trainingContent.practiceChapterEnd);
+				state.userChooseEndIndex = evaluatingWorkData.end
+				// 设置小节
+				setSection(evaluatingWorkData.start, evaluatingWorkData.end, evaluatingWorkData.evaluateSpeed);
 			}
 			}
 		};
 		};
 		/** 添加记录 */
 		/** 添加记录 */

+ 5 - 24
src/page-instrument/custom-plugins/work-home/index.tsx

@@ -2,7 +2,7 @@ import { defineComponent, onMounted, reactive, watch } from "vue";
 import styles from "./index.module.less";
 import styles from "./index.module.less";
 // import { verifyMembershipServices } from "../vip-verify";
 // import { verifyMembershipServices } from "../vip-verify";
 import { api_lessonTrainingSubmitTraining, api_lessonTrainingTrainingStudentDetail } from "../../api";
 import { api_lessonTrainingSubmitTraining, api_lessonTrainingTrainingStudentDetail } from "../../api";
-import state, { handleSetSpeed } from "/src/state";
+import state, { handleSetSpeed, hanldeDirectSelection, setSection } from "/src/state";
 
 
 export default defineComponent({
 export default defineComponent({
 	name: "HomeWork",
 	name: "HomeWork",
@@ -51,33 +51,14 @@ export default defineComponent({
 				training.trainingSpeed = trainingContent.practiceSpeed;
 				training.trainingSpeed = trainingContent.practiceSpeed;
 				training.start = Number(trainingContent.practiceChapterBegin);
 				training.start = Number(trainingContent.practiceChapterBegin);
 				training.end = Number(trainingContent.practiceChapterEnd);
 				training.end = Number(trainingContent.practiceChapterEnd);
+				state.userChooseEndIndex = training.end
 				if (training.isAddOk === 0) {
 				if (training.isAddOk === 0) {
-					
-					setSection();
+					// 设置小节
+					setSection(training.start, training.end, training.trainingSpeed);
 				}
 				}
 			}
 			}
 		};
 		};
-		/**设置小节 */
-		const setSection = () => {
-			const startNotes = state.times.filter(
-			  (n: any) => n.noteElement.sourceMeasure.MeasureNumberXML == training.start
-			)
-			const endNotes = state.times.filter(
-			  (n: any) => n.noteElement.sourceMeasure.MeasureNumberXML == training.end
-			)
-			const startNote = startNotes[0]
-			const endNote = endNotes[endNotes.length - 1]
-			//   console.log('🚀 ~ activeNote', startNote, endNote, questionExtendsInfo.value.end)
-			if (startNote && endNote) {
-			  // 设置小节
-			  state.sectionStatus = true
-			  state.section = [startNote, endNote]
-			  //设置速度
-			  if (training.trainingSpeed) {
-				handleSetSpeed(training.trainingSpeed);
-			  }
-			}
-		  }
+
 		const getWorkDetail = async () => {
 		const getWorkDetail = async () => {
 			const res = await api_lessonTrainingTrainingStudentDetail(props.workeData.id);
 			const res = await api_lessonTrainingTrainingStudentDetail(props.workeData.id);
 			if (res?.code === 200) {
 			if (res?.code === 200) {

BIN
src/page-instrument/evaluat-model/delay-check/image/icon-back.png


+ 9 - 1
src/page-instrument/evaluat-model/delay-check/index.module.less

@@ -13,7 +13,15 @@
     background-repeat: no-repeat;
     background-repeat: no-repeat;
     background-size: 102% 110%;
     background-size: 102% 110%;
 }
 }
-
+.delayBackBtn {
+    position: absolute;
+    left: 0.62rem;
+    top: 0.47rem;
+    display: block;
+    width: 1.19rem;
+    height: 1.19rem;
+    z-index: 99;
+}
 .item {
 .item {
     position: absolute;
     position: absolute;
     left: 0;
     left: 0;

+ 42 - 20
src/page-instrument/evaluat-model/delay-check/index.tsx

@@ -26,14 +26,17 @@ import icon_4_2 from "./image/icon_4_2.png";
 import icon_3_0 from "./image/icon_3_0.png";
 import icon_3_0 from "./image/icon_3_0.png";
 import icon_3_1 from "./image/icon_3_1.png";
 import icon_3_1 from "./image/icon_3_1.png";
 import icon_3_2 from "./image/icon_3_2.png";
 import icon_3_2 from "./image/icon_3_2.png";
+import iconBack from "./image/icon-back.png";
 import { evaluatingData } from "/src/view/evaluating";
 import { evaluatingData } from "/src/view/evaluating";
 import { IPostMessage } from "/src/utils/native-message";
 import { IPostMessage } from "/src/utils/native-message";
 import state from "/src/state";
 import state from "/src/state";
 
 
 export default defineComponent({
 export default defineComponent({
 	name: "delay-check",
 	name: "delay-check",
-	emits: ["close"],
+	emits: ["close", "back"],
 	setup(props, { emit }) {
 	setup(props, { emit }) {
+		// startTune定时器
+		let startTuneTimer: any = null
 		const anim = ref();
 		const anim = ref();
 		const data = reactive({
 		const data = reactive({
 			show: false,
 			show: false,
@@ -54,27 +57,19 @@ export default defineComponent({
 		};
 		};
 		/** 持续检测耳机状态 */
 		/** 持续检测耳机状态 */
 		const keepCheckEarphone = async () => {
 		const keepCheckEarphone = async () => {
-			if (data.step >= 7) return;
+			if (data.step >= 7 || !data.show) return;
 			evaluatingData.earphone = await getEarphoneState();
 			evaluatingData.earphone = await getEarphoneState();
+			// console.log('erji',evaluatingData.earphone,data.step)
 			if (evaluatingData.earphone) {
 			if (evaluatingData.earphone) {
-				if (data.step <= 5) {
-					if (data.checkStatus === "ing") {
-						data.count = 0;
-						clearTimeout(data.startTimer);
-						clearTimeout(data.stopTimer);
-						toggleTune("stop");
-					}
-					data.step = 3;
-				}
+				clearTimeout(data.startTimer);
+				clearTimeout(data.stopTimer);
+				clearTimeout(startTuneTimer);
+				data.checkStatus = "init"
+				data.step = 3
 			} else {
 			} else {
-				if (data.step === 2 || data.step === 3) {
-					data.step = 4;
-					setTimeout(() => {
-						data.step = 5;
-						data.checkStatus = "ing";
-						data.count = 0;
-						toggleTune("start");
-					}, 2000);
+				if (data.step === 3) {
+					data.step = 2
+					data.checkStatus = "init"
 				}
 				}
 			}
 			}
 			setTimeout(() => {
 			setTimeout(() => {
@@ -101,14 +96,36 @@ export default defineComponent({
 		onMounted(() => {
 		onMounted(() => {
 			data.show = true;
 			data.show = true;
 			sendResult(listenerResult);
 			sendResult(listenerResult);
+			keepCheckEarphone();
 		});
 		});
 		onUnmounted(() => {
 		onUnmounted(() => {
+			data.show = false;
 			removeResult(listenerResult);
 			removeResult(listenerResult);
 		});
 		});
 
 
 		const handleStartTune = async () => {
 		const handleStartTune = async () => {
-			await keepCheckEarphone();
 			// data.step = evaluatingData.earphone ? 4 : 3;
 			// data.step = evaluatingData.earphone ? 4 : 3;
+			if (evaluatingData.earphone) {
+				if (data.step <= 5) {
+					if (data.checkStatus === "ing") {
+						data.count = 0;
+						clearTimeout(data.startTimer);
+						clearTimeout(data.stopTimer);
+						toggleTune("stop");
+					}
+					data.step = 3;
+				}
+			} else {
+				if (data.step === 2 || data.step === 3) {
+					data.step = 4;
+					startTuneTimer = setTimeout(() => {
+						data.step = 5;
+						data.checkStatus = "ing";
+						data.count = 0;
+						toggleTune("start");
+					}, 2000);
+				}
+			}			
 		};
 		};
 		const hanldeEndTune = () => {
 		const hanldeEndTune = () => {
 			console.log('设置soundEffect')
 			console.log('设置soundEffect')
@@ -138,6 +155,11 @@ export default defineComponent({
 				v-model:show={data.show}
 				v-model:show={data.show}
 			>
 			>
 				<div class={styles.delayBox}>
 				<div class={styles.delayBox}>
+					{/*返回按钮*/}
+					<img class={styles.delayBackBtn} src={iconBack} onClick={() => {
+						clearTimeout(startTuneTimer)
+						emit("back")
+					}} />
 					<div class={[styles.item, data.step !== 7 && styles.show]}>
 					<div class={[styles.item, data.step !== 7 && styles.show]}>
 						<Vue3Lottie animationData={bg}></Vue3Lottie>
 						<Vue3Lottie animationData={bg}></Vue3Lottie>
 					</div>
 					</div>

+ 89 - 14
src/page-instrument/evaluat-model/index.tsx

@@ -3,7 +3,7 @@ import { connectWebsocket, evaluatingData, handleEndBegin, handleStartBegin, han
 import Earphone from "./earphone";
 import Earphone from "./earphone";
 import styles from "./index.module.less";
 import styles from "./index.module.less";
 import SoundEffect from "./sound-effect";
 import SoundEffect from "./sound-effect";
-import state from "/src/state";
+import state, { handleRessetState } from "/src/state";
 import { storeData } from "/src/store";
 import { storeData } from "/src/store";
 import { browser } from "/src/utils";
 import { browser } from "/src/utils";
 import { getNoteByMeasuresSlursStart } from "/src/helpers/formateMusic";
 import { getNoteByMeasuresSlursStart } from "/src/helpers/formateMusic";
@@ -19,20 +19,42 @@ import iconTastBg from "./icons/task-bg.svg";
 import iconEvaluat from "./icons/evaluating.json";
 import iconEvaluat from "./icons/evaluating.json";
 import { api_musicPracticeRecordVideoUpload } from "../api";
 import { api_musicPracticeRecordVideoUpload } from "../api";
 import DelayCheck from "./delay-check";
 import DelayCheck from "./delay-check";
+import { headTopData } from "../header-top/index";
+import { getQuery } from "/src/utils/queryString";
 
 
 // frequency 频率, amplitude 振幅, decibels 分贝
 // frequency 频率, amplitude 振幅, decibels 分贝
 type TCriteria = "frequency" | "amplitude" | "decibels";
 type TCriteria = "frequency" | "amplitude" | "decibels";
 
 
+/**
+ * 节拍器时长
+ * 评测模式时,应该传节拍器时长
+ * 阶段评测时,判断是否从第一小节开始,并且曲子本身含有节拍器,需要传节拍器时长,否则传0
+ */
+let actualBeatLength = 0
+let calculateInfo: any = {}
+
 export default defineComponent({
 export default defineComponent({
   name: "evaluat-model",
   name: "evaluat-model",
   setup() {
   setup() {
+    const query = getQuery();
     const evaluatModel = reactive({
     const evaluatModel = reactive({
       tips: true,
       tips: true,
       evaluatUpdateAudio: false,
       evaluatUpdateAudio: false,
       isSaveVideo: state.setting.camera && state.setting.saveToAlbum,
       isSaveVideo: state.setting.camera && state.setting.saveToAlbum,
       shareMode: false,
       shareMode: false,
     });
     });
-
+    /**
+     * 检测返回
+     */
+    const handleDelayBack = () => {
+      if (query.workRecord) {
+        evaluatingData.soundEffectMode = false;
+      } else {
+        evaluatingData.soundEffectMode = false;
+        handleRessetState();
+        headTopData.modeType = "init";
+      }
+    }
     /**
     /**
      * 执行检测
      * 执行检测
      */
      */
@@ -92,19 +114,56 @@ export default defineComponent({
 
 
     /** 生成评测曲谱数据 */
     /** 生成评测曲谱数据 */
     const formatTimes = () => {
     const formatTimes = () => {
+      let starTime = 0
       let ListenMode = false;
       let ListenMode = false;
       let dontEvaluatingMode = false;
       let dontEvaluatingMode = false;
       let skip = false;
       let skip = false;
       const datas = [];
       const datas = [];
-      for (let index = 0; index < state.times.length; index++) {
-        const item = state.times[index];
+      let selectTimes = state.times
+      let unitTestIdx = 0
+			let preTime = 0
+			let preTimes = []
+      // 系统节拍器时长
+      actualBeatLength = Math.round(state.times[0].fixtime * 1000 / 1)
+      // 如果是阶段评测,选取该阶段的times
+			if (state.isSelectMeasureMode && state.section.length) {
+				const startIndex = state.times.findIndex(
+				  (n: any) => n.noteId == state.section[0].noteId
+				)
+				let endIndex = state.times.findIndex(
+				  (n: any) => n.noteId == state.section[1].noteId
+				)
+        endIndex = endIndex < state.section[1].i ? state.section[1].i : endIndex
+        if (startIndex > 1) {
+					// firstNoteTime应该取预备小节的第一个音符的开始播放的时间
+					const idx = startIndex - 1 - (state.times[startIndex-1].si)
+					preTime = state.times[idx] ? state.times[idx].time * 1000 : 0
+				}
+        actualBeatLength = startIndex == 0 && !state.needTick ? actualBeatLength : 0
+				selectTimes = state.times.filter((n: any, index: number) => {
+				  return index >= startIndex && index <= endIndex
+				})
+        preTimes = state.times.filter((n: any, index: number) => {
+					return index < startIndex
+				})				
+				unitTestIdx = startIndex
+        starTime = selectTimes[0].sourceRelativeTime || selectTimes[0].relativeTime
+			}	
+			// 阶段评测beatLength需要加上预备小节的持续时长
+			actualBeatLength = preTimes.length ? actualBeatLength + preTimes[preTimes.length - 1].relaMeasureLength * 1000 : actualBeatLength				
+			let firstNoteTime = unitTestIdx > 1 ? preTime : 0
+			let measureIndex = -1
+			let recordMeasure = -1
+
+      for (let index = 0; index < selectTimes.length; index++) {
+        const item = selectTimes[index];
         const note = getNoteByMeasuresSlursStart(item);
         const note = getNoteByMeasuresSlursStart(item);
         // #8701 bug: 评测模式,是以曲谱本身的速度进行评测,所以rate取1,不需要转换
         // #8701 bug: 评测模式,是以曲谱本身的速度进行评测,所以rate取1,不需要转换
         // const rate = state.speed / state.originSpeed;
         // const rate = state.speed / state.originSpeed;
         const rate = 1;
         const rate = 1;
         const difftime = item.difftime;
         const difftime = item.difftime;
-        const start = difftime + (item.sourceRelativeTime || item.relativeTime);
-        const end = difftime + (item.sourceRelaEndtime || item.relaEndtime);
+        const start = difftime + (item.sourceRelativeTime || item.relativeTime) - starTime;
+        const end = difftime + (item.sourceRelaEndtime || item.relaEndtime) - starTime;
         const isStaccato = note.noteElement.voiceEntry.isStaccato();
         const isStaccato = note.noteElement.voiceEntry.isStaccato();
         const noteRate = isStaccato ? 0.5 : 1;
         const noteRate = isStaccato ? 0.5 : 1;
         if (note.formatLyricsEntries.contains("Play") || note.formatLyricsEntries.contains("Play...")) {
         if (note.formatLyricsEntries.contains("Play") || note.formatLyricsEntries.contains("Play...")) {
@@ -119,7 +178,7 @@ export default defineComponent({
         if (note.formatLyricsEntries.contains("纯律")) {
         if (note.formatLyricsEntries.contains("纯律")) {
           dontEvaluatingMode = true;
           dontEvaluatingMode = true;
         }
         }
-        const nextNote = state.times[index + 1];
+        const nextNote = selectTimes[index + 1];
         // console.log("noteinfo", note.noteElement.isRestFlag && !!note.stave && !!nextNote)
         // console.log("noteinfo", note.noteElement.isRestFlag && !!note.stave && !!nextNote)
         if (skip && (note.stave || !item.noteElement.isRestFlag || (nextNote && !nextNote.noteElement.isRestFlag))) {
         if (skip && (note.stave || !item.noteElement.isRestFlag || (nextNote && !nextNote.noteElement.isRestFlag))) {
           skip = false;
           skip = false;
@@ -130,6 +189,10 @@ export default defineComponent({
         // console.log(note.measureOpenIndex, item.measureOpenIndex, note);
         // console.log(note.measureOpenIndex, item.measureOpenIndex, note);
         // console.log("skip", skip)
         // console.log("skip", skip)
         // console.log(end,start,rate,noteRate, '评测')
         // console.log(end,start,rate,noteRate, '评测')
+        if (note.measureOpenIndex != recordMeasure) {
+					measureIndex++
+					recordMeasure = note.measureOpenIndex
+				}
         const data = {
         const data = {
           timeStamp: (start * 1000) / rate,
           timeStamp: (start * 1000) / rate,
           duration: ((end * 1000) / rate - (start * 1000) / rate) * noteRate,
           duration: ((end * 1000) / rate - (start * 1000) / rate) * noteRate,
@@ -137,23 +200,27 @@ export default defineComponent({
           nextFrequency: item.nextFrequency,
           nextFrequency: item.nextFrequency,
           prevFrequency: item.prevFrequency,
           prevFrequency: item.prevFrequency,
           // 重复的情况index会自然累加,render的index是谱面渲染的index
           // 重复的情况index会自然累加,render的index是谱面渲染的index
-          measureIndex: note.measureOpenIndex,
+          measureIndex: measureIndex,
           measureRenderIndex: item.measureListIndex,
           measureRenderIndex: item.measureListIndex,
           dontEvaluating: ListenMode || dontEvaluatingMode || item.skipMode,
           dontEvaluating: ListenMode || dontEvaluatingMode || item.skipMode,
-          musicalNotesIndex: item.i,
+          musicalNotesIndex: index,
           denominator: note.noteElement?.Length.denominator,
           denominator: note.noteElement?.Length.denominator,
           isOrnament: !!note?.voiceEntry?.ornamentContainer,
           isOrnament: !!note?.voiceEntry?.ornamentContainer,
         };
         };
         datas.push(data);
         datas.push(data);
       }
       }
-      return datas;
+			return {
+				datas,
+				firstNoteTime
+			}
     };
     };
     /** 连接websocket */
     /** 连接websocket */
     const handleConnect = async () => {
     const handleConnect = async () => {
       const behaviorId = localStorage.getItem("behaviorId") || undefined;
       const behaviorId = localStorage.getItem("behaviorId") || undefined;
       const rate = state.speed / state.originSpeed;
       const rate = state.speed / state.originSpeed;
+      calculateInfo = formatTimes()
       const content = {
       const content = {
-        musicXmlInfos: formatTimes(),
+        musicXmlInfos: calculateInfo.datas,
         subjectId: state.subjectId,
         subjectId: state.subjectId,
         detailId: state.detailId,
         detailId: state.detailId,
         examSongId: state.examSongId,
         examSongId: state.examSongId,
@@ -166,7 +233,8 @@ export default defineComponent({
         reactionTimeMs: state.setting.reactionTimeMs,
         reactionTimeMs: state.setting.reactionTimeMs,
         speed: state.speed,
         speed: state.speed,
         heardLevel: state.setting.evaluationDifficulty,
         heardLevel: state.setting.evaluationDifficulty,
-        beatLength: Math.round((state.fixtime * 1000) / rate),
+        // beatLength: Math.round((state.fixtime * 1000) / rate),
+        beatLength: actualBeatLength,
         evaluationCriteria: getEvaluationCriteria(),
         evaluationCriteria: getEvaluationCriteria(),
       };
       };
       await connectWebsocket(content);
       await connectWebsocket(content);
@@ -199,7 +267,7 @@ export default defineComponent({
         handleStartEvaluat();
         handleStartEvaluat();
       } else if (type === "tryagain") {
       } else if (type === "tryagain") {
         // 再来一次
         // 再来一次
-        handleStartBegin();
+        startBtnHandle()
       }
       }
       evaluatingData.resulstMode = false;
       evaluatingData.resulstMode = false;
     };
     };
@@ -236,6 +304,10 @@ export default defineComponent({
       showToast("上传成功");
       showToast("上传成功");
     };
     };
 
 
+    const startBtnHandle = () => {
+      handleConnect();
+      handleStartBegin(calculateInfo.firstNoteTime);
+    }
     onMounted(() => {
     onMounted(() => {
       evaluatingData.isDisabledPlayMusic = true;
       evaluatingData.isDisabledPlayMusic = true;
       handlePerformDetection();
       handlePerformDetection();
@@ -244,7 +316,9 @@ export default defineComponent({
       <div>
       <div>
         <Transition name="pop-center">
         <Transition name="pop-center">
           {evaluatingData.websocketState && !evaluatingData.startBegin && evaluatingData.checkEnd && (
           {evaluatingData.websocketState && !evaluatingData.startBegin && evaluatingData.checkEnd && (
-            <div class={styles.startBtn} onClick={handleStartBegin}>
+            <div class={styles.startBtn} onClick={() => {
+              startBtnHandle()
+            }}>
               <img src={iconEvaluat.evaluatingStart} />
               <img src={iconEvaluat.evaluatingStart} />
             </div>
             </div>
           )}
           )}
@@ -275,6 +349,7 @@ export default defineComponent({
               evaluatingData.soundEffectMode = false;
               evaluatingData.soundEffectMode = false;
               handlePerformDetection();
               handlePerformDetection();
             }}
             }}
+            onBack={() => handleDelayBack()}
           />
           />
         )}
         )}
 
 

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

@@ -163,7 +163,8 @@ export default defineComponent({
       state.enableNotation = data.notation ? true : false;
       state.enableNotation = data.notation ? true : false;
       // 是否是合奏,先根据background判断
       // 是否是合奏,先根据background判断
       state.isConcert = data.background?.length > 1;
       state.isConcert = data.background?.length > 1;
-
+      // 开启预备小节
+      state.isOpenPrepare = true;
       // console.log("🚀 ~ state.subjectId:", state.subjectId, state.track as any , state.subjectId)
       // console.log("🚀 ~ state.subjectId:", state.subjectId, state.track as any , state.subjectId)
       // 是否打击乐
       // 是否打击乐
       // state.isPercussion =
       // state.isPercussion =

+ 61 - 3
src/state.ts

@@ -10,6 +10,7 @@ import { audioListStart, getAudioCurrentTime, getAudioDuration, setAudioCurrentT
 import { toggleFollow } from "./view/follow-practice";
 import { toggleFollow } from "./view/follow-practice";
 import { browser, setStorageSpeed } from "./utils";
 import { browser, setStorageSpeed } from "./utils";
 import { api_createMusicPlayer } from "./helpers/communication";
 import { api_createMusicPlayer } from "./helpers/communication";
+import { verifyCanRepeat } from "./helpers/formateMusic";
 
 
 /** 入门 | 进阶 | 大师 */
 /** 入门 | 进阶 | 大师 */
 export type IDifficulty = "BEGINNER" | "ADVANCED" | "PERFORMER";
 export type IDifficulty = "BEGINNER" | "ADVANCED" | "PERFORMER";
@@ -180,7 +181,11 @@ const state = reactive({
   /** 是否隐藏评测报告弹窗,保存演奏按钮,默认不隐藏 */
   /** 是否隐藏评测报告弹窗,保存演奏按钮,默认不隐藏 */
   isHideEvaluatReportSaveBtn: false,
   isHideEvaluatReportSaveBtn: false,
   /** 是否是合奏 */
   /** 是否是合奏 */
-  isConcert: false
+  isConcert: false,
+  /** 用户选择的结束小节数 */
+  userChooseEndIndex: 0,
+  /** 重播小节集合信息 */
+  repeatInfo: [],  
 });
 });
 const browserInfo = browser();
 const browserInfo = browser();
 let offset_duration = 0;
 let offset_duration = 0;
@@ -254,7 +259,7 @@ const handlePlaying = () => {
   const duration = getAudioDuration();
   const duration = getAudioDuration();
   state.playProgress = (currentTime / duration) * 100;
   state.playProgress = (currentTime / duration) * 100;
   let item = getNote(currentTime);
   let item = getNote(currentTime);
-
+  // console.log(11111,currentTime,duration,state.playSource, item.i)
   if (item) {
   if (item) {
     // 选段状态下
     // 选段状态下
     if (state.sectionStatus && state.section.length === 2) {
     if (state.sectionStatus && state.section.length === 2) {
@@ -304,6 +309,9 @@ export const skipNotePlay = (itemIndex: number, isStart = false) => {
  */
  */
 export const togglePlay = async (playState?: "play" | "paused") => {
 export const togglePlay = async (playState?: "play" | "paused") => {
   state.playState = playState ? playState : state.playState === "paused" ? "play" : "paused";
   state.playState = playState ? playState : state.playState === "paused" ? "play" : "paused";
+  if (state.playState === "play" && state.sectionStatus && state.section.length == 2 && state.playProgress === 0) {
+    resetPlaybackToStart();
+  }
   // 设置为开始播放时, 如果需要节拍,先播放节拍器
   // 设置为开始播放时, 如果需要节拍,先播放节拍器
   if (state.playState === "play" && state.needTick) {
   if (state.playState === "play" && state.needTick) {
     const tickend = await handleStartTick();
     const tickend = await handleStartTick();
@@ -374,6 +382,7 @@ const setCursorPosition = (note: any, cursor: any) => {
       if (!bbox) return;
       if (!bbox) return;
       const baseW = state.platform === IPlatform.PC ? 29 : 18;
       const baseW = state.platform === IPlatform.PC ? 29 : 18;
       const width = (bbox.width - baseW) / 3;
       const width = (bbox.width - baseW) / 3;
+      // console.log(555555,bbox.left,width)
       cursor.cursorElement.style.left = bbox.left + "px";
       cursor.cursorElement.style.left = bbox.left + "px";
       cursor.cursorElement.style.transform = `translateX(${width}px)`;
       cursor.cursorElement.style.transform = `translateX(${width}px)`;
     });
     });
@@ -381,6 +390,7 @@ const setCursorPosition = (note: any, cursor: any) => {
 };
 };
 /** 跳转到下一个音符 */
 /** 跳转到下一个音符 */
 export const gotoNext = (note: any) => {
 export const gotoNext = (note: any) => {
+  // console.log(33333333333,state.activeNoteIndex,note.i)
   const num = note.i;
   const num = note.i;
 
 
   if (state.activeNoteIndex === note.i) {
   if (state.activeNoteIndex === note.i) {
@@ -391,6 +401,7 @@ export const gotoNext = (note: any) => {
     }
     }
     return;
     return;
   }
   }
+  
   const osmd = state.osmd;
   const osmd = state.osmd;
   let prev = state.activeNoteIndex;
   let prev = state.activeNoteIndex;
   state.activeNoteIndex = num;
   state.activeNoteIndex = num;
@@ -525,11 +536,58 @@ export const handleSelection = (item: any) => {
   }
   }
 };
 };
 
 
+/** 阶段练习、阶段评测设置选段小节 */
+export const setSection = (start: number, end: number, userSpeed?: number) => {
+  const startNotes = state.times.filter(
+    (n: any) => n.noteElement.sourceMeasure.MeasureNumberXML == start
+  )
+  const endNotes = state.times.filter(
+    (n: any) => n.noteElement.sourceMeasure.MeasureNumberXML == end
+  )
+
+  const lastEndId = endNotes[endNotes.length - 1].noteId
+  let lastEndNotes = endNotes.filter((n: any) => n.noteId === lastEndId)
+  // 是否符合重播规则
+  const canRepeatInfo = verifyCanRepeat(start, end)
+  const isCanRepeat = canRepeatInfo.canRepeat
+  // 如果符合重播规则,但是lastEndNotes长度为1,则需要向前找,直到找到lastEndNotes长度为2
+  let currentEndNum: number = end
+  const lastEndIndex = state.repeatInfo[canRepeatInfo.repeatIdx]?.end || 0
+  while (isCanRepeat && lastEndNotes.length === 1 && lastEndNotes[0].MeasureNumberXML <= lastEndIndex) {
+    currentEndNum = currentEndNum - 1
+    const newEndNotes = state.times.filter(
+      (n: any) => n.noteElement.sourceMeasure.MeasureNumberXML == currentEndNum
+    )
+    const newLastEndId = newEndNotes[newEndNotes.length - 1].noteId
+    lastEndNotes = newEndNotes.filter((n: any) => n.noteId === newLastEndId)
+  }
+
+  const endIdx: any = (isCanRepeat && canRepeatInfo.repeatIdx == state.repeatInfo.length - 1) ? lastEndNotes.length - 1 : 0
+  const startNote = startNotes[0]
+  // const endNote = endNotes[endNotes.length - 1]
+  const endNote = lastEndNotes[endIdx]			
+  
+  if (startNote && endNote) {
+    state.isSelectMeasureMode = true;
+    // 设置小节
+    hanldeDirectSelection([startNote, endNote]);
+
+    //设置速度
+    if (userSpeed) {
+      handleSetSpeed(userSpeed);
+    }
+  }
+}	
+
+
 /** 直接设置选段 */
 /** 直接设置选段 */
 export const hanldeDirectSelection = (list: any[]) => {
 export const hanldeDirectSelection = (list: any[]) => {
   if (!Array.isArray(list) || list.length !== 2) return;
   if (!Array.isArray(list) || list.length !== 2) return;
   state.sectionStatus = true;
   state.sectionStatus = true;
-  state.section = formateSelectMearure(list);
+  setTimeout(() => {
+    state.section = formateSelectMearure(list);
+    console.log(333333333,state.section)
+  }, 500);
 };
 };
 let offsetTop = 0;
 let offsetTop = 0;
 /**
 /**

+ 5 - 0
src/view/audio-list/index.tsx

@@ -56,8 +56,10 @@ export const getAudioCurrentTime = () => {
 		const c = getMidiCurrentTime();
 		const c = getMidiCurrentTime();
 		return c;
 		return c;
 	}
 	}
+	// console.log('返回的时间',audioData.songEle?.currentTime,audioData.progress)
 	if (state.playSource === "music") return audioData.songEle?.currentTime || audioData.progress;
 	if (state.playSource === "music") return audioData.songEle?.currentTime || audioData.progress;
 	if (state.playSource === "background") return audioData.backgroundEle?.currentTime || audioData.progress;
 	if (state.playSource === "background") return audioData.backgroundEle?.currentTime || audioData.progress;
+	
 	return audioData.songEle?.currentTime || audioData.progress;
 	return audioData.songEle?.currentTime || audioData.progress;
 };
 };
 /** 获取曲谱的总时间 */
 /** 获取曲谱的总时间 */
@@ -72,6 +74,7 @@ export const getAudioDuration = () => {
 
 
 /** 设置播放的开始时间 */
 /** 设置播放的开始时间 */
 export const setAudioCurrentTime = (time: number, index = 0) => {
 export const setAudioCurrentTime = (time: number, index = 0) => {
+	// console.log('开始时间12345',time)
 	// 如果是midi播放
 	// 如果是midi播放
 	if (audioData.midiRender) {
 	if (audioData.midiRender) {
 		setMidiCurrentTime(index);
 		setMidiCurrentTime(index);
@@ -149,6 +152,8 @@ export default defineComponent({
 			const total = res?.totalDuration || res?.content?.totalDuration;
 			const total = res?.totalDuration || res?.content?.totalDuration;
 			const time = currentTime / 1000;
 			const time = currentTime / 1000;
 			audioData.progress = time;
 			audioData.progress = time;
+			audioData.songEle && (audioData.songEle.currentTime = time);
+			audioData.backgroundEle && (audioData.backgroundEle.currentTime = time);
 			audioData.duration = total / 1000;
 			audioData.duration = total / 1000;
 			if (
 			if (
 				res?.content?.totalDuration > 1000 &&
 				res?.content?.totalDuration > 1000 &&

+ 8 - 5
src/view/evaluating/index.tsx

@@ -236,7 +236,7 @@ const handleScoreResult = (res?: IPostMessage) => {
 };
 };
 
 
 /** 开始评测 */
 /** 开始评测 */
-export const handleStartBegin = async () => {
+export const handleStartBegin = async (preTimes?: number) => {
 	evaluatingData.isComplete = false;
 	evaluatingData.isComplete = false;
 	evaluatingData.evaluatings = {};
 	evaluatingData.evaluatings = {};
 	evaluatingData.resultData = {};
 	evaluatingData.resultData = {};
@@ -271,7 +271,8 @@ export const handleStartBegin = async () => {
 	}
 	}
 	//开始录音
 	//开始录音
 	await api_startRecording({
 	await api_startRecording({
-		accompanimentState: state.setting.enableAccompaniment ? 1 : 0
+		accompanimentState: state.setting.enableAccompaniment ? 1 : 0,
+		firstNoteTime: preTimes || 0,
 	});
 	});
 
 
 	// 如果开启了摄像头, 开启录制视频
 	// 如果开启了摄像头, 开启录制视频
@@ -340,9 +341,11 @@ export const handleEndEvaluat = (isComplete = false) => {
 	// 结束录音
 	// 结束录音
 	api_stopRecording();
 	api_stopRecording();
 	// 结束评测
 	// 结束评测
-	endEvaluating({
-		musicScoreId: state.examSongId,
-	});
+	setTimeout(() => {
+		endEvaluating({
+			musicScoreId: state.examSongId,
+		});
+	}, 500);
 	showLoadingToast({
 	showLoadingToast({
 		message: "评分中",
 		message: "评分中",
 		duration: 0,
 		duration: 0,

+ 6 - 4
src/view/selection/index.tsx

@@ -137,7 +137,7 @@ const calcNoteData = () => {
 			}
 			}
 		}
 		}
 	}
 	}
-	// console.log("🚀 ~ selectData.notes:", selectData.staves);
+	console.log("🚀 ~ selectData.notes:", selectData.notes, selectData.staves);
 };
 };
 
 
 /** 重新计算 */
 /** 重新计算 */
@@ -168,24 +168,26 @@ export default defineComponent({
 						}
 						}
 					}
 					}
 					if (state.section.length === 2) {
 					if (state.section.length === 2) {
+						// 实际的结束位置
+						const actualEndIndex = state.userChooseEndIndex > state.section[1].MeasureNumberXML ? state.userChooseEndIndex : state.section[1].MeasureNumberXML
 						// 选段预备拍背景
 						// 选段预备拍背景
 						if (state.sectionFirst && item.MeasureNumberXML === state.sectionFirst.MeasureNumberXML) {
 						if (state.sectionFirst && item.MeasureNumberXML === state.sectionFirst.MeasureNumberXML) {
 							return styles.prepareStaveBox;
 							return styles.prepareStaveBox;
 						}
 						}
 						if (
 						if (
 							item.MeasureNumberXML >= state.section[0].MeasureNumberXML &&
 							item.MeasureNumberXML >= state.section[0].MeasureNumberXML &&
-							item.MeasureNumberXML <= state.section[1].MeasureNumberXML
+							item.MeasureNumberXML <= actualEndIndex
 						) {
 						) {
 							if (
 							if (
 								item.MeasureNumberXML == state.section[0].MeasureNumberXML &&
 								item.MeasureNumberXML == state.section[0].MeasureNumberXML &&
-								item.MeasureNumberXML == state.section[1].MeasureNumberXML
+								item.MeasureNumberXML == actualEndIndex
 							) {
 							) {
 								return styles.centerStaveBox;
 								return styles.centerStaveBox;
 							}
 							}
 							if (item.MeasureNumberXML == state.section[0].MeasureNumberXML) {
 							if (item.MeasureNumberXML == state.section[0].MeasureNumberXML) {
 								return styles.leftStaveBox;
 								return styles.leftStaveBox;
 							}
 							}
-							if (item.MeasureNumberXML == state.section[1].MeasureNumberXML) {
+							if (item.MeasureNumberXML == actualEndIndex) {
 								return styles.rightStaveBox;
 								return styles.rightStaveBox;
 							}
 							}
 							return styles.staveBox;
 							return styles.staveBox;